babyos2(14)—— user stack, stack expand, load elf

到目前为止,babyos2用户态栈只分配并映射了一个页,所以当栈的内容超过这个界限时,就会PAGE_FAULT,并且无法处理导致进程halt。先简单验证下:

int main()
{
    uint32 cs = 0xffffffff;
    __asm__ volatile("movl %%cs, %%eax" : "=a" (cs));

    int i = 0;
    while (buffer[i] != '\0') {
        i++;
    }

    char num[9] = {0};
    int j = 0;
    while (cs != 0) {
        num[j++] = digits[cs % 16];
        cs /= 16;
    }

    while (j) {
        buffer[i++] = num[--j];
    }
    buffer[i++] = '\n';
    print(buffer);

    before_fork = (unsigned *) mmap(0, 4096, PROT_READ | PROT_WRITE, 0);
    for (unsigned int i = 0; i < 1024; i++) {
        before_fork[i] = i;
    }

    // fork
    int32 ret = fork();
    if (ret == 0) {
        // child
        buffer2[0] = 'I';
        buffer2[1] = 'C';
        buffer2[2] = ',';
        buffer2[3] = '\0';
        while (1) {
            for (int i = 0; i < 100000000; i++) ;
            print(buffer2);

            times2++;
            if (times2 == 10) {
                print(test2);
                for (unsigned int i = 100; i < 110; i++) {
                    before_fork[i] = i+1;
                }
                for (unsigned int i = 0; i < 16; i++) {
                    before_fork[i] = 100*i;
                    print_int(before_fork[i], 16, 0);
                }
            }
        }
    }
    else {
        // parent
        buffer[0] = 'I';
        buffer[1] = ',';
        buffer[2] = '\0';

        while (1) {
            for (int i = 0; i < 100000000; i++) ;
            print(buffer);

            times++;
            if (times == 15) {
                print(test);
                for (unsigned int i = 200; i < 210; i++) {
                    before_fork[i] = i;
                }
                for (unsigned int i = 0; i < 16; i++) {
                    print_int(before_fork[i], 16, 0);
                }

                // test stack
                unsigned ints[4000];
                for (unsigned i = 0; i < 4000; i++) {
                    ints[i] = i;
                }
                print_int(ints[400], 16, 0);
            }
        }
    }

    return 0;
}

目前依然没有任何用户态库,这个丑陋的测试函数还得用一阵子,将就下吧暂时……
增加的几行代码在 // test stack下面
因为4000个ints远远超过了4096字节,所以当调用函数写返回地址时会超过1个page,并且目前的page_fault无法处理这种情况:
这里写图片描述

1)sys_exec改为加载elf文件,并为栈分配单独的vma,带有VM_STACK标记:

io_clb_t clb_elf;
int32 sys_exec(trap_frame_t* frame)
{
    uint32 lba = frame->ebx;
    uint32 sector_num = frame->ecx;

    // 1. read init from hd
    clb_elf.flags = 0;
    clb_elf.read = 1;
    clb_elf.dev = 0;
    clb_elf.lba = lba;

    // 2. read elf from hard disk
    uint8* buffer = (uint8*) os()->get_mm()->alloc_pages(3); // 8 pages, 32K
    for (uint8* b = buffer; b < buffer + SECT_SIZE*sector_num; b += SECT_SIZE) {
        memset(clb_elf.buffer, 0, SECT_SIZE);
        os()->get_ide()->request(&clb_elf);
        memcpy(b, clb_elf.buffer, SECT_SIZE);

        clb_elf.flags = 0;
        clb_elf.lba++;
    }

    elf_hdr_t *elf = (elf_hdr_t *) (buffer);
    uint8 *base = (uint8 *) elf;

    // 3. check if it's a valid elf file
    if (elf->magic != ELF_MAGIC) {
        return -1;
    }

    pde_t* pg_dir = current->m_vmm.get_pg_dir();

    // 4. load program segments
    prog_hdr_t *ph = (prog_hdr_t *)(base + elf->phoff);
    prog_hdr_t *end_ph = ph + elf->phnum;
    for (; ph < end_ph; ph++) {
        if (ph->type != PT_LOAD || ph->filesz == 0) {
            continue;
        }
        void* vaddr = (void*) (ph->vaddr & PAGE_MASK);
        uint32 offset = ph->vaddr - (uint32)vaddr;
        uint32 len = PAGE_ALIGN(ph->filesz + ((uint32)vaddr & (PAGE_SIZE-1)));
        uint32 pagenum = len / PAGE_SIZE;
        uint32 order = 0, num = 1;
        while (num < pagenum) {
            num *= 2;
            order++;
        }

        current->m_vmm.do_mmap((uint32) vaddr, len, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_FIXED);
        void* mem = os()->get_mm()->alloc_pages(order);
        os()->get_mm()->map_pages(pg_dir, vaddr, VA2PA(mem), len, PTE_W | PTE_U);
        memcpy(mem+offset, base+ph->off, ph->filesz);
        if (ph->memsz > ph->filesz) {
            memset(mem+offset+ph->filesz, 0, ph->memsz - ph->filesz);
        }
    }

    // 5. frame
    frame->cs = (SEG_UCODE << 3 | 0x3);
    frame->ds = (SEG_UDATA << 3 | 0x3);
    frame->es = (SEG_UDATA << 3 | 0x3);
    frame->ss = (SEG_UDATA << 3 | 0x3);
    frame->fs = (SEG_UDATA << 3 | 0x3);
    frame->gs = (SEG_UDATA << 3 | 0x3);

    // 6. stack, esp
    vm_area_t* vma = (vm_area_t *) os()->get_obj_pool(VMA_POOL)->alloc_from_pool();
    if (vma == NULL) {
        return -1;
    }
    vma->m_end   = USER_STACK_TOP;
    vma->m_start = USER_STACK_TOP - PAGE_SIZE;
    vma->m_page_prot = PROT_READ | PROT_WRITE;
    vma->m_flags = VM_STACK;
    vma->m_next = NULL;
    current->m_vmm.insert_vma(vma);

    void* stack = os()->get_mm()->alloc_pages(0);
    os()->get_mm()->map_pages(pg_dir, (void*) USER_STACK_TOP-PAGE_SIZE, VA2PA(stack), PAGE_SIZE, PTE_W | PTE_U);
    frame->esp = USER_STACK_TOP - PAGE_SIZE;

    // 7. eip
    void (*entry)(void) = (void(*)(void))(elf->entry);
    frame->eip = (uint32)entry;         // need get eip by load elf and get address

    return 0;
}

2)修改page_fault

/*
 * bit 0: 0 no page found, 1 protection fault
 * bit 1: 0 read, 1 write
 * bit 2: 0 kernel, 1 user
 */
uint32 vmm_t::do_page_fault(trap_frame_t* frame)
{
    uint32 addr = 0xffffffff;
    __asm__ volatile("movl %%cr2, %%eax" : "=a" (addr));

    vm_area_t* vma = find_vma(addr);

    /* not find the vma or out of range */
    if (vma == NULL || vma->m_start > addr) {
        uint32 expand_stk = 0;
        if (frame->err & 0x4) {
            if (vma != NULL && (vma->m_flags & VM_STACK) && addr + 32 >= frame->esp) {
                console()->kprintf(YELLOW, "expand stack\n");
                expand_stack(vma, addr);
                expand_stk = 1;
            }
            else {
                console()->kprintf(RED, "segment fault, addr: %x!\n", addr);
            }
        }

        if (!expand_stk) {
            return -1;
        }
    }

    /* find a vma and the addr in this vma */
    if (vma != NULL && vma->m_start <= addr) {
        if (frame->err & 0x1) {
            return do_protection_fault(vma, addr, (uint32) (frame->err & 2));
        }

        console()->kprintf(YELLOW, "handle no page, addr: %x\n", addr);

        /* no page found */
        void* mem = os()->get_mm()->alloc_pages(0);
        console()->kprintf(YELLOW, "addr: %x, map page: %x\n", addr, os()->get_mm()->va_2_pa(mem));

        addr = (addr & PAGE_MASK);
        os()->get_mm()->map_pages(m_pg_dir, (void*) addr, VA2PA(mem), PAGE_SIZE, PTE_W | PTE_U);
        console()->kprintf(GREEN, "alloc and map pages\n");
    }

    return 0;
}

