《Linux内核分析》 第6周
一、进程的描述
1.进程控制块PCB
2.linux下的进程转化图
-
TASK_RUNNING可以是就绪态或者执行态,具体取决于系统调用
-
TASK_ZOMBIE僵尸进程(终止的进程)
3.进程描述符task_struct
-
- task_struct是一个数据结构,数据结构庞大;
- pid:进程标识值,一个int型数值
- 进程链表struct list_head tasks,数据结构如下:
-
它是一个双向链表,用来把当前所有进程用链表连起来
- 进程描述符中有几个域表示父子关系
-
程序创建的进程具有父子关系在编程时经常需要用到这样的父子关系。进程描述符中有几个域用来表示这样的关系。
-
每个进程都有一个父进程,每一个父进程都有0个或多个子进程
- struct thread_struct thread
-
与CPU相关
- struct *file表示打开的文件链表
- Linux为每个进程分配一个8k大小的内存区域,用于存放该进程两个不同的数据结构:Thread_info和进程的内核堆栈
二、进程的创建
1.复习:“道生一,一生二”
- start_kernel创建了cpu_idle,即0号进程
- 0号进程又创建了两个线程kernel_init和kthreadd
- kernel_init即1号进程,这个进程最终启动了用户态
2.系统调用回顾
- 系统调用
-
用户态int 0x80指令实现软中断,过程详细如图:
- fork与系统调用相关,如图:
3.创建新进程的过程理解
- 创建一个新进程的框架
-
dup——thread复制父进程的pcb
-
copy_process修改复制的pcb以适应子进程的特点,也就是子进程的初始化
-
分配一个新的内核堆栈(用于存放子进程数据)
- 内核堆栈的一部分也要从父进程中拷贝 - ![](http://images2015.cnblogs.com/blog/744616/201603/744616-20160328214201832-60467643.png) - ![](http://images2015.cnblogs.com/blog/744616/201603/744616-20160328214217316-1809241664.png) - 根据拷贝的内核堆栈情况设置eip,esp寄存器的值
- 一个新进程(子进程)从哪一行代码开始执行?
- 与之前写过的my_kernel相比较,kernel中可以指定新进程开始的位置(通过eip)。fork中也有相似的机制。
- copy_thread in copy_process
1.*childregs = *current_pt_regs(); //复制内核堆栈,并不是全部,只是regs结构体(内核堆栈栈底的程序)
2.childregs->ax = 0; //为什么子进程的fork返回0,这里就是原因!
3.
4.p->thread.sp = (unsigned long) childregs; //调度到子进程时的内核栈顶
5.p->thread.ip = (unsigned long) ret_from_fork; //调度到子进程时的第一条指令地址,也就是说返回的就是子进程的空间了
4.fork代码
1.#include <stdio.h>
2.#include <stdlib.h>
3.#include <unistd.h>
4.int main(int argc, char * argv[])
5.{
6.int pid;
7./* fork another process */
8.pid = fork();
9.if (pid < 0)
10.{
11./* error occurred */
12.fprintf(stderr,"Fork Failed!");
13.exit(-1);
14.}
15.else if (pid == 0) //pid == 0和下面的else都会被执行到(一个是在父进程中即pid ==0的情况,一个是在子进程中,即pid不等于0)
16.{
17./* child process */
18.printf("This is Child Process!\n");
19.}
20.else
21.{
22./* parent process */
23.printf("This is Parent Process!\n");
24./* parent will wait for the child to complete*/
25.wait(NULL);
26.printf("Child Complete!\n");
27.}
28.}
三、实验 使用gdb跟踪创建新进程的过程
1.在实验楼环境下的操作步骤
-
更新menu内核,然后删除test_fork.c以及test.c(以减少对之后实验的影响)
-
编译内核,可以看到fork命令
-
启动gdb调试,并对主要的函数设置断点
-
在MenuOS中执行fork,就会发现fork函数停在了父进程中
-
继续执行之后,停在了do_fork的位置。然后n单步执行,依次进入copy_process、dup_task_struct。按s进入该函数,可以看到*dst = *src(也就是复制父进程的struct)
-
在copy_thread中,把task_pg_regs(p)也就是内核堆栈特定的地址找到并初始化
四、总结
本周的课堂主要讲述的是进程的前半部分——关于基本的描述,以及新进程生命周期的开始(进程创建)。
关于进程描述,可以看到的是task_struct是进程描述的关键;其中含有一个进程从创建到终结的全部信息。关于进程创建,这里以fork函数为例进行了讲解;因为调用了fork函数之后就会创建新进程,创建的过程从代码和gdb调试两个方向进行了分析。