Exec非完美版

    光fork出与父进程的一样的映像是没什么意义的,最终是要用指定映像替换掉原来父进程的映像,这也是实现最终的SHELL必不可少的一步。这里我们要模拟的是linux的echo命令。具体实现如下。

    先在工程根目录建立一个command文件夹,先建立start.asm:

    command/start.asm

Code:
  1. ;By Marcus Xing   
  2. ;command/start.asm   
  3. ;Echo的入口   
  4.   
  5. ;声明   
  6. extern Echo   
  7. extern Exit   
  8.   
  9. bits 32   
  10.   
  11. [section .text]   
  12.   
  13. global _start   
  14.   
  15. ;入口   
  16. _start:   
  17.     push    eax     ;存放串指针数组的首址   
  18.     push    ecx     ;存放串指针数组的length   
  19.     call    Echo    ;调用Echo   
  20.        
  21.     ;Echo的返回值压栈,作为Exit的形参,作为exit status返回给父进程   
  22.     push    eax        
  23.     call    Exit    ;调用Exit   
  24.        
  25.     hlt             ;不会执行到这   
  26.        

    其中的eax,ecx进栈指令后面再说,Echo的实现如下:

    command/echo.c

Code:
  1. /*    
  2.     By Marcus Xing  
  3.     command/echo.c  
  4.     echo命令的执行体  
  5. */  
  6.   
  7. /* 函数声明 */  
  8. int Printf(const char *fmt,...);   
  9.   
  10. /*--------------------------------------------------------------------------Echo  
  11.     echo命令执行体,功能模仿LINUX的echo  
  12. */  
  13. int Echo(int argc,char *argv[])   
  14. {   
  15.     int i;   
  16.     for(i = 0;i < argc;i++)   
  17.     {   
  18.         Printf("%s%s",(i == 0) ? "" : " ",argv[i]);   
  19.     }   
  20.        
  21.     Printf("/n");   
  22.     return 0;   
  23. }   

    功能很简单,把形参的指针数组遍历一遍打印出来即可。

    我们看到里面用到了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

Code:
  1. /*-------------------------------------------------------------------------Execl  
  2.     Execl用户接口  
  3. */  
  4. void Execl(const char *path,int argc,char *arg,...)   
  5. {   
  6.     char **args = (char**)&arg;    
  7.     Execv(path,argc,args);     
  8.     while(1);   /* 死循环,等待eip被重置 */  
  9. }   
  10.   
  11. /*-------------------------------------------------------------------------Execv  
  12.     功能函数,把指针数组做成一个命令行参数空间  
  13.     这个空间给Echo使用。  
  14.     内容是argc个指针,组成一个指针数组  
  15.     指向空间后续的字符串的实体  
  16. */  
  17. static void Execv(const char *path,int argc,char *argv[])   
  18. {   
  19.     char arg_stack[1024];   /* 命令行参数空间 */  
  20.     int stack_len = sizeof(char*) * argc;   /* 所有指针的空间 */  
  21.        
  22.     *(int*)&arg_stack[stack_len] = 0;       /* 指针数组的结束符 */  
  23.     stack_len += sizeof(char*);             /* 算上结束符的空间 */  
  24.        
  25.     char **p = argv;   
  26.     int c = argc;   
  27.     char **q = (char**)&arg_stack;   
  28.        
  29.     /* 复制参数实体到空间去,并把指针指向相应的参数实体 */  
  30.     while(c--)   
  31.     {   
  32.         *q++ = &arg_stack[stack_len];   
  33.         Str_Cpy(&arg_stack[stack_len],*p);   
  34.         stack_len += Str_Len(*p++);   
  35.         arg_stack[stack_len] = '/0';   
  36.         stack_len++;   
  37.     }   
  38.        
  39.     /* 发送消息到MM进程,完成映像替换 */  
  40.     Message exec_msg;   
  41.     exec_msg.msg_type = MM_EXEC;   
  42.     exec_msg.p1 = (void*)path;   
  43.     exec_msg.i1 = Str_Len(path);   
  44.     exec_msg.p2 = (void*)arg_stack;   
  45.     exec_msg.i2 = stack_len;   
  46.     exec_msg.i3 = argc;   
  47.        
  48.     Send_Receive_Shell(SEND,PROC_MM_PID,&exec_msg);   
  49. }   

    在MM进程中接收相应的消息:

    kernel/mm.c