uint32 vmm_t::expand_stack(vm_area_t* vma, uint32 addr)
{
    addr &= PAGE_MASK;
    uint32 grow = (vma->m_start - addr) >> PAGE_SHIFT;
    vma->m_start = addr;

    return 0;
}

当满足if (vma != NULL && (vma->m_flags & VM_STACK) && addr + 32 >= frame->esp)时,即找到了一个vma,但vma->m_start > addr,即addr比vma->m_start小时,如果此vma带有 VM_STACK标记,且addr + 32 >= frame->esp,我们认为是堆栈操作低于栈顶导致的,我们做栈扩展。

这里写图片描述

修改后可以发现可以正常运行了。

但这时依然存在问题:
用户态fork后,拷贝了父进程的vma,如果再执行exec想要执行新的代码,此时因为没有释放掉旧的vma,因为babyos2的mmap在map到已有的位置时会失败,所以加载新的elf过程中,mmap代码段、数据段等时会失败。

下一个进程就叫shell吧,一个简单的用户态shell是必不可少的,但在这之前先搞一个简单的用户态的lib吧,让init清爽些…,shell也得用这些必不可少的系统调用,库函数。

/*
 * guzhoudiaoke@126.com
 * 2017-12-10
 */

#ifndef _USERLIB_H_
#define _USERLIB_H_

#include "types.h"
#include "syscall.h"


#define PROT_NONE           0x0       /* page can not be accessed */
#define PROT_READ           0x1       /* page can be read */
#define PROT_WRITE          0x2       /* page can be written */
#define PROT_EXEC           0x4       /* page can be executed */


class userlib_t {
public:
    static int fork();
    static void print(const char *str);
    static void *mmap(uint32 addr, uint32 len, uint32 prot, uint32 flags);

    static void print_int(int32 n, int32 base, int32 sign);
};

#endif

/*
 * guzhoudiaoke@126.com
 * 2017-12-10
 */

#include "userlib.h"

static char digits[] = "0123456789abcdef";

int userlib_t::fork()
{
    int ret;
    __asm__ volatile("int $0x80" : "=a" (ret) : "a" (SYS_FORK));
    return ret;
}

void userlib_t::print(const char *str)
{
    __asm__ volatile("int $0x80" : : "b" (str), "a" (SYS_PRINT));
}

void *userlib_t::mmap(uint32 addr, uint32 len, uint32 prot, uint32 flags)
{
    uint32 ret = 0;
    __asm__ volatile("int $0x80" : "=a" (ret) : "a" (SYS_MMAP), "b" (addr), "c" (len), "d" (prot), "S" (flags));

    return (void*) ret;
}

char buffer_int[16] = {1};
char buffer_int_r[16] = {1};
void userlib_t::print_int(int32 n, int32 base, int32 sign)
{

    uint32 num = (uint32)n;
    if (sign && (sign = (n < 0))) {
        num = -n;
    }

    int i = 0;
    do {
        buffer_int[i++] = digits[num % base];
        num /= base;
    } while (num != 0);

    if (base == 16) {
        while (i < 8) {
            buffer_int[i++] = '0';
        }
        buffer_int[i++] = 'x';
        buffer_int[i++] = '0';
    }

    if (sign) {
        buffer_int[i++] = '-';
    }

    int j = 0;
    while (i-- > 0) {
        buffer_int_r[j++] = buffer_int[i];
    }

    buffer_int_r[j++] = ',';
    print(buffer_int_r);
}

修改后的init:

/*
 * guzhoudiaoke@126.com
 * 2017-11-4
 */

#include "userlib.h"

