全部博文(436)
分类: LINUX
2013-03-11 23:15:21
第二十章 程序的执行
本章集中讨论程序(program)和进程(process)之间的关系,即内核如何通过程序文件建立进程的执行上下文。我觉得阅读本章的难点之一,就是分清哪些概念是属于程序的,哪些概念是属于进程的。比如,属于程序的概念有:程序文件、可执行文件、程序的段;属于进程的概念有:进程的可执行上下文、进程的信任状、进程的权能、进程的线性区。
程序是以可执行文件的形式存在硬盘上的,可执行文件包括被执行函数的目标代码也包括这些函数所使用的数据。
影响程序执行方式的两种信息:
1.命令行参数:用户在shell提示符下紧跟文件名输入的
2.环境变量:从shell继承而来
可执行文件
问:什么是可执行文件?
答:可执行文件既包括被执行函数的目标代码,也包括这些函数所使用的数据。它描述了如何初始化一个新的可执行上下文。
举例:
实现功能:在当前目录下显示文件
用户做法:敲入外部命令/bin/ls
实现过程:
新进程开始→1.调用系统调用execve(),其中/bin/ls作为一个参数被传递进去
→2.sys_execve()服务例程修改当前进程的执行上下文
→3.以上系统调用终止后,新进程开始执行放在可执行文件中的代码,也就是执行在当前目录下显示文件的功能
总结:在当前目录下显示文件:对于用户来说,在shell提示符下简单的敲入外部命令——/bin/ls,可得到结果。对于系统内部来说,shell创建了一个新进程,新进程又调用execve(),其中传递的第一个参数就是ls可执行的文件的全部路径名,服务器找到相应的文件,检查可执行格式,根据存放其中的信息修改当前进程的执行上下文。
疑惑:不太理解服务例程(service routines)这个概念,它和一般的程序或者函数区别在哪里?
现在我们关注上面提到的第2点,即执行上下文发生了什么样的变化。这种变化重点包括两个,一个是进程的特权,还有一个是使用新的命令行参数和新的shell环境。
进程的信任状
问:为什么要设立进程的信任状?
答:信任状把进程与一个特定的用户或用户组捆绑在一起,决定了每个进程能做什么,不能做什么。
问:进程的信任状的优点?
答:保证了每个用户个人数据的完整性,也保证了系统整体上的稳定性。同时也可以用进程的信任状来检查与文件无关的操作。
进程的信任状存放在进程描述符中,文件的拥有者的标识符存放在所访问文件索引节点中。我们必须比较两者才可以决定进程对文件的访问是否合法。
进程描述符中的字段有下表中的5组:
问:那么进程的信任状是怎么发生改变的?
答:进程被创建→继承父进程的信任状→执行setuid程序,或发出其他合适的系统调用→euid=fsuid=这个文件拥有者的标识符→内核执行访问控制表时决定允许进程做事。
特别地,只要euid=fsuid=0(即进程拥有root超级用户特权),则一定表示允许进程做事。
问:还有哪些合适的系统调用可以改变进程的信任状标识符?
答:
括号里的字母是这些系统调用所需的参数。
进程的权能
问:什么是进程的权能?
答:进程的权能(capability)是进程信任状的另一种模型。它是一个标志,表明是否允许进程执行一个特定的操作或一组特定的操作。
问:什么是进程的权能相比较于进程的信任状有什么优点?
答:任何时候每个进程只需要有限种权能。
Linux安全模块框架
在Linux2.6中,权能是与Linux安全模块(LSM)框架紧密结合的。每个安全模型是由一组安全钩(security hook)实现的。安全钩是由内核调用的一个函数,用于执行与安全有关的操作。
命令行参数和新的shell环境
传递命令行参数的约定依赖于所用的高级语言。下面以C为例介绍如何传递命令行参数。在C语言中,程序的main()函数把传递给程序的参数个数和指向字符串指针数组的地址作为参数。
代码格式:int mian(int argc, char *argc[])
其中,int argc=参数个数
为了使用环境变量,则代码格式为:int mian(int argc, char *argc[], char *envp[])
其中,envp参数指向环境串的指针数组。
命令行参数和环境串都存放在用户态堆栈中,正好位于返回地之前。具体如图:
库
程序中的很多函数是所有程序员都可使用的服务例程,它们的目标代码存在于“库”中。
典型的有C库、数学库libm、X11库。任何可执行文件除了包括对程序的语句进行编译所直接产生的代码外,还包括一些“粘合”代码来处理用户态进程与内核之间的交互,这样的粘合代码有一部分存放在C库中。
一般的Linux系统可能轻而易举的就有50个不同的库,如数学库libm,包含浮点操作的基本函数;X11收集了所有X11窗口系统图形接口的基本底层函数。
库函数中的代码:
1.静态地拷贝到可执行文件中运行(静态库)
缺点:占用大量的磁盘空间
利用动态链接器(ld.so)指向库名。
2.在程序运行时,被链接(或者说映射)到进程(共享库)
缺点:动态链接的程序启动时间长
直接复制库代码的某些部分
程序段和进程的线性区
程序的线性地址空间被划分为几个段:
正文段:包含可执行代码
数据段:包含初始化的数据
Bss段:包含未初始化的数据
堆栈段:包含程序的堆栈,堆栈中有返回地址,参数和被执行函数的局部变量
相应地,每个mm_struct内存描述符都包含一些字段来标识相应的进程特定线性区的作用:
Start_code, end_code
Start_data, end_data
Start_brk, brk
Start_stack
Arg_start, arg_end
Env_start, env_end
疑惑:不太理解这句话:“共享库和文件的内存映射使得基于程序段的进程地址空间分类有点过时,因为每个共享库被映射到与前面所讨论的线性区不同的线性区”
若每个进程是按照用户态堆栈预期的增长量来进行内存布局的,那么使用灵活线性布局,若内核无法确定用户态堆栈的上限,就仍然使用经典线性布局。
看一个例子:
若经典线性布局:
若灵活线性布局:
可执行格式
主要可执行格式:ELF
三种方法:load_binary、Load_shlib、Core_dump
自定义格式:
:name:type:offset:string:mask:interpreter:flags
name新格式的标识符
type识别类型
offset魔数在文件中的起始偏移量
string以魔数或者以扩展名匹配的字节序列
mask用来屏蔽掉string中的一些位的字符串
interpreter解释程序完整路径名
flags可选标志,控制必须怎样调用解释程序
exec函数
Unix系统提供了一个函数家族,这样的函数名以前缀exec开始。后跟一个或两个字母。
每个函数的第一个参数表示被执行文件的路径名,路径名可以是绝对路径或是当前进程目录的相对路径,此外如果路径名不包括“/”,execlp(),execvp()在path环境变量指定的目录中搜索这个可执行文件。
所有的exec()类函数,除execve()外都是C库定义的封装历程,并利用了execve()系统调用,这是linux所提供的处理程序执行的唯一系统调用。