进程的创建与可执行程序的加载
张颜(SA*****111)
fork函数
fork函数的特点概括起来就是“调用一次,返回两次”,在父进程中调用一次,子进程返回0,父进程返回子进程ID,出错返回-1;子进程是父进程的副本,子进程从父进程那得到了数据空间,堆和栈,但不是与父进程共享而是单独分配内存,即父进程与子进程有独立的地址空间。父子进程共享正文段。fork函数返回后,子进程和父进程都是从fork函数的下一条语句开始执行。
使用例程:
运行效果:
fork系统调用
fork这个API函数由C库提供,在C库中封装了与其同名的系统调用fork(),该系统调用对编程者是隐藏的,编程者只需知道如何使用这些API即可。系统调用在内核中并不是直接实现的,而是通过调用各自对应的服务例程。系统调用fork在内核中对应的服务例程为sys_fork()。该服务例程定义在linux/arch/i386/kernel/process.c中,可以看到sys_fork()调用了do_fork(),而后者又调用了copy_process()。
对fork调用反汇编,观察其在内核中的执行过程:
exec函数
用fork函数创建子进程后,子进程往往要调用一种exec函数一执行另一个程序。当进程调用一种exec函数时,该进程执行的程序完全被替换为新程序,而新程序则从其main函数开始执行。因为调用exec并不创建新进程,所以前后的进程ID并未改变。exec只是用一个全新的程序替换了当前进程的正文、数据、堆和栈。
使用例程:
exec系统调用
所有exec函数(除execve()外)都是C库定义的封装例程 ,并利用了execve()系统调用,这是Linux所提供的处理程序执行的唯一系统调用。系统调用execve()在内核中对应的服务例程为sys_execve()。sys_execve()把可执行文件路径名拷贝到一个新分配的页框。然后调用do_execve()函数,传递给它的参数为指向这个页框的指针、指针数组的指针及把用户态寄存器内容保存到内核态堆栈的位置。
对execl调用反汇编,观察其在内核中的执行过程:
利用fork与exec实现在子进程中加载可执行程序
使用例程:
forkandexec.c
test.c(对应可执行文件test):
运行./forkandexec:
在上图中们可以发现,子进程并没有打印"execl error",这是因为子进程执行的程序已完全被execl替换为新程序。
进程描述符的结构
ELF文件介绍
ELF文件主要有三种类型:
1、可重定位文件:包含了代码和数据.可与其它ELF文件建立一个可执行或共享的文件
2、可执行文件:是可以直接执行的程序
3、共享目标文件:包括代码和数据,可以在两个地方链接。第一,连接器可以把它和其它可重定位文件和共享文件一起处理以建立另一个ELF文件;第二,动态链接器把它和一个可执行文件和其它共享文件结合在一起建立一个进程映像
ELF文件参与程序的连接(建立一个程序)和程序的执行(运行一个程序),编译器和链接器将其视为节头表(section header table)描述的一些节(section)的集合,而加载器则将其视为程序头表(program header table)描述的段(segment)的集合,通常一个段可以包含多个节。可重定位文件都包含一个节头表,可执行文件都包含一个程序头表。共享文件两者都包含有。为此,ELF文件格式同时提供了两种看待文件内容的方式,反映了不同行为的不同要求。如下图所示: