第六周 linux内核进程的创建
1.task_struct数据结构分析
对于linux而言,每个进程都有一个进程控制PCB(process control block)来保存每个进程的相关信息。其中task_struct则是PCB的具体的数据结构通过内核代码可以发现,内核当中定义一个task_struct的结构体用来保存进程的相关信息。这里先来分析下task_struct的结构体。task_struct的代码如下:
http://codelab.shiyanlou.com/xref/linux-3.18.6/include/linux/sched.h#1235
1 #define TASK_RUNNING 0 2 #define TASK_INTERRUPTIBLE 1 3 #define TASK_UNINTERRUPTIBLE 2 4 #define __TASK_STOPPED 4 5 #define __TASK_TRACED 8 6 /* in tsk->exit_state */ 7 #define EXIT_DEAD 16 8 #define EXIT_ZOMBIE 32 9 #define EXIT_TRACE (EXIT_ZOMBIE | EXIT_DEAD) 10 /* in tsk->state again */ 11 #define TASK_DEAD 64 12 #define TASK_WAKEKILL 128 13 #define TASK_WAKING 256 14 #define TASK_PARKED 512 15 #define TASK_STATE_MAX 1024
这里可以看到定义了进程的相关运行状态,这里特殊的地方在于TASK_RUNNING这里将运行态和就绪态的进程都用TASK_RUNNING表示。
进程状态之间切换如下
1 pid_t pid; 2 pid_t tgid;
pid以及tpid的区别如下
http://blog.chinaunix.net/uid-26849197-id-3201487.html
简单说来,就是对于同一进程的不同线程而言pid不同,但是tgid相同
1 struct list { 2 struct list *next, *prev; 3 };
这里显示的内核进程链表的实现,内核通过这个链表实现进程间调度等等的功能。
1 struct task_struct __rcu *real_parent; /* real parent process */ 2 struct task_struct __rcu *parent; /* recipient of SIGCHLD, wait4() reports */ 3 /* 4 * children/sibling forms the list of my natural children 5 */ 6 struct list_head children; /* list of my children */ 7 struct list_head sibling; /* linkage in my parent's children list */ 8 struct task_struct *group_leader; /* threadgroup leader */
以上是进程之间父子关系的代码,通过这些代码,可以用来访问父进程以及子进程,这样在fork新进程时可以用来复制相关的数据。
1 union thread_union { 2 struct thread_info thread_info; 3 unsigned long stack[THREAD_SIZE/sizeof(long)]; 4 };
每个进程都有8kb的内存用来存放 thread_info以及进程的内核堆栈
1 * CPU-specific state of this task */ 2 struct thread_struct thread; 3 /* filesystem information */ 4 struct fs_struct *fs; 5 /* open file information */ 6 struct files_struct *files; 7 /* namespaces */ 8 struct nsproxy *nsproxy; 9 /* signal handlers */ 10 struct signal_struct *signal; 11 struct sighand_struct *sighand;
这段这是表示CPU的工作状态,以及文件系统和文件描述符的管理
1 struct mm_struct *mm, *active_mm;
这里则是进程的内存管理
2 .实验以及进程创建过程的分析
我们知道子进程用于父进程相同的堆栈数据,却拥有不同的堆栈空间,所以我们可以猜想到内核创建进程的过程中需要将父进程的数据复制到子进程当中。实际上内核也是这么处理进程复制的。进程的复制主要分为以下几步
- 复制PCB数据
- 给子进程分配内核堆栈
- 修改子进程的数据(如pid等)
- 设置返回地址(ret_from_fork)
其程序流程图如下:
为了证实以上的流程图,现利用gdb对进程进行调试,实验如下:
可以看到程序之间的相互调用正如之前程序图所描述的那样。
3 .总结
这章主要学习了子进程在内核中具体是如何创建的,可以看到子进程创建主要思想是将父进程的堆栈数据复制过来,然后进行相关的修改,从而是实现新进程的创建。这里的返回地址需要我们额外的关心,可以看到返回地址在copy_thread中被设置成ret_from_fork,跟踪ret_from_fork可以看到子进程的开始执行的地址正是系统调用的返回地址。这也解释了为什么fork可以同时返回两个值并且同时可以进行判断的原因。