一、实验代码:
执行结果:
二、fork和exec系统调用在内核中的执行过程
Fork :
1.libc库对系统调用进行了封装,在执行int $0x80进入内核之前,封装例程就已经把系统调用号装入eax寄存器了。
2.当用户态进程发出int $0x80指令时,CPU切换到内核态并开始从地址system_call处开始执行指令。
3.system_call()函数:
· 首先把系统调用号和这个异常处理程序可以用到的所有CPU寄存器保存到相应的栈中。
· 如果系统调用号无效,该函数就把-ENOSYS值存放在栈中曾保存eax寄存器的单元中。当进程恢复它在用户态的执行时,会在eax中发现一个负的返回码。
· 如果系统调用号有效,则调用与eax中所包含的系统调用号对应的特定服务例程:call*sys_call_table(0,%eax,4);
EXEC:
EIP指向ELF可执行文件的入口点,这个入口点取决于程序的链接方式:
· 对于静态链接的ELF可执行文件,这个程序入口点就是ELF文件的文件头中e_entry所指的地址;
· 对于动态链接的ELF可执行文件,这个程序入口点就是动态链接器。
三、task_struct进程控制块结构
在linux 中每一个进程都由task_struct 数据结构来定义。task_struct 就是我们通常所说的PCB.她是对进程控制的唯一手段也是最有效的手段. 当我们调用fork() 时, 系统会为我们产生一个task_struct 结构体。然后从父进程,那里继承一些数据, 并把新的进程插入到进程树中, 以待进行进程管理。task_struct的结构如下:
四、ELF文件格式与进程地址空间的联系
ELF文件格式中的 ELF头部、段头部表对应进程地址空间中的代码段,在加载可执行文件时,会把它们映射到进程地址空间中的代码段区域。ELF文件格式中的 .data对应进程地址空间中的数据段,在加载可执行文件时,会把它们映射到进程地址空间的数据段区域。