光fork出与父进程的一样的映像是没什么意义的,最终是要用指定映像替换掉原来父进程的映像,这也是实现最终的SHELL必不可少的一步。这里我们要模拟的是linux的echo命令。具体实现如下。
先在工程根目录建立一个command文件夹,先建立start.asm:
command/start.asm
- ;By Marcus Xing
- ;command/start.asm
- ;Echo的入口
-
- ;声明
- extern Echo
- extern Exit
-
- bits 32
-
- [section .text]
-
- global _start
-
- ;入口
- _start:
- push eax ;存放串指针数组的首址
- push ecx ;存放串指针数组的length
- call Echo ;调用Echo
-
- ;Echo的返回值压栈,作为Exit的形参,作为exit status返回给父进程
- push eax
- call Exit ;调用Exit
-
- hlt ;不会执行到这
-
其中的eax,ecx进栈指令后面再说,Echo的实现如下:
command/echo.c
-
-
-
-
-
-
-
- int Printf(const char *fmt,...);
-
-
-
-
- int Echo(int argc,char *argv[])
- {
- int i;
- for(i = 0;i < argc;i++)
- {
- Printf("%s%s",(i == 0) ? "" : " ",argv[i]);
- }
-
- Printf("/n");
- return 0;
- }
功能很简单,把形参的指针数组遍历一遍打印出来即可。
我们看到里面用到了Printf和Exit函数,我们当然可以把这些函数的代码以及所有用户接口的代码直接放进去,不过这样很麻烦,我们大可以把所有接口函数做成一个库,链接的时候链接进去就可以了。而在之前没有专门把接口提炼出来,下面来做这个工作,过程简单繁琐,不赘述,新添加的这几个库如下:
lib/lib_mm.c
lib/lib_fs.c
lib/lib_ipc.c
lib/lib_print.c
lib/lib_str.c
除了上述这些文件,还需要把kernel/system_call.asm添加进库,下面是命令:
有了库就可以编译链接咯,下面是命令:
好了,映像就弄好了,我们需要把这个映像写到硬盘里,到时候就可以把它读进来在子进程中替换父进程的映像了。作者的做法是把所有的映像打成一个TAR包手工写到我们的文件系统中,到时候在Init进程解压出来。我这里先急于求成,先用一个很不完美的做法,就是先把映像写到一个指定位置,这个位置正是创建一个文件占据的首扇区,就是数据区的129扇区,相对于整个硬盘的字节偏移量为:0xa01800+ 129 * 512 = 10557952,下面是写入命令:
OK,下面就是Exec的接口了:
lib/lib_mm.c
-
-
-
- void Execl(const char *path,int argc,char *arg,...)
- {
- char **args = (char**)&arg;
- Execv(path,argc,args);
- while(1);
- }
-
-
-
-
-
-
-
- static void Execv(const char *path,int argc,char *argv[])
- {
- char arg_stack[1024];
- int stack_len = sizeof(char*) * argc;
-
- *(int*)&arg_stack[stack_len] = 0;
- stack_len += sizeof(char*);
-
- char **p = argv;
- int c = argc;
- char **q = (char**)&arg_stack;
-
-
- while(c--)
- {
- *q++ = &arg_stack[stack_len];
- Str_Cpy(&arg_stack[stack_len],*p);
- stack_len += Str_Len(*p++);
- arg_stack[stack_len] = '/0';
- stack_len++;
- }
-
-
- Message exec_msg;
- exec_msg.msg_type = MM_EXEC;
- exec_msg.p1 = (void*)path;
- exec_msg.i1 = Str_Len(path);
- exec_msg.p2 = (void*)arg_stack;
- exec_msg.i2 = stack_len;
- exec_msg.i3 = argc;
-
- Send_Receive_Shell(SEND,PROC_MM_PID,&exec_msg);
- }
在MM进程中接收相应的消息:
kernel/mm.c
-
- case MM_EXEC:
- reply = 0;
- Do_Exec(&mm_msg);
- break;
具体实现如下:
kernel/mm.c
-
-
-
- static void Do_Exec(Message *exec_msg)
- {
- int path_len = exec_msg->i1;
- int caller_pid = exec_msg->src_proc_pid;
-
-
- char path_name[MAX_PATH_SIZE];
- Memory_Copy(path_name,Virtual_Addr_2_Linear_Addr(caller_pid,exec_msg->p1),path_len);
- path_name[MAX_PATH_SIZE] = '/0';
-
-
- int fd = Open(path_name,O_CREATE | O_RDWR);
- char *a = (char*)MM_BUF;
- Read_File(fd,a,3524);
- Close(fd);
-
-
- Elf32_Ehdr *elf_hdr = (Elf32_Ehdr*)MM_BUF;
- Elf32_Phdr *elf_pdr;
- int i;
- for(i = 0;i < elf_hdr->e_phnum;i++)
- {
- elf_pdr = (Elf32_Phdr*)(MM_BUF + elf_hdr->e_phoff + i * elf_hdr->e_phentsize);
- if(elf_pdr->p_type == 1)
- {
- Memory_Copy(Virtual_Addr_2_Linear_Addr(caller_pid,elf_pdr->p_vaddr),
- (void*)(MM_BUF + elf_pdr->p_offset),
- elf_pdr->p_filesz);
- }
- }
-
- int arg_stack_len = exec_msg->i2;
- void* arg_stack_buf = exec_msg->p2;
-
- char *arg_stack_copy[1024];
-
- Memory_Copy((void*)arg_stack_copy,
- Virtual_Addr_2_Linear_Addr(caller_pid,arg_stack_buf),
- arg_stack_len);
-
-
- u8 *arg_stack_pos = (u8*)(1024 * 1024 - 1024);
-
- int c = exec_msg->i3;
-
- int delta = (int)arg_stack_pos - (int)arg_stack_buf;
- char **p = (char**)&arg_stack_copy;
- while(c--)
- {
- *p += delta;
- p++;
- }
-
- Memory_Copy(Virtual_Addr_2_Linear_Addr(caller_pid,arg_stack_pos),
- (void*)arg_stack_copy,
- arg_stack_len);
-
-
- PCB_Table[caller_pid].stack_frame.ecx = exec_msg->i3;
- PCB_Table[caller_pid].stack_frame.eax = (u32)arg_stack_pos;
-
-
- PCB_Table[caller_pid].stack_frame.esp = (u32)(1024 * 1024 - 1024);
- PCB_Table[caller_pid].stack_frame.eip = elf_hdr->e_entry;
-
- Str_Cpy(PCB_Table[caller_pid].proc_name,path_name);
- }
解析ELF文件用到的结构如下:
include/const.h
-
- typedef struct
- {
- unsigned char e_ident[16];
- u16 e_type;
- u16 e_machine;
- u32 e_version;
- u32 e_entry;
- u32 e_phoff;
- u32 e_shoff;
- u32 e_flags;
- u16 e_ehsize;
- u16 e_phentsize;
- u16 e_phnum;
- u16 e_shentsize;
- u16 e_shnum;
- u16 e_shstrndx;
- }Elf32_Ehdr;
-
-
- typedef struct
- {
- u32 p_type;
- u32 p_offset;
- u32 p_vaddr;
- u32 p_paddr;
- u32 p_filesz;
- u32 p_memsz;
- u32 p_flags;
- u32 p_align;
- }Elf32_Phdr;
这里有3点要注意。
一是Write_File的时候,由于我们是手工写入映像数据的,所以映像文件的i结点的i_size为0,写入时需要把相应的判断语句注释掉,Do_Read_Write_File函数中:
ke
二还是这个函数,由于之前没有详细的测试,所以在读入的时候出现了BUG:
-
- else if(m->msg_type == FS_READ_FILE)
- {
- Memory_Copy(rdwr_buf,fs_buf + (pos % SECTOR_SIZE),copy_bytes);
- }
- pos += copy_bytes;
-
-
- left_bytes -= copy_bytes;
- done_bytes += copy_bytes;
- rdwr_buf += copy_bytes;
- Write_Sector(p_inode->i_dev,begin_sec + i);
- p_caller->fd_ptr_table[fd]->fd_pos += copy_bytes;
-
-
- if(left_bytes <= 0)
- {
- break;
- }
三是MM进程的堆栈大小增大一倍到2048字节,否则出现莫名其妙的错误,估计是栈溢出了。。
OK,最后在进程A添加测试语句:
kernel/proc.c
-
-
-
- void Proc_A()
- {
- int pid = Fork();
- int ret_status;
- if(pid > 0)
- {
- while(1)
- {
- pid = Wait(&ret_status);
- if(pid != -1)
- {
- Printf("child proc{%d} exit status is:%d",pid,ret_status);
- }
- }
- }
- else if(pid == 0)
- {
- Execl("/echo",2,"hello","world");
-
- }
- }
传入了两个参数给Echo:hello,world。
make,bochs,运行结果如下:
生平写了多个Hello World,但无疑这个最牛B。。