【刘蔚然 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000】
WEEK SIX(3.28——4.3)进程的创建与描述
SECTION 1 进程描述
1.进程控制块——PCB
2.进程描述符——struct task_struct
1. pid_t pid又叫进程标识符,唯一地标识进程
2. 第1295行,list_head tasks即 进程链表
- 双向循环链表链接起了所有的进程,也表示了父子、兄弟等进程关系
3. struct mm_struct 指的是进程地址空间,涉及到内存管理(对于X86而言,一共有4G的地址空间)
4. thread_struct thread 与CPU相关的状态结构体
5. struct *file表示打开的文件链表
6. Linux为每个进程分配一个8KB大小的内存区域,用于存放该进程两个不同的数据结构:Thread_info和进程的内核堆栈
3.进程状态转换图
- TASK_RUNNING具体是就绪还是执行,要看系统当前的资源分配情况;
- TASK_ZOMBIE也叫僵尸进程
SECTION 2 进程的创建
1.进程的创建概览及fork一个进程的源代码
回顾:start_kernel创建了cpu_idle,也就是0号进程。而0号进程又创建了两个线程,一个是kernel_init,也就是1号进程,这个进程最终启动了用户态;另一个是kthreadd。这就是“道生一,一生二”。0号进程是固定的代码;1号进程是通过复制0号进程PCB之后在此基础上做修改得到的。
-
回顾:系统调用的进程创建过程
iret与int 0x80指令对应,一个是弹出寄存器值,一个是压入寄存器的值
如果将系统调用类比于fork();那么就相当于系统调用创建了一个子进程,然后子进程返回之后将在内核态运行,而返回到父进程后仍然在用户态运行
-
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.} -
创建新进程的框架
fork,vfork,clone都可以创建新进程,他们都是通过调用do_fork来实现的。dup_thread复制父进程的PCB
copy_process修改复制的PCB以适应子进程的特点,也就是子进程的初始化
-
分配一个新的内核堆栈(用于存放子进程数据)
内核堆栈的一部分也要从父进程中拷贝
-根据拷贝的内核堆栈情况设置eip,esp寄存器的值
-
一个新创建的子进程,(当它获得CPU之后)是从哪一行代码进程执行的?
与之前写过的my_kernel相比较,kernel中是可以指定新进程开始的位置(也就是通过eip寄存器指定代码行)。fork中也有相似的机制
-
这就涉及子进程的内核堆栈数据状态和task_struct中thread记录的sp和ip的一致性问题,这是在哪里设定的?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; //调度到子进程时的第一条指令地址,也就是说返回的就是子进程的空间了
2.使用gdb跟踪创建新进程的过程
-
更新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)也就是内核堆栈特定的地址找到并初始化
-
到了159、160行的代码就是把压入的代码再放到子进程中:
*children = *current_pt_regs();
childregs->ax = 0; -
164行,是确定返回地址
p->thread.ip = (unsigned long) ret_from_fork;
此后,可以输入finish使得进程运行完。
总结与补充
我在上周的实验中尝试用实验楼(链接个人热点)进行实验,发现报错(地址无法解析)。然而在本周的实验中,连接广域网之后,实验楼的环境可以正常进行menu的联网更新。我认为应该是连接的网络决定了实验楼的实验环境能否与外界网络发生交互。