缺点:
对程序的数据和代码段进行分割,常用的放到内存,不常用的扔在磁盘,需要的时候放入内存。
段页式管理机制结合了段式管理和页式管理的优点。简单来说段页式管理机制就是把主存先分成若干段,每个段又分成若干页,也就是说 段页式管理机制 中段与段之间以及段的内部的都是离散的。
gcc -E hello.c -o hello.i
gcc -S hello.i -o hello.s
源代码:
源程序送入扫描器,运用有限状态机简单进行词法分析,输出一系列token,例如:
对扫描器产生的记号进行语法分析,通过上下文无关语法,生成以表达式为节点的语法树,
语法分析仅仅完成了对表达式的语法层面的分析,不了解语句是否真的有意义,例如C语言两个指针相乘,合法但无意义。语义分析由语义分析器完成,编译器能完成的是静态语义,即在编译期间能确定的语义信息,相对于的动态语义只有在运行期才能确定的语义。
优化一些在编译时期可以确定的表达式,比如2+6就被优化成8减少表达式数量,输出的结果是中间代码
中间代码转化为目标机器代码,依赖于目标机器的配置,如字长,寄存器,整数类型 浮点类型等,目标机器代码可以是汇编形式。
gcc -c hello.s -o hello.o
ELF文件 executable linkable file,链接前的.o,链接后的可执行文件以及静态库动态库均是ELF格式
.rel.text text段的重定位表 对于每个需要重定位的代码段和数据段,在重定位表进行存储。
.rel.data data段的重定位表
.text 代码段
.data 数据段 已初始化的全局变量和静态变量
.comment 注释信息,例如编译器版本信息等
.rodata 只读数据,const变量和字符串常量等
.bss 未初始化的全局变量和局部静态变量
section table 段表 除头之外最重要的结构,描述每个段的信息,如段名,段的长度,文件中的偏移,读写权限及段的其他属性。编译器,链接器和装载器都是依据段表访问段。
strtab/shstrtab 字符串表/段字符串表
ld a.o b.o -e main -o ab
生成静态库,需要先对源文件进行汇编操作 (使用参数 -c) 得到二进制格式的目标文件 (.o 格式), 然后在通过 ar 工具将目标文件打包就可以得到静态库文件了 (libxxx.a)。
使用 ar 工具创建静态库的时候需要三个参数:
参数c:创建一个库,不管库是否存在,都将创建。
参数s:创建目标文件索引,这在创建较大的库时能加快时间。
参数r:在库中插入模块 (替换)。默认新的成员添加在库的结尾处,如果模块名已经在库中存在,则替换同名的模块。
# hello.o生成libmyhello.a静态库
ar -crs libmyhello.a hello.o
动态链接就是将链接的过程推迟到运行时在进行。使得每个库文件在内存和磁盘上都只有一个副本,共用同一个库内存的好处是减少内存的换入换出,增加cpu缓存命中率,程序可扩展性和兼容性强。性能略有下降,但这点性能换来空间节省和灵活性,可接受的。
# a.c文件生成 a.so动态库
gcc -fPIC -shared -o liba.so a.c
#使用动态库
gcc main.c -o main -L ./ -la
#其中-L指明动态链接库的路径,-l后是链接库的名称,省略lib
对于全局/静态的数据,由于不能事先确定是否引用了外部的库,所以也使用GOT处理
bar@plt:
jmp *(bar@GOT) //如果是第一次链接,该语句的效果只是跳转到下一句指令。否则,将会跳转到 bar()函数对应的位置
push n //压栈 n,n 是 bar 这个符号在重定位表 .rel.plt 中的下标
push moduleID // 压栈当前模块的模块ID,上述例子中的 liba.so
jump _dl_runtime_resolve() //跳转到动态链接器中的地址绑定处理函数
plt表的前三项:
动态链接器是所有程序运行时的代码入口,动态链接器本身也是一个共享对象,但它不能依赖于其他对象。
从ELF文件头和dynamic中得到依赖的所有共享对象集合,找到相应的共享对象映射到进程空间,若共享对象有依赖就将依赖的也放入集合中,整个装载的过程是广度优先搜索的过程。当对象被装载后,符号表会合并到全局符号表,当所有的共享对象都装载后,符号表包含所有符合。
通过两个寄存器来实现:sbq
抽象:
eax寄存器存储返回值。但eax本身只有四字节,若大于四字节,则在调用函数前的函数栈内申请temp中间内存,在调用函数内部将得到的结果拷贝到temp中,之后返回后将temp的内存拷贝到返回的结果中。需要两次拷贝。
为了让应用程序访问操作系统的资源或借助操作系统完成相应行为,操作系统为应用程序提供一些接口供其使用。
进程运行时,有两种不同的特权级别,内核态和用户态。用户态程序通过中断从用户态切换到内核态。
什么是中断?中断是一个硬件或软件发出请求,要求CPU暂停当前的手头工作转手去处理更加重要的事情。
中断具有两个属性,中断号和中断处理程序,不同中断具有不同的中断号,内核有一个中断向量表,包含了指向指定中断号的执行函数的指针,中断到来,中断向量表查找相应代码,执行中断代码,之后返回继续原先工作。
中断有两类:硬件中断和软件中断。硬件中断包括电源掉电,键盘被按下等。软件中断通常是一条带有中断号的指令,用户可以手动的触发。在windows下,系统调用的中断号是int 0x2e;linux下是int 0x80。
由于中断号宝贵,所有多个系统调用的接口都是使用同一个80中断号。如何区分不同的系统调用?通过EAX寄存器,EAX寄存器中断调用前可以传递系统调用号,调用结束后可以传递返回结果。
部分系统调用号: