Linux内核进程管理
介绍:
在Linux的内核的五⼤组成模块中,进程管理模块时很重要的⼀部分。它尽管不像内存管理、虚拟⽂件系统等模块那样复杂。也不像进程间通信模块那样条理化,但作为五⼤内核模块之中的⼀个,进程管理对我们理解内核的运作、对于我们以后的编程很重要。同⼀时候。作为五⼤组成模块中的核⼼模块。它与其它四个模块都有联系。
以下就对进程模块进⾏想写的介绍,⾸先要了解进程及其相关的概念。其次介绍进程的创建、切换、撤销等基本操作。除此之外,还给出了Linux内核是怎样对进程进⾏调度管理的。
⼀、进程及其相关概念
进程:进程能够理解为程序运⾏的⼀个实例,它包括可运⾏程序以及与其相关的系统资源,⽐⽅打开的⽂件、挂起的信号、内核内部数据、处理器状态、内存地址空间及包括全局变量的数据段等。
从内核的⾓度看,进程也能够称为任务。
进程描写叙述符:与进程相关的事情许多。⽐⽅进程的状态、进程的优先级、进程的地址空间、同意该
进程訪问的⽂件等等,Linux内核为此专门设计了⼀个类型为task_struct的结构体,称之为进程描写叙述符。进程描写叙述符中包括了内核管理进程的全部信息。能够说,仅仅要得到⼀个进程的进程描写叙述符,就能够知道⼀个进程的全部信息。
进程状态:进程描写叙述符task_struct结构体中有⼀个state字段,表⽰进程当前的所处状态。从进程的创建到进程的删除。它能够经过5种不同的状态。各⾃是可执⾏状态、可中断的等待状态、不可中断的等待状态、暂停状态、跟踪状态。
除此之外,当进程被终⽌时,还可能会变为僵死状态、僵死撤消状态。内核能够使⽤宏set_current_state(state)设置当前进程的状态,⽤set_task_state(task,state)设置某进程的状态。
进程标⽰符:进程描写叙述task_struct结构体中的pid字段能够标识唯⼀标识⼀个进程,称之为进程标识符PID。当创建⼀个新进程时,PID是依照顺序从⼩到⼤分配给新进程的。内核通过管理⼀个pidmap_array位图来表⽰当前已分配的PID和闲置的PID号。
注意:在多线程组中。全部的线程共享同样的PID。
除了进程标识符外,内核对进程的⼤部分訪问时通过进程描写叙述符指针进⾏的。
进程关系:进程之间的关系有亲属关系和⾮亲属关系。亲属关系包含⽗⼦关系和兄弟关系等。当中由t
ast_struct结构体中的parent/children/real_parent/sibling等字段描写叙述。
除了亲属关系外,还有其它关系,⽐⽅,⼀个进程是⼀个进程组或登录会话的领头进程,可能是⼀个线程组的领头进程。这些关系由group_leader/tgid/signal->pgrp等字段描写叙述。
进程资源:为了防⽌进程过度的使⽤系统资源。内核为每⼀个进程使⽤资源的数量进⾏了⼀些限制。
当中包含进程地址空间的最⼤数、进程使⽤CPU的最⼤时间、堆的最⼤值、⽂件⼤⼩的最⼤值、⽂件锁数量的最⼤值、消息队列的最⼤字节数、打开⽂件描写叙述符的最⼤数、进程拥有的页框最⼤数等。
⼆、进程的创建、切换、撤销
进程的创建:在Linux环境编程时,⼀般採⽤fork()函数来创建新的进程,当然。那是在⽤户空间的函数。它会调⽤内核中的clone()系统调⽤,由clone()函数继续调⽤do_fork()完毕进程的创建。
传统Unix系统中,创建的⼦进程复制⽗进程所拥有的资源,这样的⽅法效率低,由于⼦进程须要拷贝⽗进程的整个地址空间。
可是,⼦进程差点⼉不必读或改动⽗进程拥有的全部资源,由于⾮常多情况下,⼦进程创建后会马上
调⽤exec()⼀族的函数。并清除⽗进程细致拷贝过来的地址空间。现代Unix系统⽤三种⽅式攻克了这个问题:1、写实复制技术同意⽗⼦进程读同样的物理页。
2、轻量级进程同意⽗⼦进程共享每进程在内核的⾮常多数据结构。
3、vfork()系统调⽤创建的进程能共享⽗进程的内存地址空间,为了防⽌⽗进程重写⼦进程须要的数据。堵塞⽗进程的运⾏,⼀直到⼦进程退出或运⾏⼀个新的程序为⽌。整个进程创建过程可能涉及到例如以下函数:
fork()/vfork()/_clone----------->clone()--------->do_fork()---------->copy_process()
上⾯的创建过程结束之后,就有了处于可执⾏状态的完整的⼦进程,新的⼦进程有了PID、进程描写叙述符等各种数据结构。要想实际执⾏它,还须要调度程序把CPU交给新创建的⼦进程。
除了进程外,还有内核线程(⽤kernet_thread创建)的概念。
在Linux中。内核线程与普通进程存在下⾯两个⽅⾯的不同:
1、内核线程仅仅执⾏在内核态。⽽普通进程既能够执⾏在内核态,也可执⾏在⽤户态。
2、由于内核线程仅仅执⾏在内核态。它仅仅使⽤⼤于PAGE_OFFSET的线性地址空间。
还有⼀⽅⾯。⽆论在⽤户态还是在内核态,普通进程能够⽤4GB的线性地址空间。
撤销进程:进程终⽌后,须要通知内核以便内核释放进程所拥有的资源。包含内存、打开⽂件以及其它资源。如信号量。进程终⽌的⼀般⽅式是调⽤exit()库函数,该函数释放C函数库所分配的资源,运⾏编程者所注冊的每⼀个函数,并结束从系统回收进程的那个系统调⽤。
除了进程⾃⼰终⽌⾃⼰外。内核能够有选择地强迫整个线程组死掉。这发⽣在:当进程接收到⼀个不能处理或忽视的信号时,或者当内核正在代表进程执⾏时再内核态产⽣⼀个不可恢复的CPU异常时。
有两个终⽌⽤户态应⽤的系统调⽤:exit_group()系统调⽤,它终⽌整个线程组,即整个基于多线程的应⽤。
do_group_exit()是实现这个系统调⽤的主要内核函数。
exit()系统调⽤,它终⽌⼀个线程,⽽⽆论该线程所属线程组中的全部其它进程。do_exit()是实现这个系统调⽤的主要内核函数。
进程切换:进程切换⼜称为任务切换、上下⽂切换。它是这样⼀种⾏为,为了控制进程的运⾏,内核挂起当前在CPU上运⾏的进程。并恢复曾经挂起的某个进程的运⾏。
跟函数的调⽤类似。进程切换时。⼀般要在CPU上装载要运⾏进程的进程上下⽂。
进程的硬件上下⽂指:可运⾏程序上下⽂的⼀个⼦集,是进程恢复运⾏前装⼊寄存器的⼀组数据。
当中⼀部分放在TSS段。即任务状态段。剩余部分存放在内核态堆栈中。
linux内核设计与实现 pdf进程的切换仅仅发⽣在内核态,在运⾏进程切换之前。⽤户态进程使⽤的全部寄存器内容都已保存在内核态堆栈上。
进程的切换有两种⽅法,⼀种是硬件切换,⼀种是软件切换。软件切换就是利⽤程序逐步运⾏切换,它的长处是。能够对切换时装⼊的数据进⾏合法性检查,运⾏时间虽与硬件切换⼤致同样,但仍有可改进的地⽅。
进程切换使⽤schedule()函数完毕,在本质上。每⼀个进程切换由两部分组成:1、切换页全局⽂件夹以安装⼀个空间。2、切换内核态堆栈和硬件上下⽂,由于硬件上下⽂提供了内核运⾏新进程所须要的全部信息,包含CPU寄存器。主要有switch_to函数完毕。
三、进程调度
调度策略:调度策略就是这样⼀组规则:决定什么时候以如何的⽅式选择⼀个新进程执⾏的规则。Linux的调度基于分时技术:多个进程以“时间多路复⽤”⽅式执⾏,由于CPU的时间被分成“⽚”。给每⼀个可执⾏进程分配⼀⽚。
调度策略也是依据进程的优先级对它们进⾏分类。在Linux中,进程的优先级是动态的。
调度程序跟踪进程正在做什么,并周期性地调整它们的优先级。依据不同的分类标准,能够把进程分成不同的类型。
⽐⽅能够把⼀个进程看作是“I/O受限”或“CPU受限”。也可把进程区分为下⾯三类:交互式进程、批处理进程、实时进程。Linux的进程是抢占式的,不管是处于内核态还是⽤户态。时间⽚的长短对系统性能是⾮常关键的:它既不能太长也不能太短。假设平均时间⽚太短。由进程切换引起的系统额外开销就变得⾮常⾼。假设平均时间⽚太长。进程看起来就不再是并发执⾏的。对时间⽚⼤⼩的选择始终是⼀种折中。
Linux採⽤单凭经验的⽅法,即选择尽可能长、同⼀时候能保持良好响应时间的⼀个时间⽚。
调度算法:早起的Linux中,调度算法是依据进程的优先级选择“最佳”进程来执⾏,它的缺点是时间开销与“可执⾏进程数量”有关。
现代的Linux中,调度算法能够在固定时间内(与可执⾏进程数量⽆关)选中要执⾏的进程。⾸先,我们必须知道进程能够分为实时进程与普通进程。每⼀个LInux进程总是依照例如以下的调度类型被调度:先进先出的实时进程、时间⽚轮转的实时进程、普通的分时进程。
调度算法依据进程是普通进程还是实时进程⽽有⾮常⼤不同。
普通进程的调度:每⼀个普通进程都有它⾃⼰的静态优先级(值是从100到139)。调度程序使⽤静态优先级来估价系统中这个进程与其它普通进程之间调度的程度。
静态优先级决定进程的基本时间⽚。即进程⽤完了曾经的时间⽚时。系统分配给进程的时间⽚长度。普通进程除了静态优先级,还有动态优先级。
动态优先级是调度程序在选择新进程来执⾏的时候使⽤的数。平均睡眠时间是进程在睡眠状态所消耗的平均纳秒数。即使具有较⾼静态优先级的普通进程获得了较⼤的CPU时间⽚,也不应该使静态优先级较低的进程⽆法执⾏。为了避免这个问题,提出了活动进程和过期进程的概念。活动进程指进程的时间⽚还未⽤完。过期进程指进程的时间⽚以⽤完,即使过期进程的优先级更⾼,也不能继续执⾏。除⾮等到全部活动进程都过期以后。
实时进程的调度:每⼀个实时进程都与⼀个试试优先级相关,实时优先级是⼀个范围从1到99的值。
跟普通进程不同,实时进程总是被当作活动进程。
调度程序所使⽤的主要数据结构:数据结构runqueue和进程描写叙述符
数据结构runqueue:runqueue数据结构中最重要的字段是与可执⾏进程的链表相关的字段。当中的arrays字段是活动进程和过期进程的两个集合,active字段是指向活动进程链表的指针,expired字段是指向过期进程链表的指针。
进程描写叙述符:每⼀个进程描写叙述符都包含⼏个与调度相关的字段。当中的time_slice字段是在进程的时间⽚中还剩余的时钟节拍数。它由copy_process函数设置:⽗进程的剩余节拍数被划分为两等分,⼀份给⽗进程,⼀份给⼦进程。
四、调度程序所使⽤的函数
调度程序依靠⼏个函数来完毕调度⼯作。当中最重要的函数例如以下:
try_to_wake_up()函数通过把进程状态设置为TASK_RUNNING,并把该进程插⼊本地CPU的执⾏队列来唤醒睡眠或停⽌的进程。
recalc_task_prio()函数更新进程的平均睡眠时间和动态优先级。
schedule()憾事实现调度程序。它的任务时从执⾏队列的链表中到⼀个进程,并随后将CPU分配给这个进程。schedule()能够由⼏个内核控制路径调⽤。能够採⽤直接调⽤或延迟调⽤的⽅式
总结:
Linux内核中的进程管理模块很重要,它是连接其它4⼤模块的重要桥梁,它也很复杂,理解它的⼀些基本原理,对于理解Linux内核很重要。上⾯仅仅是对它进⾏了⼀些简单的描写叙述,它没有深⼊到具体的实施细节,我们希望能有机会到已经能够实现在深⼊分析的细节。
版权声明:本⽂博客原创⽂章。博客,未经同意,不得转载。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论