Code:
  1. /* 处理MM_EXEC消息 */  
  2. case MM_EXEC:   
  3.     reply = 0;   
  4.     Do_Exec(&mm_msg);   
  5.     break;  

    具体实现如下:

    kernel/mm.c

Code:
  1. /*-----------------------------------------------------------------------Do_Exec  
  2.       
  3. */  
  4. static void Do_Exec(Message *exec_msg)   
  5. {   
  6.     int path_len = exec_msg->i1;    /* 取出路径的长度 */  
  7.     int caller_pid = exec_msg->src_proc_pid;    /* 取出caller的PID */  
  8.        
  9.     /* 拷贝路径到RING1 */  
  10.     char path_name[MAX_PATH_SIZE];   
  11.     Memory_Copy(path_name,Virtual_Addr_2_Linear_Addr(caller_pid,exec_msg->p1),path_len);   
  12.     path_name[MAX_PATH_SIZE] = '/0';   
  13.        
  14.     /* 读出echo映像到MM_BUF,MM_BUF在8M处 */  
  15.     int fd = Open(path_name,O_CREATE | O_RDWR);   
  16.     char *a = (char*)MM_BUF;   
  17.     Read_File(fd,a,3524);   
  18.     Close(fd);   
  19.        
  20.     /* 解析ELF格式的映像,放置到对应的位置中 */  
  21.     Elf32_Ehdr *elf_hdr = (Elf32_Ehdr*)MM_BUF;   
  22.     Elf32_Phdr *elf_pdr;   
  23.     int i;   
  24.     for(i = 0;i < elf_hdr->e_phnum;i++)   
  25.     {   
  26.         elf_pdr = (Elf32_Phdr*)(MM_BUF + elf_hdr->e_phoff + i * elf_hdr->e_phentsize);   
  27.         if(elf_pdr->p_type == 1)   
  28.         {   
  29.             Memory_Copy(Virtual_Addr_2_Linear_Addr(caller_pid,elf_pdr->p_vaddr),   
  30.                 (void*)(MM_BUF + elf_pdr->p_offset),   
  31.                 elf_pdr->p_filesz);   
  32.         }   
  33.     }   
  34.        
  35.     int arg_stack_len = exec_msg->i2;       /* 取出参数空间的大小 */  
  36.     void* arg_stack_buf = exec_msg->p2;     /* 取出参数空间的指针 */  
  37.        
  38.     char *arg_stack_copy[1024];             /* RING1的空间 */  
  39.     /* 拷贝到RING1 */  
  40.     Memory_Copy((void*)arg_stack_copy,   
  41.         Virtual_Addr_2_Linear_Addr(caller_pid,arg_stack_buf),   
  42.         arg_stack_len);   
  43.            
  44.     /* 参数空间准备放置到地址,相对于子进程的空间 */  
  45.     u8 *arg_stack_pos = (u8*)(1024 * 1024 - 1024);     
  46.        
  47.     int c = exec_msg->i3;   /* 参数个数 */  
  48.     /* 改变参数空间的串指针的偏移,因为串实体的放置位置改变了 */  
  49.     int delta = (int)arg_stack_pos - (int)arg_stack_buf;   
  50.     char **p = (char**)&arg_stack_copy;   
  51.     while(c--)   
  52.     {   
  53.         *p += delta;   
  54.         p++;   
  55.     }   
  56.     /* 把参数空间写回到子进程的指定空间处 */  
  57.     Memory_Copy(Virtual_Addr_2_Linear_Addr(caller_pid,arg_stack_pos),   
  58.         (void*)arg_stack_copy,   
  59.         arg_stack_len);   
  60.            
  61.     /* ecx设置成参数大小,eax设置成参数空间的地址,两者是Echo的形参 */  
  62.     PCB_Table[caller_pid].stack_frame.ecx = exec_msg->i3;      
  63.     PCB_Table[caller_pid].stack_frame.eax = (u32)arg_stack_pos;   
  64.        
  65.     /* 设置相应的esp,eip */  
  66.     PCB_Table[caller_pid].stack_frame.esp = (u32)(1024 * 1024 - 1024);   
  67.     PCB_Table[caller_pid].stack_frame.eip = elf_hdr->e_entry;   
  68.   
  69.     Str_Cpy(PCB_Table[caller_pid].proc_name,path_name); /* 把路径名作为子进程的进程名 */  
  70. }  

    解析ELF文件用到的结构如下:

    include/const.h

