linux上应用程序的执行机制
执行文件是如何在shell中被"执行"的。本文中尽可能少用一些源码,免得太过于无
聊,主要讲清这个过程,感兴趣的同学可以去查看相应的源码了解更多的信息。
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()是操作系统提供的非常重要的一个系统调用,在很多文章中被称为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()函数开始
加载可执行文件。
建立可知性文件列表struct linux_binfmt,遍历执行
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()就成功了。)
简短的说,整个在shell中键入./test执行应用程序的过程为:当前shell进程fork出一个
子进程(子shell),子进程使用execve来脱离和父进程的关系,加载test文件(ELF格式)到
内存中。如果test使用了动态链接库,就需要加载动态链接器(或者叫程序解释器),
进一步加载test使用到的动态链接库到内存,并重定位以供test调用。最后从test的入
口地址开始执行test。
PS: 现代的动态链接器因为性能等原因都采用了延迟加载和延迟解析技术,延迟加
载是动态连接库在需要的时候才被加载到内存空间中(通过页面异常机制),延迟解
析是指到动态链接库(以加载)中的函数被调用的时候,才会去把这个函数的起始地
址解析出来,供调用者使用。动态链接器的实现相当的复杂,为了性能等原因,对
堆栈的直接操作被大量使用,感兴趣的可以找相关的代码看看。
通俗篇:
linux下,当你使用./xxx运行一个程序时,首先是SHELL来接管你的输入,然后用fork
派生子进程,最后用execv系列将你的那个程序的代码交给内核
1。检查你运行的文件的属性,其属性在它的I节点中描述,如果你的那个文件不是
可执行的属性,结果就会拒绝执行,如果有可执行的属性,但可执行的权限高于你
目前正在使用的用户的权限,拒绝执行
2。检查是SHELL文件吗?如果是,调用相应的SHELL来解析你的这个脚本文件
3。是ELF文件格式吗??是coff文件格式吗?是a.out文件格式吗?如果是其中任何
一种,并且当前的LINUX内核都支持这三种文件格式,那么就由操作系统内核分析
你的文件格式,去掉文件头信息,将真正的代码,数据等加载进内存(实际过程并
不是这样的,只不过为了描述简单,所以省略了很多细节,更多详细说明,请参见
内核中的execv系统调用)...
4.等待系统的进程调度,当内核选中你的那个程序的时候,你的那个程序就得到运
行了
LINUX下的文件扩展名是形同虚设的,只是一种习惯,为了给用户更好的理解其作
用,比如配置文件一般都以.conf结尾,“文本文件”一般都以.txt结尾(主要是为了跟
WINDOWS用户习惯相接近),ELF文件不用扩展名,所以当你说可执行文件的时候
千万不要说是exe文件,那是很不严格的说法,只说明你仅是一个WINDOWS程序员
而已
一个奔跑的程序员
linux上应用程序的执行机制相关推荐
- linux上开发应用程序_如何在Linux上安装软件应用程序
linux上开发应用程序 如何在Linux上安装应用程序? 与许多操作系统一样,该问题不仅有一个答案. 应用程序可以来自许多来源-几乎无法计数-每个开发团队都可以以自己认为最佳的方式交付软件. 知道如 ...
- 一款分析linux上应用程序分析的工具----0x.tools
0x.tools是一个用于分析Linux上应用程序性能的开源实用程序.部署简单且依赖性小.无需升级操作系统.安装内核模块.繁重的监控框架.Java代理或数据库.这款工具还适用于十多年前的Linux内核 ...
- c#如何在linux上运行程序,在Linux上运行C#
众所周知,C#是Microsoft推出的.NET语言,只能在.NET平台上运行,例如Win 9x.ME.NT.2000.XP和Win CE之类的操作系统.但是,现在却有了一个叫做Mono的项目,它的目 ...
- java跑到linux上,Java程序在Linux上运行虚拟内存耗用很大
突然集群的2个节点挂了,通过top查看, 虚拟内存22G, 通过 pmap -x 8 | grep anon 一大堆64M Linux下glibc的内存管理机制用了一个很奇妙的东西,叫arena.在g ...
- Linux上使用at单一执行例行性工作,使用crontab循环执行例行性任务以及系统例行任务
在工作或学习中,我们经常需要对重要的文件进行备份,或者做一些日常性的排查等工作,了解如何在linux上执行例行性工作显得尤为重要! 本文基于Linux上CentOS 7版本进行演示 一.单一执行例行性 ...
- linux上原生程序运行QQ,微信,百度网盘,王者荣耀,cf
在 Linux 上运行王者荣耀游戏的截图: 运行微信的截图: 与现有的各种Android模拟器不同,xDroid没有使用虚拟机,而是使Android应用以Linux原生程序的形式运行,性能媲美标准的L ...
- Linux下C程序插入执行shell脚本
1.system(执行shell命令) 相关函数 fork,execve,waitpid,popen 表头文件 #include<stdlib.h> 定义函数 int system(con ...
- linux 如何让程序后台执行
$ (./test.sh &) $ setsid ./test.sh & $ nohup ./test.sh & 具体的转自:http://digdeeply.org/arch ...
- Linux上跑程序加大内存
一个shell 命令 加内存方法 Java -Xms768m -Xmx768m -XX:PermSize=128m -classpath .:$lib........................ ...
- Linux上的程序是通用的吗,运行兼容 Linux Standard Base (LSB)程序的通用解决办法
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 再次运行看看 $lmgrd 1:51:32 (lmgrd) ----------------------------------------------- ...
最新文章
- 1024块TPU在燃烧!BERT训练从3天缩短到76分钟 | 技术头条
- JAVA中float和double的区别、String和char的区别
- apple music有一点坏处。。这个乱码有点不本土化啊
- PostCSS自学笔记(二)【番外篇二】
- php js下拉框与文本联动,php mysql js 下拉框 二级联动
- 4款深度学习框架简介,初学者该如何选择?
- Android开发笔记(九十五)自定义Drawable
- 超简单!Vue 短信验证码组件开发详解
- 分享《通过开源项目去学习》
- python 接口自动化测试王浩然 pdf_Python接口自动化测试
- 数字图像处理(8):实现FFT快速算法(C语言)
- CES 2020最佳科技产品官方入围名单!三星笑了
- 《沈剑架构师训练营》第3章 - 快速性能优化
- 计算机教师个人业绩成果自述,申报中学信息技术高级教师职称晋升个人业绩成果自述.docx...
- python pandas拆分单元格
- 全球5G手机最新排名!
- 2022-2027年中国西服行业市场调研及未来发展趋势预测报告
- 数学分析 曲面积分与场论初步(第22章)
- 手机拍照光线不够白平衡自动改变_白平衡:一个被低估的参数,掌握好它,提升摄影中的色彩管理水平...
- 山西农信社计算机知识,山西农村信用社考试:计算机知识知识点(二)