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上应用程序的执行机制的更多相关文章

  1. Linux 上 C 程序的内存布局

    在仔细研究这个问题之前,我认为 C 程序在内存中只有代码段,堆和栈三部分构成.前几天面试被问到了这个问题,才发现自己的印象是不完全的. 在本文中通过解析析一个 C 程序中变量和函数的地址来分析 C 程 ...

  2. Linux上java程序的jar包启动通用脚本(稳定用过)

    Linux上java程序的jar包启动通用脚本如下: #! /bin/sh export LANG="zh_CN.GBK" SERVICE_NAME=` .sh` SCRIPT_N ...

  3. Linux上使用程序相对路径访问文件【转】

    转自:http://blog.csdn.net/yinxusen/article/details/7444249 今天一个朋友问我这个问题,说为什么在Windows上跑得很好的应用程序,移植到Linu ...

  4. linux 如何让程序后台执行

    $ (./test.sh &)   $ setsid ./test.sh &   $ nohup ./test.sh &   具体的转自:http://digdeeply.or ...

  5. Linux下C程序插入执行shell脚本

    1.system(执行shell命令) 相关函数 fork,execve,waitpid,popen表头文件 #include<stdlib.h>定义函数 int system(const ...

  6. Linux上传文件与执行

    ls ——查看文件夹 mkdir——新建文件夹 cd:——进入文件 nohup python3 文件名.py & ——让代码在后台运行 ps -aux | grep 文件——查看进程 ps-a ...

  7. 分析Linux上的程序依赖

    ldd [path_to_exe] ldd通过调用动态链接器来获取可执行程序的依赖库,但是并不推荐在未知的可执行程序上执行业ldd来获取其依赖库,因为部分版本的ldd会直接通过调用该可执行程序来获取其 ...

  8. linux上安装程序出现的问题汇总

    1.程序在编译过程中出现:variable set but not used [-Werror=unused-but-set-variable] 解决方法:将configure文件和Makefile文 ...

  9. Linux上程序执行的入口--Main

    main()函数,想必大家都不陌生了,从刚开始写程序的时候,大家便开始写main(),我们都知道main是程序的入口.那main作为一个函数,又是谁调用的它,它是怎么被调用的,返回给谁,返回的又是什么 ...

随机推荐

  1. mongodb的常用操作(三)

    继续mongodb的学习和总结: 11.mongodb的mapreduce功能 mapreduce可以说是mongodb的一个很强大的功能,可以实现复杂的运算和统计,做一个简要的总结: 假设有user ...

  2. mac OS X下安装Redis及Thinkphp3.1使用Redis

    一.安装Redis 1.安装Homebrew 在终端输入ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/install/maste ...

  3. 防火墙设置:虚拟机ping不通主机,但是主机可以ping通虚拟机(转载)

    我在Windows7系统安装了虚拟机,通过虚拟机安装了Ubuntu13.04,我设置的主机与虚拟机的连接方式是桥接,安装好后,发现虚拟机ping不通主机,但是主机可以ping通虚拟机. 我的操作是:关 ...

  4. vs2010中安装ASP.NET AJAX Control Toolkit

    方法一: 第一步 下载Ajax Control Toolkit 进入网址http://ajaxcontroltoolkit.codeplex.com/ 即可下载 第二步 解压下载下来的Ajax Con ...

  5. Python开发【第一篇】Python基础之正则表达式补充

    正则表达式 一简介:就其本质而言,正则表达式(或RE)是一种小型的.高度专业化的标称语言,(在Python中)它内嵌在Python中,并通过re模块实现.正则表达式模式被编译成一系列的字节码,然后由用 ...

  6. openerp学习笔记 domain 增加扩展支持,例如支持 <field name="domain">[('type','=','get_user_ht_type()')]</field>

    示例代码1,ir_action_window.read : # -*- coding: utf-8 -*-from openerp.osv import fields,osv class res_us ...

  7. Mysql数据库中的计数器表实时更新

    如果某个应用中存在计数器,例如网站的总访问量.用户的粉丝数.文件下载数等等.如果相关应用在Mysql数据库的表中保存计数器,在更新计数器的时候可能会碰到并发问题.例如在web应用中,记录网站的点击次数 ...

  8. cppcheck使用

    一.splint介绍 splint是一个动态检查C语言程序安全弱点和编写错误的程序.splint会进行多种常规检查,包括未使用的变量,类型不一致,使用未定义变量,无法执行的代码,忽略返回值,执行路径未 ...

  9. ios按钮点击后翻转效果

    代码是网上找到的,不过找到的时候直接复制下来不能用,稍微整理下,为和我一样水平的菜鸟观摩一下下. (1)引入“QuartzCore.framework”库,头部引用. #include<Quar ...

  10. C# Windows - SDI和MDI应用程序

    生成MDI应用程序 MDI应用程序至少要由两个截然不同的窗口组成.第一个窗口叫做MDI容器(Container),可以在容器中显示的窗口叫做MDI子窗口. 要把应用程序的主窗口从一个窗体改为MDI容器 ...