实验六———分析Linux内核创建一个新进程的过程

分析Linux内核创建一个新进程的过程

攥写人:李鹏举  学号:20132201

( *原创作品转载请注明出处*)

( 学习课程:《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 )

 本周要求:

  1. 阅读理解task_struct数据结构http://codelab.shiyanlou.com/xref/linux-3.18.6/include/linux/sched.h#1235;

  2. 分析fork函数对应的内核处理过程sys_clone,理解创建一个新进程如何创建和修改task_struct数据结构;

  3. 使用gdb跟踪分析一个fork系统调用内核处理函数sys_clone ,验证您对Linux系统创建一个新进程的理解,推荐在实验楼Linux虚拟机环境下完成实验。

  4. 特别关注新进程是从哪里开始执行的?为什么从哪里能顺利执行下去?即执行起点与内核堆栈如何保证一致。

进程管理总结:

Linux通过复制父进程来创建一个新进程

fork函数,具体的过程:

复制一个PCB——task_struct要给新进程分配一个新的内核堆栈、

ti = alloc_thread_info_node(tsk, node); t

sk->stack = ti;

setup_thread_stack(tsk, orig); //这里只是复制thread_info,而非复制内核堆栈要修改复制过来的进程数据,比如pid、进程链表等,见copy_process内部。

 

*childregs = *current_pt_regs(); //复制内核堆栈
childregs->ax = 0; // 子进程的fork返回0

p->thread.sp = (unsigned long) childregs; //调度到子进程时的内核栈顶
p->thread.ip = (unsigned long) ret_from_fork; //调度到子进程时的第一条指令地址

系统调用内核处理函数sys_fork,sys_vfrok,sys_clone,其实最终执行的都是do_fork

do_fork里有:

copy_process

dup_task_struct // 复制pcb

alloc_thread_info_node // 创建了一个页面,其实就是实际分配内核堆栈空间的效果。

setup_thread_stack // 把thread_info的东西复制过来之后初始化子进程


***创建的新进程是从哪里开始执行的--》ret_from_fork(这是本周的重中之重)
*childregs = *current_pt_regs(); 复制内核堆栈(复制的pt_regs,是SAVE_ALL中系统调用压栈的那一部分。)
childregs->ax = 0; 子进程的fork返回0
p->thread.sp = (unsigned long) childregs; 调度到子进程时的内核栈顶
p->thread.ip = (unsigned long) ret_from_fork; 调度到子进程时的第一条指令地址
ip指向的是ret_from_fork,所以是从这里开始执行的。

实验过程:

1.删除原来的menu,并clone新的menu,用test_fork.c覆盖test.c

2.make rootfs之后新的内核启动,测试fork功能:

3. 使用-s -S冷冻内核,准备调试:

4.进行gdb分布调试:

之后经过一步步的调试得到最终的结果。

实验总结:

本周的课程最重要的就是进程的管理,而do_fork就是其中一个最关键的宏。

do_fork处理了以下内容:

1. 调用copy_process,将当期进程复制一份出来为子进程,并且为子进程设置相应地上下文信息。
2. 初始化vfork的完成处理信息(如果是vfork调用)
3. 调用wake_up_new_task,将子进程放入调度器的队列中,此时的子进程就可以被调度进程选中,得以运行。
4. 如果是vfork调用,需要阻塞父进程,知道子进程执行exec。

进程创建的关键copy_process:

copy_process的大体流程:

 检查各种标志位(已经省略)
 调用dup_task_struct复制一份task_struct结构体,作为子进程的进程描述符。
 检查进程的数量限制。
 初始化定时器、信号和自旋锁。
 初始化与调度有关的数据结构,调用了sched_fork,这里将子进程的state设置为TASK_RUNNING。
 复制所有的进程信息,包括fs、信号处理函数、信号、内存空间(包括写时复制)等。
 调用copy_thread,这又是关键的一步,这里设置了子进程的堆栈信息。
 为子进程分配一个pid
 设置子进程与其他进程的关系,以及pid、tgid等。这里主要是对线程做一些区分。
在copy_process中,copy_thread函数为子进程准备了上下文堆栈信息

copy_thread的流程如下:

1. 获取子进程寄存器信息的存放位置
2. 对子进程的thread.sp赋值,将来子进程运行,这就是子进程的esp寄存器的值。
3. 如果是创建内核线程,那么它的运行位置是ret_from_kernel_thread,将这段代码的地址赋给thread.ip,之后准备其他寄存器信息,退出
4. 将父进程的寄存器信息复制给子进程。
5. 将子进程的eax寄存器值设置为0,所以fork调用在子进程中的返回值为0.
6. 子进程从ret_from_fork开始执行,所以它的地址赋给thread.ip,也就是将来的eip寄存器。
(这就是我所了解到的全部的流程,本来应该画个流程图的,但是作图太困难了,还是直接文字说明一下吧)




 

posted @ 2016-04-03 21:04  普普通通  阅读(704)  评论(0编辑  收藏  举报