1 程序是如何生成和终止的?
!!!!可执行程序的生成
源代码 ->预处理 ->编译-> 汇编 ->链接-> 可执行文件
预处理:
gcc main.c -o main.i -E
主要做的任务;宏替换,条件编译指令处理,头文件包含,特殊符号处理
编译:
gcc main.i -o main.s -S
主要做的任务是:在确定所有指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码
汇编:
gcc main.s-o main.o-C
把汇编语言翻译成目标机器指令的过程,对被翻译系统处理的每一个C语言源程序,都将最终经过这一处理
而得到相应的目标文件,目标文件也就是与源程序等效的目标机器语言代码。目标文件由段组成,一般至少有
两段:代码段,数据段。
unix环境下主要有三种类型的目标文件
1)可重定位文件 其中包含有适合于其它目标文件链接来创建一个可执行的或者共享的目标文件的代码和数据。
2)共享目标文件
3)可执行文件 它包含了一个可以被操作系统创建一个进程来执行的文件。 汇编程序生成的实际上是第一种类型的目标文件。
对于后两种还需要其他的一些处理方能得到,这个就是链接程序的工作了。
连接:
连接程序就是把有关的目标文件彼此相连,就是讲一个文件中引用的符号与另一个文件中的定义连接起来
使得所有的目标文件成为一个可行性的统一整体。
连接又分两种:
静态连接:函数的代码将从其所在地静态链接库中被拷贝到最终的可执行程序中
动态连接:函数的代码被放到动态链接库或共享对象的某个目标文件中,连接程序只需要记录下少量的名字和登记信息
只有调用的时候才去系统中调用,不与可执行文件一起,执行起来比较慢,但是可执行文件比较短小
!!!!可执行文件的组成
a.out 文件和进程的内存布局是有区别的,a.out中包括若干其他类型的段,例如,包含符号表的段,包
含调试信息的段以及包含动态共享库链接表的段等
这些部分并不装载到进程执行的程序映像中,还包括最终的数据段,文本段,但是对bss(非初始化数据
段)进行了优化,只记录他的大小,然后内核在程序开始
运行前把他们设置为0。只有初始化的全局变量和静态变量,其他的局部变量在程序运行时才开始创建。
!!!!存储空间布局
正文段
初始化数据段
非初始化数据段
栈
堆
正文段和初始化数据段由exec从程序文件(可执行文件)中读入,未初始化数据段,则由exec从可执行文
件中读入大小,然后全部初始化为
0,栈用来存储一些临时变量和函数调用时的环境信息,记录中断的位置等,堆,动态分配存储空间
程序的存储空间从低到高是:正文,初始化数据,未初始化数据,堆(从低到高),栈(从高到低),最
上面存放的是命令行参数和环境变量。运行程序时操作系统将命令行参数传递给main函数的形式参数。环境变量是
系统告诉程序所在的完整路径,系统除了在当前目录下面寻找此程序外,还应到path中指定的路径去找。
!!!!!执行和退出
内核使程序执行的唯一方法
是调用一个exec函数(exec函数并不创建新的进程,调用后进程ID不变,exec函数只是用一个全新的程序替换了当前进程的正文、数据段、堆和栈段。
fork可以创建新进程,exec可以执行新程序),然后调用一个特殊的启动例程。可执行的程序文件将此启动例程指定为程序的起始地址(连接编辑器设置的),
启动例程从内核取得命令行参数和环境变量,然后这个启动例程调用main函数。
进程终止的唯一方法是:显示或隐式的(通过调用exit,exit首先调用各种终止处理程序(由atexit函数注册),然后按需多次调用fclose,关闭所有打开的流,
在调用_exit函数)调用_exit或_Exit返回给内核,进程也可以非自愿的由一个信号使其终止。只要进程中的调用了eixt,不管是调用子函数中调用的,
还是在线程中调用的,进程都要终止,都会返回内核。而且不管进程是如何的结束都要执行一段相同的代码就是用来清理内存和释放文件描述符。