int main()
{
    uint32 cs = 0xffffffff;
    __asm__ volatile("movl %%cs, %%eax" : "=a" (cs));

    // print cs to show work in user mode
    userlib_t::print("This is printed by init, cs = ");
    userlib_t::print_int(cs, 16, 0);
    userlib_t::print("\n");

    // test mmap
    unsigned* before_fork = (unsigned *) userlib_t::mmap(0, 4096, PROT_READ | PROT_WRITE, 0);
    for (unsigned int i = 0; i < 1024; i++) {
        before_fork[i] = i;
    }

    // fork
    int32 ret = userlib_t::fork();
    if (ret == 0) {
        // child
        while (1) {
            for (int i = 0; i < 100000000; i++) ;
            userlib_t::print("IC,");
        }
    }
    else {
        // parent
        while (1) {
            for (int i = 0; i < 100000000; i++) ;
            userlib_t::print("I,");
        }
    }

    return 0;
}

清爽多了……

尝试子进程exec shell:

init.cc:

/*
 * guzhoudiaoke@126.com
 * 2017-11-4
 */

#include "userlib.h"

#define SHELL_LBA       544
#define SHELL_SECT_NUM  32

int main()
{
    uint32 cs = 0xffffffff;
    __asm__ volatile("movl %%cs, %%eax" : "=a" (cs));

    // print cs to show work in user mode
    userlib_t::print("This is printed by init, cs = ");
    userlib_t::print_int(cs, 16, 0);
    userlib_t::print("\n");

    // test mmap
    unsigned* before_fork = (unsigned *) userlib_t::mmap(0, 4096, PROT_READ | PROT_WRITE, 0);
    for (unsigned int i = 0; i < 1024; i++) {
        before_fork[i] = i;
    }

    // fork
    int32 ret = userlib_t::fork();
    if (ret == 0) {
        // child
        int ret = userlib_t::exec(SHELL_LBA, SHELL_SECT_NUM);
        if (ret != 0) {
            userlib_t::print("exec failed!!!\n");
            while (1) {
            }
        }
    }
    else {
        // parent
        while (1) {
            for (int i = 0; i < 100000000; i++) ;
            userlib_t::print("I,");
        }
    }

    return 0;
}

shell.cc:
/*
 * guzhoudiaoke@126.com
 * 2017-12-10
 */

#include "shell.h"
#include "userlib.h"

int main()
{
    userlib_t::print("This is printed by shell...\n");
    while (1) {
        for (int i = 0; i < 100000000; i++) ;
        userlib_t::print("S,");
    }

    return 0;
}

这里写图片描述

因为elf加载的时候,目前babyos2编译init, shell的时候代码段地址设为0x1000,所以init, shell的代码段加载地址是一样的,init fork出来的子进程拷贝了父进程的vma,已经拥有了0x1000~0x3000的一个vma,所以exec时要加载新的vma指定地址0x1000 do_mmap失败。
要解决这个问题,需要exec的时候先释放掉拷贝的父进程的映射的页,页表,vma。

