• 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!
    ");
    16         execlp("/hello","hello",NULL);
    17     } 
    18     else 
    19     {     
    20         /*     parent process     */
    21         printf("This is Parent Process!
    ");
    22         /* parent will wait for the child to complete*/
    23         wait(NULL);
    24         printf("Child Complete!
    ");
    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装载可执行程序的过程基本就是按照特定的格式,将程序放入虚拟地址空间。最后利用系统调用的机制,巧妙地在内核态设置了返回用户态的入口,使得新加载的程序开始运行。

  • 相关阅读:
    Maven插件之portable-config-maven-plugin(不同环境打包)
    redis
    MySQL之group_concat 配合substring_index查询
    Jmeter执行测试计划同时监听服务器性能PerfMon Metrics Collector
    【转】证书和编码
    [转]SSL/TLS协议运行机制的概述
    OC—MVC框架图解
    安卓intent
    day8---多线程socket 编程,tcp粘包处理
    day7---socket
  • 原文地址:https://www.cnblogs.com/cscat/p/6660015.html
Copyright © 2020-2023  润新知