冬天OS(三十六):执行应用程序

--------------------------------------------------------

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:唤醒父进程、回收进程表
     

运行
 

欧克,对子进程的替换操作看来是没有问题的 ...

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柔弱胜刚强.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值