首页 > 代码库 > Linux内核分析第七周作业

Linux内核分析第七周作业

Linux内核如何装载和启动一个可执行程序

有了上次的教训,这次直接用vmware完成 (~ ̄3 ̄)~

技术分享

先观察MenuOS新增的函数

 1 int Exec(int argc, char *argv[])
 2 {
 3     int pid;
 4     /* fork another process */
 5     pid = fork();
 6     if (pid < 0) 
 7     { 
 8         /* error occurred */
 9         fprintf(stderr,"Fork Failed!");
10         exit(-1);
11     } 
12     else if (pid == 0) 
13     {
14         /*     child process     */
15         printf("This is Child Process!\n");
16         execlp("/hello","hello",NULL);
17     } 
18     else 
19     {     
20         /*     parent process     */
21         printf("This is Parent Process!\n");
22         /* parent will wait for the child to complete*/
23         wait(NULL);
24         printf("Child Complete!\n");
25     }
26 }

和上次的Fork差不多,只不过在子进程的分支中调用了execlp。

这里要提一下exec大家族,一共有6个函数

(1)int execl(const char *path, const char *arg, ......);

(2)int execle(const char *path, const char *arg, ...... , char * const envp[]);

(3)int execv(const char *path, char *const argv[]);

(4)int execve(const char *filename, char *const argv[], char *const envp[]);

(5)int execvp(const char *file, char * const argv[]);

(6)int execlp(const char *file, const char *arg, ......);

只有4-execve是本体,其他都是execve的封装。函数的区别从函数名就能看出来:

带p的5和6的第一个参数是文件名,而不带p的1-4是路径名。5和6会在PATH系统变量中搜索输入的文件名

带e的2和4的最后一个参数是环境变量字符串的数组。环境变量字符串的形式是“MYENV=123”的键值对。而不带e的函数将把当前进程的环境变量保留给新程序。

最后是带v的345和带l的126的区别。 v就是vector,即参数是通过字符串数组传递。l指的是list,参数直接通过不定长的参数列表传递,最后要加一个NULL表示结束。

 

这次使用的execlp就是仅指定了文件名,参数直接通过函数参数列表传递,并且最后用NULL表示结束。

exec家族都是调用execve系统调用,具体是通过do_execve函数实现的。而do_execve也只是简单的封装一下输入的参数与环境变量,调用了do_execve_common函数。

do_execve_common函数也不长,除去一些基本的检查、后处理代码,核心是创建了一个linux_binprm结构体

技术分享

简单填充了一些如文件名、文件句柄、参数、环境变量以及它们的个数等等信息,

接下来调用exec_binprm,使用前面准备的bprm来启动程序。

因为linux支持多种可执行文件格式(elf/a.out/coff等), 所以内核首先要通过search_binary_handler找到用于加载bprm中指定的程序的加载器

所有的加载器都串联在formats链表上,因此可以用list_for_each_entry遍历这个链表,逐个尝试解析可执行程序。

 技术分享

如果某个解析器无法解析这个程序,就返回ENOEXEC。

通过 objdump -f hello 可以知道hello是一个elf格式的可执行程序

 技术分享

所以是由load_elf_binary函数装载这个程序。在该函数设置断点,断点确实触发。

技术分享

 

load_elf_binary比较长。前面基本上是各种检查,然后解析elf文件头

技术分享

 

调用参数以及系统变量也是在create_elf_tables函数中设置到栈上

技术分享

 

再处理动态链接的段

技术分享

 

接下来把可执行程序的各个段映射到内存中

技术分享

 

最后设置了执行的起点。如果需要动态链接,则起点交给动态库的加载器。否则直接由elf文件的执行入口位置开始。

 技术分享

这个起始地址最后通过start_thread函数保存到用户态的EIP寄存器中,一起保存的还有用户堆栈

技术分享

当进程从execve系统调用返回的时候,会把用户态的寄存器恢复,就自动从指定的地址开始运行。并且能从堆栈中取出参数与系统变量。

 

 

总结

Linux装载可执行程序的过程基本就是按照特定的格式,将程序放入虚拟地址空间。最后利用系统调用的机制,巧妙地在内核态设置了返回用户态的入口,使得新加载的程序开始运行。

 

王岩

原创作品转载请注明出处

《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

Linux内核分析第七周作业