进程在Linux内核中的角色扮演
在Linux内核中,内核将进程、线程和内核线程一视同仁,即内核使用唯一的数据结构task_struct来分别表示他们;内核使用相同的调度算法对这三者进行调度;并且内核也使用同一个函数do_fork()来分别创建这三种执行线程(thread of execution)。执行线程通常是指任何正在执行的代码实例,比如一个内核线程,一个中断处理程序或一个进入内核的进程。
这样处理无疑是简洁方便的,并且内核在统一处理这三者之余并没有失去他们本身所具有的特性。本文将结合进程、线程和内核线程的特性浅谈进程在内核中的角色扮演问题。
1.进程描述符task_struct的多角色扮演
上述三种执行线程在内核中都使用统一的数据结构task_struct来表示。task_struct结构即所谓的进程描述符,它包含了与一个进程相关的所有信息。进程描述符中不仅包含了许多描述进程属性的字段,而且还有一系列指向其他数据结构的指针。下面将简单介绍进程描述符中几个比较特殊的字段,它们分别指向代表进程所拥有的资源的数据结构。
mm字段:指向mm_struct结构的指针,该类型用来描述进程整个的虚拟地址空间。
fs字段:指向fs_struct结构的指针,该类型用来描述进程所在文件系统的根目录和当前进程所在的目录信息。
files字段:指向files_struct结构的指针,该类型用来描述当前进程所打开文件的信息。
signal字段:指向signal_struct结构(信号描述符)的指针,该类型用来描述进程所能处理的信号。
对于普通进程来说,上述字段分别指向具体的数据结构以表示该进程所拥有的资源。
对应每个线程而言,内核通过轻量级进程与其进行关联。轻量级进程之所轻量,是因为它与其他进程共享上述所提及的进程资源。比如进程A创建了线程B,则B线程会在内核中对应一个轻量级进程。这个轻量级进程很自然的对应一个进程描述符,只不过B线程的进程描述符中的某些代表资源指针会和A进程中对应的字段指向同一个数据结构,这样就实现了多线程之间的资源共享。
由于内核线程只运行在内核态,并且只能由其他内核线程创建,所以内核线程并不需要和普通进程那样的独立地址空间。因此内核线程的进程描述符中的mm指针即为NULL。内核线程是否共享父内核线程的某些资源,则通过向内核线程创建函数kernel_thread()传递参数来决定。
通过上面的分析可以发现,内核中使用统一的进程描述符来表示进程、线程和内核线程,根据他们不同的特性,其进程描述符中某些代表资源的字段的指向会有所不同,以实现扮演不同角色。
2. do_fork()的多角色扮演
进程、线程以及内核线程都有对应的创建函数,不过这三者所对应的创建函数最终在内核都是由do_fork()进行创建的,具体的调用关系图如下:
从图中可以看出,内核中创建进程的核心函数即为看do_fork(),该函数的原型如下:
long do_fork(unsigned long clone_flags,
unsigned long stack_start,
struct pt_regs *regs,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr)
该函数的参数个数是固定的,每个参数的功能如下:
clone_flags:代表进程各种特性的标志。低字节指定子进程结束时发送给父进程的信号代码,一般为SIGCHLD信号,剩余三个字节是若干个标志或运算的结果。
stack_start:子进程用户态堆栈的指针,该参数会被赋值给子进程的esp寄存器。
regs:指向通用寄存器值的指针,当进程从用户态切换到内核态时通用寄存器中的值会被保存到内核态堆栈中。
stack_size:未被使用,默认值为0。
parent_tidptr:该子进程的父进程用户态变量的地址,仅当CLONE_PARENT_SETTID被设置时有效。
child_tidptr:该子进程用户态变量的地址,仅当CLONE_CHILD_SETTID被设置时有效。
既然进程、线程和内核线程在内核中都是通过do_fork()完成创建的,那么do_fork()是如何体现其功能的多样性?其实,clone_flags参数在这里起到了关键作用,通过选取不同的标志,从而保证了do_fork()函数实现多角色——创建进程、线程和内核线程——功能的实现。clone_flags参数可取的标志很多,下面只介绍几个与本文相关的标志。
CLONE_VIM:子进程共享父进程内存描述符和所有的页表。
CLONE_FS:子进程共享父进程所在文件系统的根目录和当前工作目录。
CLONE_FILES:子进程共享父进程打开的文件。
CLONE_SIGHAND:子进程共享父进程的信号处理程序、阻塞信号和挂起的信号。使用该标志必须同时设置CLONE_VM标志。
如果创建子进程时设置了上述标志,那么子进程会共享这些标志所代表的父进程资源。
2.1 进程的创建
在用户态程序中,可以通过fork()、vfork()和clone()三个接口函数创建进程,这三个函数在库中分别对应同名的系统调用。系统调用函数通过128号软中断进入内核后,会调用相应的系统调用服务例程。这三个函数对应的服务历程分别是sys_fork()、sys_vfork()和sys_clone()。
int sys_fork(struct pt_regs *regs)
{
return do_fork(SIGCHLD, regs->sp, regs, 0, NULL, NULL);
}
int sys_vfork(struct pt_regs *regs)
{
return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs->sp, regs, 0,
NULL, NULL);
}
long sys_clone(unsigned long clone_flags, unsigned long newsp,
void __user *parent_tid, void __user *child_tid, struct pt_regs *regs)
{
if (!newsp)
newsp = regs->sp;
return do_fork(clone_flags, newsp, regs, 0, parent_tid, child_tid);
}
通过上述系统调用服务例程的源码可以发现,三个服务历程内部都调用了do_fork(),只不过差别在于第一个参数所传的值不同。这也正好导致由这三个进程创建函数所创建的进程有不同的特性。下面对每种进程作以简单说明。
fork():由于do_fork()中clone_flags参数除了子进程结束时返回给父进程的SIGCHLD信号外并无其他特性标志,因此由fork()创建的进程不会共享父进程的任何资源。子进程会完全复制父进程的资源,也就是说父子进程相对独立。不过由于写时复制技术(Copy On Write,COW)的引入,子进程可以只读父进程的物理页,只有当两者之一去写某个物理页时,内核此时才会将这个页的内容拷贝到一个新的物理页,并把这个新的物理页分配给正在写的进程。
vfork():do_fork()中的clone_flags使用了CLONE_VFORK和CLONE_VM两个标志。CLONE_VFORK标志使得子进程先于父进程执行,父进程会阻塞到子进程结束或执行新的程序。CLONE_VM标志使得子进程共享父进程的内存地址空间(父进程的页表项除外)。在COW技术引入之前,vfork()适用子进程形成后立马执行execv()的情形。因此,vfork()现如今已经没有特别的使用之处,因为写实复制技术完全可以取代它创建进程时所带来的高效性。
clone():clone通常用于创建轻量级进程。通过传递不同的标志可以对父子进程之间数据的共享和复制作精确的控制,一般flags的取值为CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND。由上述标志可以看到,轻量级进程通常共享父进程的内存地址空间、父进程所在文件系统的根目录以及工作目录信息、父进程当前打开的文件以及父进程所拥有的信号处理函数。
2.2 线程的创建
每个线程在内核中对应一个轻量级进程,两者的关联是通过线程库完成的。因此通过pthread_create()创建的线程最终在内核中是通过clone()完成创建的,而clone()最终调用do_fork()。
2.3 内核线程的创建
一个新内核线程的创建是通过在现有的内核线程中使用kernel_thread()而创建的,其本质也是向do_fork()提供特定的flags标志而创建的。
int kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
{
/*some register operations*/
return do_fork(flags | CLONE_VM | CLONE_UNTRACED, 0, ®s, 0, NULL, NULL);
}
从上面的组合的flag可以看出,新的内核线程至少会共享父内核线程的内存地址空间。这样做其实是为了避免赋值调用线程的页表,因为内核线程无论如何都不会访问用户地址空间。CLONE_UNTRACED标志保证内核线程不会被任何进程所跟踪,
3. 进程的调度
由于进程、线程和内核线程使用统一数据结构来表示,因此内核对这三者并不作区分,也不会为其中某一个设立单独的调度算法。内核将这三者一视同仁,进行统一的调度。
参考资料:
1. 深入理解Linux内核
2. Linux内核设计与实现
进程在Linux内核中的角色扮演的更多相关文章
- 向linux内核中添加外部中断驱动模块
本文主要介绍外部中断驱动模块的编写,包括:1.linux模块的框架及混杂设备的注册.卸载.操作函数集.2.中断的申请及释放.3.等待队列的使用.4.工作队列的使用.5.定时器的使用.6.向linux内 ...
- KSM剖析——Linux 内核中的内存去耦合
简介: 作为一个系统管理程序(hypervisor),Linux® 有几个创新,2.6.32 内核中一个有趣的变化是 KSM(Kernel Samepage Merging) 允许这个系统管理程序通 ...
- 【转】 Linux内核中读写文件数据的方法--不错
原文网址:http://blog.csdn.net/tommy_wxie/article/details/8193954 Linux内核中读写文件数据的方法 有时候需要在Linuxkernel--大 ...
- 【转】在linux内核中读写文件 -- 不错
原文网址:http://blog.csdn.net/tommy_wxie/article/details/8194276 1. 序曲 在用户态,读写文件可以通过read和write这两个系统调用来完成 ...
- Linux内核中的算法和数据结构
算法和数据结构纷繁复杂,但是对于Linux Kernel开发人员来说重点了解Linux内核中使用到的算法和数据结构很有必要. 在一个国外问答平台stackexchange.com的Theoretica ...
- Linux内核中的软中断、tasklet和工作队列具体解释
[TOC] 本文基于Linux2.6.32内核版本号. 引言 软中断.tasklet和工作队列并非Linux内核中一直存在的机制,而是由更早版本号的内核中的"下半部"(bottom ...
- (笔记)Linux内核中内存相关的操作函数
linux内核中内存相关的操作函数 1.kmalloc()/kfree() static __always_inline void *kmalloc(size_t size, gfp_t flags) ...
- Linux内核中锁机制之RCU、大内核锁
在上篇博文中笔者分析了关于完成量和互斥量的使用以及一些经典的问题,下面笔者将在本篇博文中重点分析有关RCU机制的相关内容以及介绍目前已被淘汰出内核的大内核锁(BKL).文章的最后对<大话Linu ...
- Linux内核中锁机制之完成量、互斥量
在上一篇博文中笔者分析了关于信号量.读写信号量的使用及源码实现,接下来本篇博文将讨论有关完成量和互斥量的使用和一些经典问题. 八.完成量 下面讨论完成量的内容,首先需明确完成量表示为一个执行单元需要等 ...
随机推荐
- 如何通过js关闭微信浏览器页面
WeixinJSBridge.call('closeWindow'); jssdk wx.closeWindow(); WeixinJSBridge对象还提供了哪些功能: WeixinJSBridge ...
- Linux系统多网卡绑定实战
导读 对于服务器来说,网络设备的稳定也是比较重要的,特别是网卡.在生产型的系统中,网卡的可靠性就更为重要了. 多块网卡绑定到一个IP地址,当一块网卡发生物理性损坏的情况下,另一块网卡自动启用,并提供正 ...
- 用Java发送HTML格式邮件测试类(支持中文)
代码由纯Java写成,支持中文,一目了然,只要将Main函数中的相关信息填写正确就直接用了,便于修改,可以在此类基础上任意扩展成自己的类. 注意做HTML形式的邮件,最好把HTML,CSS都写全,只写 ...
- (剑指Offer)面试题61:按之字形顺序打印二叉树
题目: 请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推. 思路: 按照广度优先遍历来遍历二叉树,但是需要 ...
- JavaScript 之 截取字符串函数
一.函数:split() 功能:使用一个指定的分隔符把一个字符串分割存储到数组 例子: str=”jpg|bmp|gif|ico|png”; arr=theString.split(”|”); //a ...
- 在MyEclipse中设置jsp页面为默认utf-8编码(转)
http://www.cnblogs.com/xdp-gacl/p/3496161.html 在MyEclispe中创建Jsp页面,Jsp页面的默认编码是“ISO-8859-1”,如下图所示: 在这种 ...
- 简单分页查询(web基础学习笔记十三)
一.建立资源文件和工具类 1.1 .database.properties jdbc.driver_class=oracle.jdbc.driver.OracleDriver jdbc.connect ...
- web应用程序访问串口
https://github.com/tylermenezes/SerialServe https://github.com/straend/SerialWebsocket http://www.cn ...
- eclipse使用egit插件
本来想用myeclipse,奈何试过网上所列的常用方法,都无法成功安装egit插件.只得转到eclipse.话说eclipse不仅是免费的,启动也较myeclipse更为迅速,安装插件也非常顺利.使用 ...
- U872-结算成本处理步骤及索引处理
U872每月都须要做月结,对于制造企业来说,结算成本处理是不可缺少的一个处理环节,每次查询出来待暂估记录也比較多(我接触到的有3万左右),暂估时间一般要2-3小时左右,若调用的大表索引碎片多时,会须要 ...