加入收藏 | 设为首页 | 会员中心 | 我要投稿 常州站长网 (https://www.0519zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 站长资讯 > 评论 > 正文

是如何运行起来的

发布时间:2021-04-06 16:06:17 所属栏目:评论 来源:互联网
导读:学都会有疑问,一个程序是如何运行起来的,为什么我们在shell中执行了一个程序,它的main函数就会被调用呢?在main函数被调用之前及之后,又经历了什么呢? 今天我们就来详细的说下这个问题。 还是和之前一样,我画了一张程序运行的全景图,在上图中,一个程序


学都会有疑问,一个程序是如何运行起来的,为什么我们在shell中执行了一个程序,它的main函数就会被调用呢?在main函数被调用之前及之后,又经历了什么呢?

今天我们就来详细的说下这个问题。

还是和之前一样,我画了一张程序运行的全景图,在上图中,一个程序运行所经历的代码段,我都标注了其所在的git仓库、源文件、及函数名,想要自己看源码的,可以参考下上图中的这些信息。

我们先从整体上讲一下这张图。

在linux下,我们一般都是通过shell来执行程序的。

shell其实也是一个普通的程序,它也有自己的main函数,它在正常运行后,会通过调用read_command函数,来等待用户输入命令。

在接收到用户输入的命令后,shell会先使用fork系统调用,创建一个子进程,然后再在这个子进程中,通过execve系统调用,执行最终的用户程序。

在子进程执行用户程序期间,shell主进程会调用waitpid函数,阻塞等待子进程的完成,子进程完成之后,waitpid从阻塞状态中返回,且status参数中会带着子进程的退出码,这个退出码会在后续的逻辑中被保存起来,供用户查询。

之后,shell主进程进入到下一次循环,继续等待用户输入命令并执行。

以上就是shell的主体逻辑,对应于上面全景图中的蓝色部分。

下面我们再来看下linux内核中有关execve系统调用的代码,也就是上面全景图中的绿色部分。

shell通过execve系统调用,告知linux内核,要在当前进程中执行目标程序,linux内核经过层层代码,最终到达load_elf_binary函数。

该函数是整个系统调用中最核心的一段逻辑,它主要用来为目标程序准备各种执行环境。

比如,映射代码区、数据区等到当前进程的虚拟地址空间,将程序名、环境变量、程序参数、及各种其他数据,有规律的压入到新分配的栈中,等等。

之后,load_elf_binary函数会调用start_thread,进而会调用start_thread_common函数。

在该函数里,会将返回到用户区之后,要执行的,用户区程序的起始地址,设置到regs->ip里,同时也会将上面新初始化好的,用户堆栈的栈顶地址,设置到regs->sp里。

当execve系统调用返回到用户区之后,regs->ip和regs->sp里的值,会分别赋值到rip和rsp寄存器里,这样指定的用户程序就可以继续执行了。

这一流程我们在之前的文章 精致全景图 | 系统调用是如何实现的 中讲过,这里就不再赘述。

不过这里还是有一点需要注意,就是设置到regs->ip中的地址,并不是我们自己程序的起始地址,而是动态链接器 /lib64/ld-linux-x86-64.so.2 的起始地址。

之所以要设置动态链接器的起始地址,是因为我们需要在返回到用户区之后,让其可以继续为我们的程序准备执行环境,比如,帮忙加载程序依赖的各种动态链接库等。

在动态链接器为我们的程序准备好执行环境之后,它会从进程堆栈的auxiliary vector区,取出最终用户程序的真正起始地址,并跳转到该位置开始执行

(编辑:常州站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    热点阅读