Linux:公司这么多项目,怎么管

在Linux下面,对二进制程序有着严格的格式要求,这就是ELF,这个格式可以根据编译的结果不同,分为不同的格式。

ELF的三种类型

一:可重定位文件

在编译的时候,先做预处理工作,例如将头文件嵌入到正文中,将定义的宏展开,然后就是真正 的编译过程,最终编译成为.o文件,这就是ELF的第一种类型,可重定位文件,长这样:

 二、可执行文件:

要想让函数作为库文件被重用,不能以.o的形式存在,而是要形成库文件,最简单的类型是静态链接库.a文件(Archives),仅仅将一系列对象文件(.o)归档为一个文 件,使用命令ar创建

ar cr libstaticprocess.a process.o

虽然这里libstaticprocess.a里面只有一个.o,但是实际情况可以有多个.o。当有程序要使用这个静态连接库的时候,会将.o文件提取出来,链接到程序中。在这个命令里,-L表示在当前目录下找.a文件,-lstaticprocess会自动补全文件名,比如加前缀 lib,后缀.a,变成libstaticprocess.a,找到这个.a文件后,将里面的process.o取出来,和 createprocess.o做一个链接,形成二进制执行文件staticcreateprocess。这个链接的过程,重定位就起作用了,原来createprocess.o里面调用了create_process函数,但是不能确定位置,现在将process.o合并了进来,就知道位置了,形成的二进制文件叫可执行文件,是ELF的第二种格式

这个格式和.o文件大致相似,还是分成一个个的section,并且被节头表描述。只不过这些section是多个.o文件合并过的。但是这个时候,这个文件已经是马上就可以加载到内存里面执行的文件了,因而这些section被分成了需要加载到内存里面的代码段、数据段和不需要加载到内存里面的部分,将小的section合成了大的段segment,并且在最前面加一个段头表 (Segment Header Table)。在代码里面的定义为struct elf32_phdr和struct elf64_phdr,这里面除了有对于段的描述之外,最重要的是p_vaddr,这个是这个段加载到内存的虚拟地址。

三、共享对象文件

静态链接库有个缺点,就是一旦链接进去,代码和变量的section都合并了,因而程序运行的时候,就不依赖于这个库是否存在,是相同的代码段,如果被多个程序使用的话,在内存里面就有多份,而且一旦静态链接库更新了,如果二进制执行文件不重新编译,也不随着更新。因为出现了另外一种——动态链接库,不仅是一组对象文件的简单归档,而是多个对象文件的重新组合,可被多个程序共享。

gcc -shared -fPIC -o libdynamicprocess.so process.o

当一个动态链接库被链接到一个程序文件中的时候,最后的程序文件并不包括动态链接库中的代 码,而仅仅包括对动态链接库的引用,并且不保存动态链接库的全路径,仅仅保存动态链接库的 名称。动态链接库就是第三种类型,共享对象文件

基于动态连接库创建出来的二进制文件格式还是ELF,但是稍有不同。首先,多了一个.interp的Segment,这里面是ld-linux.so,这是动态链接器,也就是说,运行时的链接动作都是它做的。另外,ELF文件中还多了两个section,一个是.plt,过程链接表(Procedure Linkage Table, PLT),一个是.got.plt,全局偏移量表(Global Offset Table,GOT)

比如对于dynamiccreateprocess这个程序要调用libdynamicprocess.so里的create_process函数。由于是运行时才去找,编译的时候,压根不知道这个函数在哪里,所以就在PLT里面建立一项 PLT[x]。这一项也是一些代码,有点像一个本地的代理,在二进制程序里面,不直接调用 create_process函数,而是调用PLT[x]里面的代理代码,这个代理代码会在运行的时候找真正的 create_process函数。而代理代码就是用到了GOT这里面也会为create_process函数创建一项GOT[y]。这一项是运行时create_process函数在内存中真正的地址。

对于GOT怎么直到create_process函数在内存中真正的地址?一开始创建了GOT[y]里面是空的,他调用plt[0],PLT[0]转而调用GOT[2],这里面是是ld-linux.so的 入口函数,这个函数会找到加载到内存中的libdynamicprocess.so里面的create_process函数的地址,然后把这个地址放在GOT[y]里面。

运行程序为进程

上面讲完了ELF格式,生成ELF文件之后还是个程序,那怎么把这个文件加载到内存里面呢?在内核中,有这么一个数据结构用来定义加载二进制文件的方法

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

 进程树

既然所有的进程都是从父进程fork过来的,那总归有一个祖宗进程,这就是咱们系统启动的init进程。在解析Linux的启动过程的时候,1号进程是/sbin/init。如果在centOS 7里面,我们ls一下,可 以看到,这个进程是被软链接到systemd的。PID 1的进程就是我们的init进程systemd,PID 2的进程是内核线程kthreadd,这两 个我们在内核启动的时候都见过。其中用户态的不带中括号,内核态的带中括号。

总结

对高级编程语言,比如对C语言:

1、我们先将文件编译成so文件和可执行文件,放在硬盘上。

2、用户态的进程A执行fork(),创建进程B。

3、在进程B的处理逻辑中,执行exec系列系统调用,这个系统调用会通过load_elf_binary方法,将刚才生成的可执行文件,加载到进程B的内存中执行。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值