到目前为止,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,可以做文件系统时再做。
最近有点忙,进度可能要……