io_clb_t clb_elf;
int32 sys_exec(trap_frame_t* frame)
{
    uint32 lba = frame->ebx;
    uint32 sector_num = frame->ecx;

    // 1. read init from hd
    clb_elf.flags = 0;
    clb_elf.read = 1;
    clb_elf.dev = 0;
    clb_elf.lba = lba;

    // 2. read elf from hard disk
    uint8* buffer = (uint8*) os()->get_mm()->alloc_pages(3); // 8 pages, 32K
    for (uint8* b = buffer; b < buffer + SECT_SIZE*sector_num; b += SECT_SIZE) {
        memset(clb_elf.buffer, 0, SECT_SIZE);
        os()->get_ide()->request(&clb_elf);
        memcpy(b, clb_elf.buffer, SECT_SIZE);

        clb_elf.flags = 0;
        clb_elf.lba++;
    }

    elf_hdr_t *elf = (elf_hdr_t *) (buffer);
    uint8 *base = (uint8 *) elf;

    // 3. check if it's a valid elf file
    if (elf->magic != ELF_MAGIC) {
        return -1;
    }

    pde_t* pg_dir = current->m_vmm.get_pg_dir();

    // flush old mmap
    current->m_vmm.release();

    // 4. load program segments
    prog_hdr_t *ph = (prog_hdr_t *)(base + elf->phoff);
    prog_hdr_t *end_ph = ph + elf->phnum;
    for (; ph < end_ph; ph++) {
        if (ph->type != PT_LOAD || ph->filesz == 0) {
            continue;
        }
        void* vaddr = (void*) (ph->vaddr & PAGE_MASK);
        uint32 offset = ph->vaddr - (uint32)vaddr;
        uint32 len = PAGE_ALIGN(ph->filesz + ((uint32)vaddr & (PAGE_SIZE-1)));
        uint32 pagenum = len / PAGE_SIZE;
        uint32 order = 0, num = 1;
        while (num < pagenum) {
            num *= 2;
            order++;
        }

        int32 ret = current->m_vmm.do_mmap((uint32) vaddr, len, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_FIXED);
        if (ret < 0) {
            return -1;
        }
        void* mem = os()->get_mm()->alloc_pages(order);
        os()->get_mm()->map_pages(pg_dir, vaddr, VA2PA(mem), len, PTE_W | PTE_U);
        memcpy(mem+offset, base+ph->off, ph->filesz);
        if (ph->memsz > ph->filesz) {
            memset(mem+offset+ph->filesz, 0, ph->memsz - ph->filesz);
        }
    }

    // 5. frame
    frame->cs = (SEG_UCODE << 3 | 0x3);
    frame->ds = (SEG_UDATA << 3 | 0x3);
    frame->es = (SEG_UDATA << 3 | 0x3);
    frame->ss = (SEG_UDATA << 3 | 0x3);
    frame->fs = (SEG_UDATA << 3 | 0x3);
    frame->gs = (SEG_UDATA << 3 | 0x3);

    // 6. stack, esp
    vm_area_t* vma = (vm_area_t *) os()->get_obj_pool(VMA_POOL)->alloc_from_pool();
    if (vma == NULL) {
        return -1;
    }
    vma->m_end   = USER_STACK_TOP;
    vma->m_start = USER_STACK_TOP - PAGE_SIZE;
    vma->m_page_prot = PROT_READ | PROT_WRITE;
    vma->m_flags = VM_STACK;
    vma->m_next = NULL;
    if (current->m_vmm.insert_vma(vma) != 0) {
        return -1;
    }

    void* stack = os()->get_mm()->alloc_pages(0);
    os()->get_mm()->map_pages(pg_dir, (void*) USER_STACK_TOP-PAGE_SIZE, VA2PA(stack), PAGE_SIZE, PTE_W | PTE_U);
    frame->esp = USER_STACK_TOP-PAGE_SIZE;

    // 7. eip
    void (*entry)(void) = (void(*)(void))(elf->entry);
    frame->eip = (uint32)entry;         // need get eip by load elf and get address

    return 0;
}

void vmm_t::free_page_range(uint32 start, uint32 end)
{
    uint32 addr = start & PAGE_MASK;
    while (addr < end) {
        uint32 pa = os()->get_mm()->va_2_pa((void *) addr);
        if (pa != -1) {
            os()->get_mm()->free_pages((void *) (PA2VA(pa)), 0);
        }

        addr += PAGE_SIZE;
    }
}

void vmm_t::free_page_table()
{
    for (uint32 i = 0; i < KERNEL_BASE/(4*MB); i++) {
        pde_t *pde = &m_pg_dir[i];
        if (!(*pde & PTE_P)) {
            continue;
        }

        pte_t* table = (pte_t *) PA2VA((*pde) & PAGE_MASK);
        os()->get_mm()->free_pages(table, 0);
        *pde = 0;
    }
}