Code:
  1. /*  ELF文件元信息结构 */  
  2. typedef struct    
  3. {   
  4.     unsigned char   e_ident[16];   
  5.     u16             e_type;   
  6.     u16             e_machine;   
  7.     u32             e_version;   
  8.     u32             e_entry;   
  9.     u32             e_phoff;   
  10.     u32             e_shoff;   
  11.     u32             e_flags;   
  12.     u16             e_ehsize;   
  13.     u16             e_phentsize;   
  14.     u16             e_phnum;   
  15.     u16             e_shentsize;   
  16.     u16             e_shnum;   
  17.     u16             e_shstrndx;   
  18. }Elf32_Ehdr;   
  19.   
  20. /* ELF的段信息结构 */  
  21. typedef struct  
  22. {   
  23.     u32             p_type;   
  24.     u32             p_offset;   
  25.     u32             p_vaddr;   
  26.     u32             p_paddr;   
  27.     u32             p_filesz;   
  28.     u32             p_memsz;   
  29.     u32             p_flags;   
  30.     u32             p_align;   
  31. }Elf32_Phdr;  

 

    这里有3点要注意。

    一是Write_File的时候,由于我们是手工写入映像数据的,所以映像文件的i结点的i_size为0,写入时需要把相应的判断语句注释掉,Do_Read_Write_File函数中:

    ke

Code:
  1. /* 如果是读文件,则判断是否超过文件实际大小 */  
  2. /*  
  3. else if(m->msg_type == FS_READ_FILE)  
  4. {     
  5.     if(pos + rdwr_bytes_size > p_inode->i_size)  
  6.     {  
  7.         Printf("Read Error,Beyond the Limit!/n");  
  8.         return -1;  
  9.     }  
  10. }  
  11. */  

    二还是这个函数,由于之前没有详细的测试,所以在读入的时候出现了BUG:

 

Code:
  1. /* 读 */  
  2. else if(m->msg_type == FS_READ_FILE)   
  3. {   
  4.     Memory_Copy(rdwr_buf,fs_buf + (pos % SECTOR_SIZE),copy_bytes);   
  5. }   
  6. pos += copy_bytes;  /* 移动pos到下一个读写位置 */  
  7.   
  8. /* 更新工作 */  
  9. left_bytes -= copy_bytes;   
  10. done_bytes += copy_bytes;   
  11. rdwr_buf += copy_bytes;   
  12. Write_Sector(p_inode->i_dev,begin_sec + i);         /* 写回 */  
  13. p_caller->fd_ptr_table[fd]->fd_pos += copy_bytes;   
  14.   
  15. /* 没有要读的字节了就跳出 */  
  16. if(left_bytes <= 0)   
  17. {   
  18.     break;   
  19. }  

    三是MM进程的堆栈大小增大一倍到2048字节,否则出现莫名其妙的错误,估计是栈溢出了。。

    OK,最后在进程A添加测试语句:

    kernel/proc.c

Code:
  1. /*------------------------------------------------------------------------Proc_A  
  2.     进程A的执行体   
  3. */  
  4. void Proc_A()   
  5. {          
  6.     int pid = Fork();   
  7.     int ret_status;   
  8.     if(pid > 0)   
  9.     {   
  10.         while(1)   
  11.         {   
  12.             pid = Wait(&ret_status);   
  13.             if(pid != -1)   
  14.             {   
  15.                 Printf("child proc{%d} exit status is:%d",pid,ret_status);   
  16.             }   
  17.         }   
  18.     }   
  19.     else if(pid == 0)   
  20.     {   
  21.         Execl("/echo",2,"hello","world");   
  22.   
  23.     }   
  24. }  

    传入了两个参数给Echo:hello,world。

    make,bochs,运行结果如下:

    生平写了多个Hello World,但无疑这个最牛B。。

你可能感兴趣的:(Exec非完美版)