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

原创
2017/01/04 14:40
阅读数 214

『阿男的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_vforksys_fork所做的事情已经没什么太大区别。

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

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

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

注意最新版的Linix内核代码中,这些地方的实现细节有所变化,但是整体设计并未改变。这些细节阿男后续会为大家再慢慢讲,我们目前要学习的是设计思想。

展开阅读全文
打赏
1
0 收藏
分享
加载中
最近学习又落下来,跟着男哥好好学习起来
2017/01/05 10:50
回复
举报
更多评论
打赏
1 评论
0 收藏
1
分享
返回顶部
顶部