谢文杰 +
原创作品转载请注明出处 +
《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
一、实验目的
阅读理解task_struct数据结构;分析fork函数对应的内核处理过程sys_clone,理解创建一个新进程如何创建和修改task_struct数据结构;使用gdb跟踪分析一个fork系统调用内核处理函数sys_clone,验证对Linux系统创建的理解;进程是从哪里开始执行的。
二、实验内容
编译代码,输入指令
然后利用gdb中的c、n、s等命令进行跟踪调试
do_fork断点
copy_thread
三、实验分析
1 .进程的创建
在用户态调用fork()函数来创建一个子进程,fork系统调用在父进程和子进程都会返回一次,在父进程的返回值是子进程中的进程id号,在子进程的返回值是0,在shell命令行下执行函数调用后,函数中的if和else子句中的程序都得到了执行,但是if和else子句的执行分别是在不同的进程中执行的,一个在父进程、一个在子进程中执行;其中子进程复制了父进程所有的信息。子进程从fork()函数后开始执行,与父进程的程序逻辑相同。fork、vfork和clone三个系统调用都可以创建一个新进程,而且都是通过调用do_fork来实现进程的创建。创建新进程是通过复制当前进程的PCB(task_struct)来实现的,然后进行相应的初始化。
do_fork 流程
调用 copy_process 为子进程复制出一份进程信息
如果是 vfork 初始化完成处理信息
调用 wake_up_new_task 将子进程加入调度器,为之分配 CPU
如果是 vfork,父进程等待子进程完成 exec 替换自己的地址空间
copy_process 流程
调用 dup_task_struct 复制当前的 task_struct
检查进程数是否超过限制
初始化自旋锁、挂起信号、CPU 定时器等
调用 sched_fork 初始化进程数据结构,并把进程状态设置为 TASK_RUNNING
复制所有进程信息,包括文件系统、信号处理函数、信号、内存管理等
调用 copy_thread 初始化子进程内核栈
为新进程分配并设置新的pid
dup_task_struct
先调用alloc_task_struct_node分配一个task_struct结构体。
调用alloc_thread_info_node,分配了一个union,注意,这里不仅仅分配了一个thread_info结构体,还分配了一个stack数组。返回值为ti,实际上就是栈底。
将栈底的地址赋给task的stack变量。
copy_thread
获取子进程寄存器信息的存放位置
对子进程的thread.sp赋值,将来子进程运行,这就是子进程的esp寄存器的值。
如果是创建内核线程,那么它的运行位置是ret_from_kernel_thread,将这段代码的地址赋给thread.ip,之后准备其他寄存器信息,退出。
将父进程的寄存器信息复制给子进程。
将子进程的eax寄存器值设置为0,所以fork调用在子进程中的返回值为0。
子进程从ret_from_fork开始执行,所以它的地址赋给thread.ip,也就是将来的eip寄存器。
有上述可以知道,新进程从ret_from_fork处开始执行,进程的创建基本都是基于复制的方式进行,然后在进行一些处理完成。
2 .进程执行的状态及状态转化过程