void vmm_t::release()
{
    // 1. pages
    vm_area_t* vma = m_mmap;
    while (vma != NULL) {
        console()->kprintf(YELLOW, "free page range: [%x, %x]\n", vma->m_start, vma->m_end);
        free_page_range(vma->m_start, vma->m_end);
        vma = vma->m_next;
    }

    // 2. page table
    free_page_table();

    // 3. vmas
    vma = m_mmap;
    while (vma != NULL) {
        vm_area_t* del = vma;
        vma = vma->m_next;

        console()->kprintf(YELLOW, "removing vma: [%x, %x]\n", del->m_start, del->m_end);
        os()->get_obj_pool(VMA_POOL)->free_object(del);
    }
    m_mmap = NULL;
}

加载新的elf之前调用current->m_vmm.release(); 释放旧的页,页表,vma。
这里写图片描述

shell进程启动起来之后一直打印S,经过释放旧的,加载新的,init fork出来的子进程成功开始执行shell进程的代码。

BSS段:
现在加载elf时忽略掉了filesz == 0的段,所以加载bss段,若有未初始化的全局变量,会出现PAGE_FAULT:

char buffer_int[16] = {0};
char buffer_int_r[16] = {0};
void userlib_t::print_int(int32 n, int32 base, int32 sign)
{

    uint32 num = (uint32)n;
    if (sign && (sign = (n < 0))) {
        num = -n;
    }

    int i = 0;
    do {
        buffer_int[i++] = digits[num % base];
        num /= base;
    } while (num != 0);

    if (base == 16) {
        while (i < 8) {
            buffer_int[i++] = '0';
        }
        buffer_int[i++] = 'x';
        buffer_int[i++] = '0';
    }

    if (sign) {
        buffer_int[i++] = '-';
    }

    int j = 0;
    while (i-- > 0) {
        buffer_int_r[j++] = buffer_int[i];
    }

    buffer_int_r[j++] = ',';
    print(buffer_int_r);
}

这里写图片描述

    // 5. load program segments
    prog_hdr_t *ph = (prog_hdr_t *)(base + elf->phoff);
    prog_hdr_t *end_ph = ph + elf->phnum;
    for (; ph < end_ph; ph++) {
        if (ph->type != PT_LOAD) {
            continue;
        }
        void* vaddr = (void*) (ph->vaddr & PAGE_MASK);
        uint32 offset = ph->vaddr - (uint32)vaddr;
        uint32 len = PAGE_ALIGN(ph->memsz + ((uint32)vaddr & (PAGE_SIZE-1)));
        uint32 pagenum = len / PAGE_SIZE;
        uint32 order = 0, num = 1;
        while (num < pagenum) {
            num *= 2;
            order++;
        }

        int32 ret = current->m_vmm.do_mmap((uint32) vaddr, len, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_FIXED);
        if (ret < 0) {
            return -1;
        }
        void* mem = os()->get_mm()->alloc_pages(order);
        os()->get_mm()->map_pages(pg_dir, vaddr, VA2PA(mem), len, PTE_W | PTE_U);
        memcpy(mem+offset, base+ph->off, ph->filesz);
        if (ph->memsz > ph->filesz) {
            memset(mem+offset+ph->filesz, 0, ph->memsz - ph->filesz);
        }
    }

修改加载elf代码如上:
这里写图片描述

到这里babyos2已经能够做基本的fork和exec动作,处理基本的mmap和缺页。

后面需要做的:
1)一个简单的shell,能执行简单的命令,会对测试有帮助
2)现在进程无法退出,只能一直while (1),需要一个简单的exit,wait_pid
3)sleep,wakeup目前还是可选的
4)brk也暂时不是必须的
5)目前BSS段是不能用的,所以全局变量初始化成0是有问题的。。。需要解决 [本blog已修改]
6)目前加载elf靠的是lba和扇区数,没有文件的概念,一个简单的文件系统是需要的
7)目前调度靠的是轮询,且时间片只有1,一个高级点的调度算法是需要的,但目前不着急
8)一个更丰富的user lib,可以用到啥增加啥
9)读取输入,是shell必须的
10)kmalloc, kfree 目前暂时不是必须的
11)用于IO的buffer,可以做文件系统时再做。

最近有点忙,进度可能要……

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值