--------------------------------------------------------
exec
--------------------------------------------------------
exec 和 fork 一样巧妙 ...
这一节主要涉及对子进程运行空间的操作!
·do_exec 函数
PUBLIC int do_exec()
{
/* get parameters from the message */
int name_len = mm_msg.NAME_LEN; /* length of filename */
int src = mm_msg.source; /* caller proc nr. */
assert(name_len < MAX_PATH);
char pathname[MAX_PATH];
phys_copy((void *)va2la(TASK_MM, pathname),
(void *)va2la(src, mm_msg.PATHNAME),
name_len);
pathname[name_len] = 0; /* terminate the string */
/* get the file size */
struct stat s;
int ret = stat(pathname, &s); /* 向文件系统请求文件的信息 */
if (ret != 0)
{
printl("{MM} MM::do_exec()::stat() returns error. %s", pathname);
return -1;
}
/* read the file */
int fd = open(pathname, O_RDWR);
if (fd == -1)
return -1;
assert(s.st_size < MMBUF_SIZE);
read(fd, mmbuf, s.st_size); /* 将可执行文件读取到 MM 的 mmbuf */
close(fd);
printl("mm_msg.BUF : [%d]\n\n", mm_msg.BUF);
// 将参数堆栈暂时拷贝到 MM 堆栈
int orig_stack_len = mm_msg.BUF_LEN;
char stackcopy[PROC_ORIGIN_STACK];
phys_copy((void *)va2la(TASK_MM, stackcopy),
(void *)va2la(src, mm_msg.BUF),
orig_stack_len);
// 0x10000 - 0x400 == FC00
u8 *orig_stack = (u8 *)(PROC_IMAGE_SIZE_DEFAULT - PROC_ORIGIN_STACK);
int delta = (int)orig_stack - (int)mm_msg.BUF;
int argc = 0;
if (orig_stack_len)
{ /* has args */
char **q = (char **)stackcopy;
for (; *q != 0; q++, argc++)
*q += delta;
}
phys_copy((void *)va2la(src, orig_stack),
(void *)va2la(TASK_MM, stackcopy),
orig_stack_len);
// 等不适用旧的空间了再覆盖旧的空间
Elf32_Ehdr *elf_hdr = (Elf32_Ehdr *)(mmbuf);
int i;
for (i = 0; i < elf_hdr->e_phnum; i++)
{
Elf32_Phdr *prog_hdr = (Elf32_Phdr *)(mmbuf + elf_hdr->e_phoff +
(i * elf_hdr->e_phentsize));
if (prog_hdr->p_type == PT_LOAD)
{
assert(prog_hdr->p_vaddr + prog_hdr->p_memsz <
PROC_IMAGE_SIZE_DEFAULT);
printl("\nelf_hdr->e_entry : [%d]\n", elf_hdr->e_entry);
printl("elf_hdr->e_phnum : [%d]\n", elf_hdr->e_phnum);
printl("prog_hdr->p_vaddr : [%d]\n", prog_hdr->p_vaddr);
printl("prog_hdr->p_memsz : [%d]\n", prog_hdr->p_memsz);
printl("prog_hdr->p_filesz : [%d]\n\n", prog_hdr->p_filesz);
phys_copy((void *)va2la(src, (void *)prog_hdr->p_vaddr), /* 段的第一个字节在内存中的偏移 */
(void *)va2la(TASK_MM,
mmbuf + prog_hdr->p_offset), /* 段的第一个字节在文件中的偏移 */
prog_hdr->p_filesz);
}
}
proc_table[src].regs.ecx = argc; /* argc */
proc_table[src].regs.eax = (u32)orig_stack; /* argv */
/* setup eip & esp */
proc_table[src].regs.eip = elf_hdr->e_entry; /* 从头开始执行内核代码,但此时内核代码已经被替换为 echo 的代码 */
proc_table[src].regs.esp = PROC_IMAGE_SIZE_DEFAULT - PROC_ORIGIN_STACK;
strcpy(proc_table[src].name, pathname);
return 0;
}
——首先子进程被创建之后,就调用 exec,exec 的核心作用就是:先跑去 MM(内核),然后等 MM(内核)将它子进程的执行空间全部替换之后,再跑回来,此时它将不再是一个复制品,而是一段可执行程序(新的),这段可执行程序执行完了以后就会退出(从进程表数组中退下来),以后调度的时候将不考虑这个进程表,而且这个进程表可以被新的进程使用!
——第二点需要注意的是,我们在替换子进程执行空间之前,需要使用子进程执行空间的一些内容,所以要注意将这些内容使用完了之后再替换子进程内存,否则 ...(所以程序中我将覆盖移动到了最后)!
——第三点就是执行覆盖之后子进程的 1M 内存布局是怎样的:
根据我们的输出,我们可以大概画出 1M 的空间:
图中黄色的就是主干程序了,绿色的就是堆栈了,而灰色的暂时没有被使用!
——第四点就是命令行的参数如何组织、移动、传递:
这里是很关键的,一共移动了三次我们传递给 execl 的栈参数,首先我们先调用 execl 形成栈(这个栈的样子就是三个指针指向三个字符换,还有一个 0 ,这是 C 语言压栈字符串的方式),然后调用 ececv ,在 execv 中构造了一个临时栈数组,并将 execl 中的栈复制过来,接着 execl 请求 MM 的 do_exec 服务,do_exec 在自己的内部又构造了一个临时栈数组,将 execv 中的临时堆栈复制过来,并作了字符串指针的重定位(因为字符串要被移动了,所以指向字符串的指针也要做相应的修改),最后再将 MM 中的临时堆栈数组复制到子进程的堆栈区域!
可见真的是一波三折,其实直接在 execv(或者 execl )中进行重定位并进行移动是可以的 ...
最后 _start 将字符串指针数组的首地址给了 main 函数的 argv 参数,使得可以遍历字符串数组!
——第五点就是我们整体来看看从 【fork】 ——》【exec】——》【exit】 的流程:
- fork:复制运行空间、开辟新进程
- exec:重新安置参数、替换空间
- exit:唤醒父进程、回收进程表
运行
欧克,对子进程的替换操作看来是没有问题的 ...