此篇文章就源于一个问题:hello world 到底发生了什么?
很明显,首先需要阅读的就是第20章,程序的执行
可执行文件
进程被定义为“执行上下文”,意味着特定的计算需要收集必要的信息,包括所访问的页,打开的文件,硬件寄存器的内容。
可执行文件是一个普通文件,它描述了如何初始化一个新的执行上下文,即如何开始一个新的计算。
进程开始执行一个新程序时,其执行上下文变化较大,因为进程的前一个计算执行期间所获得的大部分资源会被抛弃。
但进程的 PID 不改变,并且新的计算从前一个计算继承所有打开的文件描述符。
-
Linux 让进程只有在必要时才获得 setuid 特权,并在不需要时取消它们。
-
在 Linux 2.6 中,权能是与 Linux 安全模块(LSM)框架紧密结合在一起的。
-
具体可执行文件的格式:见 CSAPP 链接的那一章
命令行和shell环境
在 C 语言中,传递给 main() 的第三个可选参数是包含环境变量的参数。
环境变量用来定制进程的执行上下文,由此为用户或其它进程提供通用的信息,或者允许进程在执行 execve() 的过程中保持一些信息。
//输出该用户的所有环境变量。
#include
int main ( int argc, char *argv[], char *envp[])
{
while (*envp)
{
printf("%s\n", *envp);
envp++;
}
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
命令行参数和环境串都存放在用户态堆栈中,正好位于返回地址之前。
环境变量位于栈底附近正好在一个 0 长整数之后。
库
每个高级语言的源码文件都是经过几个步骤才转化为目标文件(这里就是.o文件,是经过汇编器之后形成的文件)的,目标文件中包含的是汇编语言指令的机器代码,它们和相应的高级语言指令对应。
目标文件并不能被执行,因为它不包含源代码文件所用的全局外部符号名的线性地址。
这些地址的分配或解析是由链接程序完成的,链接程序把程序所有的目标文件收集起来并构造可执行文件。
链接程序还分析程序所用的库函数,并把它们粘合成可执行文件。
以上链接的具体过程见:http://iyenn.com/rec/2141761.html 或者CSAPP
链接程序所产生的可执行文件不仅包括源程序的代码,还包括程序所引用的库函数的代码。
静态库:
将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件(.out)中。因此对应的链接方式称为静态链接。
会造成可执行文件过大
动态/共享库:
可执行文件不再包含库的目标代码,而仅仅指向库名。
当程序被装入内存执行时,一个名为动态连接器的程序就专注于分析可执行文件中的库名,确定所需库在系统目录树中的位置,并使执行进程可使用所请求的代码。
进程也可以调用 dlopen() 库函数在运行时装入额外的共享库。
共享库对支持文件内存映射的系统尤为方便,因为它们减少了执行一个程序所需的主内存量。
当动态链接程序必须把某一共享库链接到进程时,并不拷贝目标代码,而仅仅执行一个内存映射,把库文件的相关部分映射到进程的地址空间中。这就允许共享库机器代码所在的页框被使用同一代码的所有进程共享。
显然,如果程序是静态链接的,那么共享是不可能的。
程序段和进程的线性区
从逻辑上说,Unix 程序的线性地址传统上被划分为几个叫做段的区间:
- 正文(text)段,包含程序的可执行代码。
- 已初始化数据段,包含已初始化的数据,也就是初值存放在可执行文件中的所有静态变量和全局变量。(程序在启动时必须知道他们的值)
- 未初始化数据段(bss 段CSAPP上解释为省磁盘空间
具体原因查看:http://iyenn.com/rec/2141762.html ,其实就是.bss不占据实际的磁盘空间,只在段表中记录大小,在符号表中记录符号。当文件加载运行时,才分配空间以及初始化
),包含未初始化的数据,也就是初值没有存放在任何可执行文件中的所有全局变量。 - 堆栈段,包含程序的堆栈,堆栈中有返回地址、参数和倍执行函数的局部变量。
OK,在深入理解Linux内核的这里找到了"堆有大小限制吗?"的答案
灵活线性区布局(>2.6.9)与经典布局
每个进程按照用户态堆栈预期的增长量来进行内存布局。
引入灵活布局的主要优点是可以允许进程更好地使用用户态线性地址空间。
- 在经典布局中,堆的限制是小于 1GB,而其它线性区可以使用到约 2GB(减去堆栈大小)。
- 在灵活布局中,堆和其它线性区可以自由扩展,可以使用除了用户态堆栈和程序固定大小的段以外的所有线性地址空间。
执行跟踪(ptrace)
加载可执行目标文件
a.out ->execve()调用加载器->加载器创建一组新的代码,数据等段->将执行文件的页映射到虚拟地址空间中的页,新的代码和数据段被初始化为可执行文件的内容->跳转到main地址执行->程序使用到数据,缓存不命中,缓存调度
.data 段有对齐要求
程序每次运行时,程序的不同部分,包括代码段,堆栈,数据段,都会被加载到内存的不同部分,称之为地址空间随机化(为了防止攻击).但是他们的相对位置是不变的.
对于有动态库的程序:
- 检查被执行的程序,以识别哪个共享库必须装入及在每个共享库中哪个函数被有效地请求。
- 解释器发出几个 mmap() 来创建线性区,以对将存放程序实际使用的库函数(正文和数据)的页进行映射。
- 解释器根据库的线性区的线性地址更新对共享库符号的所有引用。
- 动态链接程序通过跳转到被执行程序的主入口点而终止它的执行。
接下来,自然是第四章,本文所有讲解都是来自CSAPP和深入理解Linux内核
在CSAPP上将指令的改变成为异常,在深入理解Linux内核上面成为中断,搞不懂要干嘛!!!
最终得出还是看CSAPP.比较清晰一点!!
那么我们回到原来的问题:hello world 到底发生了什么?
加载并执行->printf()->对于IO设备的处理,产生中断->中断处理程序执行->返回下一跳指令(这样回答也太虚了吧感觉)
评论记录:
回复评论: