Linux 进程控制——进程调度

一、进程的特点

    1、有一段程序供其执行

    2、有进程专用的系统空间堆栈

    3、有一个 task_struct 数据结构记录进程的信息

    4、独立的存储空间,意味着除了专用的系统空间堆栈外还要有用户空间堆栈

    如果以上条件,只是缺少用户空间堆栈,完全没有被称为“内核线程”,共享用户空间被成为“用户线程”

    Linux 系统中,进程 process 和 任务 task 是同一个意思,因此描述进程的结构体叫 task_struct 。

    每一个进程都有一个 task_struct 结构 和 一片用作系统堆栈的存储空间,内核在为每一个进程分配一个 task_struct 时,实际上分配两个连续的物理页面 8192 字节。(使用 alloc_task_struct 分配)


    系统堆栈不像用户空间那样可以在运行时扩展,而是静态确定的,因此在中断服务程序、内核软中断服务程序、其它设备驱动程序中,不应让函数嵌套太深、不应有较大较多的局部变量。

    宏:current 返回当前进程的 task_struct 结构体指针。


二、task_struct 中关于进程调度的成员


    1、task_struct.state 

       TASK_RUNNING,并不是表示一个进程正在执行(占有CPU),而是表示这个程序处于就绪太,位于“运行队列”,可以被调度器调度执行。

        TASK_UNINTERRUPTIBLE 和 TASK_INTERRUPTIBLE ,表示进程处于休眠或将要进入休眠状态,不同的是 TASK_UNINTERRUPTIBLE 的休眠可以被信号所打断唤醒。

        TASK_ZOMBIE,表示进程 exit 而未 注销 task_struct

        TASK_STOPPED,主要用于调试

    2、task_struct.counter

         与调度有关

    3、task_struct.need_resched

         与调度有关

    4、task_struct.priority 和 task_struct.rt_priority 

        优先级别和“实时”优先级别

    5、task_struct.pid 

        进程号

    6、task_struct.policy

        适用于本进程的调度策略


三、关于进程调度的三个问题

    1、调度的时机:在什么情况下调度,什么时候调度

    2、调度的策略:根据什么准则挑选下一个运行的进程

    3、调度的方式:“抢占” 还是 “不可抢占”


首先来回答第一个,调度时机:

    自愿调度:随时都可以进行,在内核里,一个进程可以通过 schedule() 启动一次调度。当然也可以在 schedule() 之前,将当前进程的状态设置成 TASK_UNINTERRUPTIBLE 、TASK_INTERRUPTIBLE ,暂时放弃运行而睡眠,也可以给这种自愿放弃运行加上一个时间限制,在内核中用 schedule_timeout(),时间到达自动唤醒。

    非自愿调度:非自愿的调度,即强制地发生在每次由系统调用返回的前夕,以及每次从中断或异常处理函数返回的前夕。这意味着,只有在用户空间(CPU在用户空间运行)时发生的中断或异常才会引起调度。

    注意:在用户空间(CPU在用户空间运行)时发生的中断或异常才会引起调度只是必要条件,还需要满足 task_struct.need_resched 非 0 ,后面再说


第二个问题,调度策略:

    内核有三种不同的调度策略,每一个进程都可以选择一个作为自己的调度策略。

    SCHED_FIFO ,适用于“实时”进程,且每次运行时间较短的进程。

    SCHED_RR ,适用于每次运行时间较长的“实时”进程

    SCHED_OTHER , 传统的调度策略,适用于“普通”进程


SCHED_FIFO:

    进程一旦收到调度开始运行之后,就要自愿让出,或者被高“资格”的进程抢占而停止,对于每次运行时间较短的进程使用 SCHED_FIFO 是恰当的。

    如果有一个进程使用 SCHED_FIFO 运行起来没完没了会怎么样呢,高“资格”的当然可以抢占它,低“资格”的需要等待,但是对于同等“资格”运行时间较短的进程是不公平的。这就好比,有一台公共电脑,大家每人用个3、5分钟百度个问题是合适的,但是有个人非要拿它来看电影,那么对于那些只用3、5分钟的人来说就是不公平的。


SCHED_RR:

    这种调度算法就是针对上面所说运行时间较长的进程。SCHED_RR 在 SCHED_FIFO 的基础上,通过 task_struct.counter “时间配额”,来实现一种同“资格”的轮换调度。什么意思呢,对于 SCHED_RR 的进程,通过task_struct.nice 计算出一个初始的“时间配额”给 task_struct.counter ,task_struct.counter 会在每次的时钟中断的处理函数里减少,减少到 0 的时候,task_struct.need_resched 被置 1 ,且该进程就会被移动到“运行队列”的尾部,当返回用户空间的时候自然就要进行调度了。调度器的规则是按照进程在“运行队列”的顺序,比较进程的“资格”,相同资格的,排在前面的先调度。那么刚刚那个被移动到“运行队列”尾部的进程就要等到前面所有同“资格”的进程运行一遍从而被放在它后边它才有机会再次运行。当然,这里只是“时间配额”减少,进程在“运行队列”里位置的移动,对于“资格”来说是不变的,因此低“资格”的进程还是没有机会运行,除非“运行队列”里高“资格”的进程都去睡眠了~

    还是打个比方来理解一下,这里有1台电脑,大家都可以用,老师具有高“资格”可以抢电脑,同学们的“资格”都一样。同学们有的用电脑看电影,一个电影2H,有的听歌10min,有的百度3mini,大家完全运行的时间不一样可能偏差很大。那么电脑管理员说了,看电影的同学,电影虽然2个小时,但是你每次只能看5分钟,看完到队伍后面去排队,轮到你再继续看。听歌的也是,每次听5分钟。百度的,每次3分钟,百度完去后面排队。这样,给各个“同资格”同学们分配一个差不多的“时间配额”,保证了公平。


SCHED_OTHER:

    这种调度算法适用于普通进程,它们的“资格”很低很低,低到只要有“实时”进程在,它们就永远没有运行的可能。为什么这么说?

    对于实时进程,它们的“资格”=1000+task_struct.pt_priority , 0 <= pt_priority <= 99,因此实时进程的“资格”至少也是1000,而且,它们的“资格”不会随时间增长而降低。

    对于普通进程,它们的“资格”跟 task_struct.nice 和 task_struct.counter 有关系,我们前面说了 task_struct.counter 决定了“时间配额”会随着时钟滴答而减少,在 task_struct.counter 未减少到 0时,“资格”= counter += 20 - nice 。nice (-20 ~ 19),因此一个普通进程的“资格”会随时间滴答而降低。还有,在 task_struct.counter 减少到 0时,它的“资格”直接为 0 。这里先透露一下,当普通进程的“资格”降低为0,它的 task_struct.need_resched 被置 1 ,因此会发生抢占。

     

第三个问题,调度的方式

    前边,我们再说调度时机的时候,提到了自愿调度和非自愿调度,非自愿调度那必然是抢占了。只不过这个抢占是有条件的。在用户空间(CPU在用户空间运行)时发生的中断或异常才会引起调度只是必要条件,还需要满足 task_struct.need_resched 非 0 。

    

自愿调度:

    随时都可以进行,在内核里,一个进程可以通过 schedule() 启动一次调度。当然也可以在 schedule() 之前,将当前进程的状态设置成 TASK_UNINTERRUPTIBLE 、TASK_INTERRUPTIBLE ,暂时放弃运行而睡眠,也可以给这种自愿放弃运行加上一个时间限制,在内核中用 schedule_timeout(),时间到达自动唤醒。

    如果当前进程的状态被设置为:TASK_INTERRUPTERABLE , 在 schdule() 时,schdule() 函数会判断当前进程是否有信号要处理,如果有,把状态改回 TASK_RUNNING ,继续去处理信号。如果没有信号要处理,那么移除“运行队列”,休眠,调度器挑选下一个“资格”最高的运行。

    如果当前进程的状态被设置为:TASK_UNINTERRUPTERABLE ,直接移除“运行队列”,休眠,调度器挑选下一个“资格”最高的运行。

    如果当前进程的状态被设置为:TASK_RUNNING ,有意思,想让出 CPU ,但是你还想在“运行队列”里有机会被调度。这让跟不让一个样啊...为啥这么说呢

    当前进程状态 TASK_RUNNING ,然后又 schdule(),如果没有更高“资格”的进程等待执行,那么调度器会满足这个进程继续执行的意愿,让它继续执行。(不管在运行队列的位置是否较同“资格”进程靠前)。如果有资格较高的进程出现,那么在返回用户空间的时候会发生抢占,你不让也是不行的。

     也就是说,当前进程状态 TASK_RUNNING ,然后又 schdule() 唯一有意义的一点是,使突然唤醒的高“资格”进程立刻执行,无需等到返回用户空间时抢占。


强制性调度(抢占):

    当前进程为实时进程:

        实时进程的“资格”不会因为时钟滴答而降低,因此也就不会出现想普通进程那样随时钟滴答降低,降低为 0 时,need_resched 被置 1 的情况。

        但是,如果有一个进程突然唤醒,那么会比较它俩的“资格”,如果新唤醒的进程“资格”更高,当前进程的 need_resched 被置 1  ,返回用户空间时发生抢占。

    当前进程为普通进程:

        前面提到了,普通进程的“资格”会随时钟滴答而降低,而且当它的时间配额为 0 ,“资格”会被设成 0,那么 need_resched 会被置 1 ,返回用户空间时发生抢占。

        如果有一个进程唤醒,和实时进程是一样的。

    抢占就是当前进程状态为 TASK_RUNNING 然后被迫 schdule(),只不过要么当前进程“资格”降低了,要么有高“资格”出现了,不会满足这个进程继续执行的意愿。


本文大多数内容来自:《Linux内核源代码情景分析》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值