进程创建
进程通过
fork()
创建的大致过程:
#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <unistd.h>extern int create_process(char* program, char** arg_list);int create_process(char* program, char** arg_list){pid_t child_pid;child_pid = fork();if(child_pid !=0 ){return child_pid;}else{execvp(program, arg_list);abort();}}
概览图:
编译知识
静态库
这里额外补充一些编译相关的内容。一个源码文件要变成可执行的程序,需要经过编译、链接。
这里编译出的
.o
文件,就是ELF的第一种类型,可重定位文件,格式如下:
-
.text
:存放编译好的二进制可执行代码
-
.data
:已经初始化好的全局变量
-
.rodata
:只读数据,例如字符创常量、const常量
-
.bss
:未初始化全局变量,运行时会置0
-
.symtab
:符号表,记录的是函数和变量
-
.strtab
:字符串表、字符串常量和变量名
在程序中我们提到过函数栈,局部变量是放在
栈
里的,在运行期随时分配、随时释放。这里讨论的是文件,还没到执行阶段。
如要将函数作为库文件被重用,不能以
.o
的形式存在,而是要形成库文件,最简单的类型是静态链接库
.a
文件。
ar cr libstaticprocess.a process.o# 形成二进制执行文件staticcreateprocessgcc -o staticcreateprocess createprocess.o -L. -lstaticprocess
上述第二条命令链接时,
createprocess.o
调用了
create_process
函数,但不知道位置,通过将
process.o
合并进来,就可以确定位置了。这里形成了ELF的第二种格式:
和
.o
相似,文件被分为多个
section
,通过节头表来描述。这里除了段描述之外,最重要的是p_vaddr——加载到内存的虚拟地址。
静态库特点:
-
一旦链接,代码和变量的section都合并在一起。当程序运行后,就不依赖这个库是否存在
-
但如果相同的代码段被多个程序使用时,在内存里就存在多份
-
当静态库更新了,需重新编译二进制执行文件以应用更新
动态库
当动态链接库被链接到一个程序文件时,最后的程序文件并不包括动态链接库中的代码,而仅仅包括对动态链接库的引用,并且不保存动态链接库的全路径,仅保存其名称。
gcc -shared -fPIC -o libdynamicprocess.so process.ogcc -o dynamiccreateprocess createprocess.o -L. -ldynamicprocess
执行此程序时,先寻找动态链接库,再进行加载。默认,系统在
/lib
和
/usr/lib
路径下寻找,找不到则报错。可以通过设定
LD_LIBRARY_PATH
环境变量,指定动态链接库的路劲。
动态链接库,就是ELF的第三种类型,共享对象文件。
-
多个
.interp
的Segment,里面是
ld-linux.so
动态连接器,执行链接动作
-
多个
.plt
和
.got.plt
,分别是 过程链接表(PLT) 和 全局偏移量表(GOT) 。
接下来,我们来看下程序运行时,是如何将so文件动态链接到进程空间的?
-
dynamiccreateprad8ocess
程序调用
libdynamicprocess.so
里的
create_process
函数时,是运行时才去找的,编译压根不知道在哪,因此在PLT里建立一个PLT[x]——在二进制程序里,不直接调用函数,而是调用PLT[x]里的代理代码,它会在运行时找到真正的
create_process
函数。
-
去哪找?去GOT里——它会为
create_process
函数创建GOT[y],对应运行时
create_process
函数在内存中真正的地址。
-
GOT又是咋知道的?对于
create_process
函数,GOT在开始时会创建GOT[y],但开始这里没有真正的地址,因为不知道…,它又回调PLT,告诉他:你里面的代理代码来找我要
create_process
的真实地址,我不知道,你自己想想。
-
PLT只能去调用PLT[0],PLT[0]去调用GOT[2],这里面保存
ld-linux.so
入口函数,它去找到加载到内存中的
libdynamicprocess.so
里的
create_process
函数地址,然后把这个地址放到GOT[y]中,以供下次PLT[x]调用。
运行程序为进程
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,};
具体调用链:do_execve->do_execveat_common->exec_binprm->search_binary_handler。
在系统调用时,
exec
最终调用的是
load_elf_binary
.
进程树
所有进程的祖宗进程,就是系统启动时的
init
进程。init进程会启动很多daemon进程,为系统运行提供服务。然后启动
getty
,让用户登录,登录后运行shell。
-
1号进程是
/sbin/init
,所有用户态进程的祖先
-
2号进程是内核线程
kthreadd
,所有内核态的祖先
-
在
ps -ef
下,用户态不带中括号,内核态带中括号