Linux应用程序加载机制是什么

50次阅读
没有评论

共计 2772 个字符,预计需要花费 7 分钟才能阅读完成。

这篇文章主要介绍“Linux 应用程序加载机制是什么”的相关知识,丸趣 TV 小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Linux 应用程序加载机制是什么”文章能帮助大家解决问题。

1. 父进程的行为: 复制,等待

执行应用程序的方式有很多,从 shell 中执行是一种常见的情况。交互式 shell 是一个进程 (所有的进程都由 pid 号为 1 的 init 进程 fork 得到,关于这个话题涉及到 Linux 启动和初始化,以及 idle 进程等,我们找一期文章讲掉),当用户在 shell 中敲入./test 执行程序时,shell 先 fork() 出一个子进程 (这也是很多文章中说的子 shell), 并且 wait() 这个子进程结束,所以当 test 执行结束后,又回到了 shell 等待用户输入(如果创建的是所谓的后台进程,shell 则不会等待子进程结束,而直接继续往下执行)。所以 shell 进程的主要工作是复制一个新的进程,并等待它的结束。

2. 子进程的行为: 执行 应用程序

2.1 execve()

另一方面, 在子进程中会调用 execve()加载 test 并开始执行。这是 test 被执行的关键,下面我们详细分析一下。execve()是什么呢?

execve()是操作系统提供的非常重要的一个系统调用,在很多文章中被称为 exec()系统调用 (注意和 shell 内部 exec 命令不一样),其实在 Linux 中并没有 exec() 这个系统调用,exec 只是用来描述一组函数,它们都以 exec 开头,分别是:

#include int execl(const char *path, const char *arg, ...); int execlp(const char *file, const char *arg, ...); int execle(const char *path, const char *arg, ..., char *const envp[]); int execv(const char *path, char *const argv[]); int execvp(const char *file, char *const argv[]); int execve(const char *path, char *const argv[], char *const envp[]);

这几个都是都是 libc 中经过包装的的库函数,*** 通过系统调用 execve()实现(#define __NR_evecve 11,编号 11 的系统调用)。

exec 函数的作用是在当前进程里执行可执行文件,也就是根据指定的文件名找到可执行文件,用它来取代当前进程的内容,并且这个取代是不可逆的,即被替换掉的内容不再保存,当可执行文件结束,整个进程也随之僵死。因为当前进程的代码段,数据段和堆栈等都已经被新的内容取代,所以 exec 函数族的函数执行成功后不会返回,失败是返回 -1。可执行文件既可以是二进制文件,也可以是可执行的脚本文件,两者在加载时略有差别,这里主要分析二进制文件的运行。

2.2 do_execve()

在用户态下调用 execve(),引发系统中断后,在内核态执行的相应函数是 do_sys_execve(),而 do_sys_execve()会调用 do_execve()函数。do_execve()首先会读入可执行文件,如果可执行文件不存在,会报错。然后对可执行文件的权限进行检查。

如果文件不是当前用户是可执行的,则 execve()会返回 -1, 报 permission denied 的错误。否则继续读入运行可执行文件时所需的信息(见 struct linux_binprm)。

Execve()- do_sys_execve()- do_execve()(check if file exist and if can be runed by current user)

2.3 search_binary_handler()

接着系统调用 search_binary_handler(),根据可执行文件的类型(如 shell,a.out,ELF 等),查找到相应的处理函数(系统为每种文件类型创建了一个 struct linux_binfmt,并把其串在一个链表上,执行时遍历这个链表,找到相应类型的结构。如果要自己定义一种可

执行文件格式,也需要实现这么一个 handler)。然后执行相应的 load_binary()函数开始加载可执行文件。

2.4 load_elf_binary()

加载 elf 类型文件的 handler 是 load_elf_binary(),它先读入 ELF 文件的头部,根据 ELF 文件的头部信息读入各种数据 (header information)。再次扫描程序段描述表, 找到类型为 PT_LOAD 的段, 将其映射(elf_map()) 到内存的固定地址上。如果没有动态链接器的描述段, 把返回的入口地址设置成应用程序入口。完成这个功能的是 start_thread(),start_thread()并不启动一个线程,而只是用来修改了 pt_regs 中保存的 PC 等寄存器的值,使其指向加载的应用程序的入口。这样当内核操作结束,返回用户态的时候,接下来执行的就是应用程序了。

2.5 load_elf_interp()

如果应用程序中使用了动态链接库,就没有那么简单了,内核除了加载指定的可执行文件,还要把控制权交给动态连接器 (program interpreter,ld.so in linux) 以处理动态链接的程序。内核搜寻段表,找到标记为 PT_INTERP 的段中所对应的动态连接器的名称,并使用 load_elf_interp()加载其映像,并把返回的入口地址设置成 load_elf_interp()的返回值,即动态链接器入口。当 execve 退出的时候动态链接器接着运行。动态连接器检查应用程序对共享连接库的依赖性,并在需要时对其进行加载, 对程序的外部引用进行重定位。然后动态连接器把控制权交给应用程序,从 ELF 文件(一个文件格式,我们抽一期单独讲解下)头部中定义的程序进入点开始执行。(比如 test.c 中使用了 userlib.so 中函数 foo(), 在编译的时候这个信息被放进了 test 这个 ELF 文件中,相应的语句也变成了 call fakefoo()。当加载 test 的时候,知道 foo()是一个外部调用, 于是求助于动态链接器,加载 userlib.so, 解析 foo()函数地址,然后让 fakefoo()重定向到 foo(), 这样 call foo()就成功了。)

关于“Linux 应用程序加载机制是什么”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识,可以关注丸趣 TV 行业资讯频道,丸趣 TV 小编每天都会为大家更新不同的知识点。

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-08-04发表,共计2772字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)