操作系统原理(5)

1.进程编译:程序的二进制格式
    在 Linux 下面,二进制的程序有严格的格式,这个格式称为 ELF(可执行与可链接格式)。这个格式可以根据编译的结果不同,分为不同的格式。
    1)在编译的时候,先做预处理工作,例如将头文件嵌入到正文中,将定义的宏展开,然后就是真正的编译过程,最终编译成为.o 文件,这就是 ELF 的第一种类型,可重定位文件(Relocatable File)。

文件的格式如右图:
在这里插入图片描述

    ELF 文件的头是用于描述整个文件。
    .text:放编译好的二进制可执行代码。
    .data:已经初始化好的全局变量。
    .rodata:只读数据,例如字符串常量、const 的变量。
    .bss:未初始化全局变量,运行时会置 0。
    .symtab:符号表,记录的则是函数和变量。
    .strtab:字符串表、字符串常量和变量名。
    这些节的元数据信息也需要有一个地方保存,就是最后的节头部表(Section Header Table)。在这个表里面,每一个 section 都有一项。在 ELF 的头里面,有描述这个文件的节头部表的位置,有多少个表项等等信息。
    .o 文件不是一个可以直接运行的程序,这里面只是部分代码片段,.o 里面的位置是不确定的,但是必须是可重新定位的。.rel.text, .rel.data 与重定位有关。
    2)静态链接库.a 文件(Archives),仅仅将一系列对象文件(.o)归档为一个文件,使用命令 ar 创建,ar cr libstaticprocess.a process.o。当有程序要使用这个静态连接库的时候,会将.o 文件提取出来,链接到程序中,gcc -o staticcreateprocess createprocess.o -L. -lstaticprocess。

形成的二进制文件叫可执行文件,
是 ELF 的第二种格式如图:
在这里插入图片描述

    这些 section 是多个.o 文件 合并过的,并且将小的 section 合成了大的段 segment,并且在最前面加一个段头表(Segment Header Table)。
p_vaddr是段加载到内存的虚拟地址。在 ELF 头里面,有一项 e_entry,也是个虚拟地址,是这个程序运行的入口。
    静态链接库一旦链接进去,代码和变量的 section 都合并了,因而程序运行的时候,就不依赖于这个库是否存在。但是这样有一个缺点,就是相同的代码段,如果被多个程序使用的话,在内存里面就有多份,而且一旦静态链接库更新了,如果二进制执行文件不重新编译,也不随着更新。
    3)动态链接库(Shared Libraries),不仅仅是一组对象文件的简单归档,而是多个对象文件的重新组合,可被多个程序共享,gcc -shared -fPIC -o libdynamicprocess.so process.o。
    当一个动态链接库被链接到一个程序文件中的时候,最后的程序文件并不包括动态链接库中的代码,而仅仅包括对动态链接库的引用,并且不保存动态链接库的全路径,仅仅保存动态链接库的名称,gcc -o dynamiccreateprocess createprocess.o -L. -ldynamicprocess。
动态链接库,就是 ELF 的第三种类型,共享对象文件(Shared Object)。首先,多了一个.interp 的 Segment,这里面是 ld-linux.so,这是动态链接器,也就是说,运行时的链接动作都是它做的。其次还多了两个 section,一个是.plt,过程链接表(Procedure Linkage Table,PLT),一个是.got.plt,全局偏移量表(Global Offset Table,GOT)。
     将 so 文件动态链接到进程空间的过程:执行PLT[x]中的代码调用GOT[y],GOT[y]保存的是被调用函数在内存中的地址。
    GOT保存地址的过程:GOT 一开始创建一项 GOT[y],然后回调 PLT,PLT 这个时候会转而调用 PLT[0],也即第一项,PLT[0]转而调用 GOT[2],这里面是 ld-linux.so 的入口函数,这个函数会找到加载到内存中的函数的地址,把这个地址放在 GOT[y]里面。
2.运行程序为进程
    怎么把这个文件加载到内存里面?在内核中,有这样一个数据结构,用来定义加载二进制文件的方法。

struct linux_binfmt {
struct list_head lh;
struct module *module;
int (*load_binary)(struct linux_binprm *);
int (*load_shlib)(struct file *);
int (*core_dump)(struct coredump_params cprm);
unsigned long min_coredump; /
minimal dump size */
} __randomize_layout;

对于 ELF 文件格式,有对应的实现。

static struct linux_binfmt elf_format = {
.module = THIS_MODULE,
.load_binary = load_elf_binary,
.load_shlib = load_elf_library,
.core_dump = elf_core_dump,
.min_coredump = ELF_EXEC_PAGESIZE,
};

    原理是 exec 这个系统调用最终调用的 load_elf_binary。
    exec 比较特殊,它是一组函数:包含 p 的函数(execvp, execlp)会在 PATH 路径下面寻找程序;不包含 p 的函数需要输入程序的全路径;包含 v 的函数(execv, execvp, execve)以数组的形式接收参数;包含 l 的函数(execl, execlp, execle)以列表的形式接收参数;包含 e 的函数(execve, execle)以数组的形式接收环境变量。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值