您的位置:首页 > 运维架构 > Linux

『阿男的Linux内核世界』*11 Process和Thread的设计*

2017-01-05 10:50 375 查看
『阿男的Linux内核世界』*11 Process和Thread的设计*

阿男给大家在这篇文章里面讲解一下Linux针对Process和Thread的设计。

我们在学习操作系统设计的时候,知道了Process是一种比较"重"的设计:每一个Process都拥有独立的内存空间,独立的生命周期;同时我们知道了Thread是相比较而言比较轻的资源:多个Threads运行在一个Process之内,共享这个Process的内存资源和CPU资源,但是各个Thread可以有自己的代码执行权力,每一个Thead可以运行在不同的代码位置。

其实我们可以看到,Linux的内核发展到今天,对于运行在上面的程序,早已经"虚拟化"了:每一个程序都拥有自以为独立的内存空间,拥有自以为独立的CPU资源,而操作系统负责管理实际的硬件资源,然后把这些资源分配给各个Processes,并且负责调度这些Processes的执行顺序并且管理它们的运行状态。

在Process和Thread的实现方面,Linux Kernel并没有做过多区分,而是用统一的
task_struct
来描述:



(图片来自
Linux Kernel Development


task_struct
的定义位于
include/linux/sched.h
^1,阿男在注解里给出了链接,大家可以自行查看一下。可以看到这个struct里面的内容非常丰富,包含了process方方面面的信息,比如:

1274/* signal handlers */
1275        struct signal_struct *signal;
1276        struct sighand_struct *sighand;

上面是这个
task_struct
所包含的signal handler信息。还有:

1174/* task state */
1176        int exit_state;
1177        int exit_code, exit_signal;
1184        pid_t pid;
1185        pid_t tgid;
1186

上面包含了这个
task_struct
的很多状态信息,比如
exit_code
,还有
pid
,这些我们比较熟悉的内容。

此外,
task_struct
之间的继承关系的信息当然也会有:

1189
1190        /*
1191         * pointers to (original) parent process, youngest child, younger sibling,
1192         * older sibling, respectively.  (p->father can be replaced with
1193         * p->real_parent->pid)
1194         */
1195        struct task_struct *real_parent; /* real parent process */
1196        struct task_struct *parent; /* recipient of SIGCHLD, wait4() reports */
1197        /*
1198         * children/sibling forms the list of my natural children
1199         */
1200        struct list_head children;      /* list of my children */
1201        struct list_head sibling;       /* linkage in my parent's children list */
1202        struct task_struct *group_leader;       /* threadgroup leader */

为什么在Linux Kernel里面,Thread和Process共用一个
task_struct
呢?阿男觉得这样的设计是非常好的,因为Thread和Process的区别本来就没有那么大,只不过很多时候可能Thread就是一个"更轻"的Process,不具有Process那么多的独立信息。因此Thread可能只需要用到一部分
task_struct
里面的元素就好,但没有必要为了Thread定义一个重复的struct。

此时,Kernel只需要在
task_struct
里面通过flag来区分一下这个
task_struct
对应的是Process和Thread就好,然后就可以针对不同的情况作不同的管理。

实际上Linux Kernel可以支持定义的
task
远远要比Process和Thread这两种概念的颗粒度要更细。大家可以看一下
sched.h
在一开始的各种flag定义[2]:



通过上面各种flag的组合,我们可以微调各种创建出来的
task
,这些
task
在资源使用方面的区别远远要比Process和Thread更细致。比如
vfork()
函数,其实就是通过这些flag的组合来创建一个
task_struct
^3

249  */
250 int sys_vfork(struct pt_regs *regs)
251 {
252         return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs->sp, regs, 0,
253                        NULL, NULL);
254 }

从上面的代码可以看到,
vfork
就是
sys_vfork
的wrapper,而
sys_vfork
则是调用
do_fork
,传递的flag是
CLONE_VFORK | CLONE_VM | SIGCHLD


vfork
其实是一个已经没什么太大意义的函数,它的历史作用是创建一个新的process但是不拷贝caller process的内存空间,这个函数是为了child process执行
exec()
函数让自己执行全新的程序而用,这样的情况下拷贝parent process的内存空间就毫无意义了。

但是新的Linux Kernel早已经对
do_fork
的实现做了大量优化,使用了copy-on-write技术,也就是说你就算调用
fork()
函数,新建立的process也不会马上拷贝parent的内存空间。

下面是
fork
的系统调用
sys_fork
的代码:

235 int sys_fork(struct pt_regs *regs)
236 {
237         return do_fork(SIGCHLD, regs->sp, regs, 0, NULL, NULL);
238 }

可以看到也是调用
do_fork
,只是传递的参数不太一样。至于
do_fork
内部怎么处理,那就是Kernel的事情,阿男刚才说过,实际上
sys_vfork
sys_fork
所做的事情已经没什么太大区别。

最后我们看创建thread所用的system call
clone
,实际上也是通过不同的flag来定制
task_struct


clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0);

而clone实际上也是
do_fork()
的一个wrapper。

注意最新版的Linix内核代码中,这些地方的实现细节有所变化,但是整体设计并未改变。这些细节阿男后续会为大家再慢慢讲,我们目前要学习的是设计思想。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息