一、I/O消耗型和处理器消耗型进程
进程可以被分为I/O消耗型和处理器消耗型。前者指进程的大部分时间用来提交I/O请求或是等待I/O请求。因此,这样的进程经常处于可运行状态,但通常都是运行短短的一会儿,因为它在等待更多的I/O请求时最后总会阻塞。相反,处理器消耗型进程把时间大多用在执行代码上。除非被抢占,否则它通常一直不停的运行,因为它们没有太多的I/O请求。当然,这种划分 方式并不是绝对的,进程可以同时展示这两种行为。
二、linux进程优先级
linux采用两种不同的优先级范围。
第一种是nice值。nice值的范围从-20到+19,默认值为0。nice值越大,优先级越低。在linux系统中nice值代表时间片的比例。
第二种是实时优先级。实时优先级的范围是0--99。与nice相反,实时优先级数值越大,进程优先级越高。
需要注意的是,nice值用于表示普通进程的优先级,实时优先级表示实时进城的优先级。任何实时进程的优先级都高于普通进程。实时优先级和nice优先级处于互不相交的两个范围。
三、时间片
时间片是一个数值,它表示进程被抢占前所能运行的最大时间。
I/O消耗型不需要太长的时间片,而处理器消耗型则希望时间片越长越好。
linux的CFS调度器并没有直接分配时间片到进程。进程所获得的处理器时间跟系统负载密切相关。nice作为权重将调整进程所使用的处理器时间比。
四、linux调度算法
linux调度器是以模块方式提供的,这样做的目的是允许不同类型的进程可以有针对性的选择调度算法,这种模块化结构被称为调度器类,它允许多种不同的可动态添加的调度算法存在。即:
linux允许多种不同的可动态添加的调度算法并存;
linux允许不同类型的进程可以有针对性的选择调度算法。
完全公平调度(CFS)是一个针对普通进程的调度类。CFS调度算法的核心是选择具有最小vruntime的进程。
CFS的出发点是基于一个简单的理念:进程调度的效果应该如同系统具备一个理想中的完美多任务处理器。在这种系统中,每个进程能够获得1/n的处理器时间--n是指可运行进程的数量。
nice值在CFS中被作为进程获得的处理器运行比的权重:越高的nice值(越低的优先级)进程获得更低的处理器使用权重。每个进程都按其权重在全部可运行进程中所占比例的时间片来运行。
CFS为每个进程引入获得时间片的最小值,这个最小值称为最小粒度。默认情况下这个值是1ms。如此一来,即便是可运行进程趋于无限大,每个进程最少也能获得1ms的运行时间。确保切换消耗被限制在一定范围内。
五、时间记账
所有的调度器都必须对进程运行时间做记账。struct sched_entity结构中的vruntime变量存放进程运行的虚拟时间。虚拟时间以ns为 单位,所以vruntime和定时器节拍不再想关。虚拟运行时间可以帮助我们逼近CFS模型所追求的理想多任务处理器。因此,CFS用vruntime来记录一个进程到底运行了多长时间以及它还再运行多久。
当CFS需要选择下一个运行进程时,它会挑一个具有最小vruntime的进程。这其实就是CFS调度算法的核心:
选择具有最小vruntime的进程。
CFS使用红黑树来组织可运行进程队列,并利用其迅速找到具有最小vruntime值的进程。CFS的进程选择算法也可以简单总结为:
运行rbtree树中最左边叶子节点所代表的那个进程。
六、睡眠和唤醒
休眠(被阻塞)的进程处于一种特殊的不可执行状态。进程的休眠有多种原因,但肯定都是为了等待一些事件。无论是等待那种事件,内核的操作都相同:进程把自己标记成休眠状态,从可执行红黑树中移出,放入等待队列,然后调用schedule选择和执行一个其他的进程。
唤醒的过程刚好相反:进程被设置为可执行状态,然后再从等待队列中移到可执行红黑树中。由此可见,休眠的进程被唤醒后,不一定立即会被执行,需要等待内核的调度。唤醒只是将进程从等待队列移到可执行红黑树中。
休眠有2中相关的状态:TASK_INTERRUPTIBLE、TASK_UNINTERRUPTIBLE。不论是哪种状态,两种状态地进程都位于同一个等待队列中,等待某些事件。
休眠通过等待队列进行处理。等待队列是由等待某些事件发生的进程组成的简单链表。内核用wait_queue_head_t来表示等待队列。
唤醒操作通过wake_up()来进行。它会唤醒指定的等待队列上的所有进程。它调用函数try_to_wake_up(),该函数负责将进程设置为TASK_RUNNING状态,调用enqueue_task()将此进程放入红黑树中。如果被唤醒的进程优先级比当前正在执行的进程优先级高,还要设置need_resched标志。
七、need_resched
内核必须知道在什么时候调用schedule()。如过仅靠用户代码显式的调用schedule(),它们就可能永远地执行下去。相反的,内核提供一个need_resched标志来表明是否需要重新执行一次调度。当某个进程应该被抢占时,scheduler_tick()就会设置这个标志;当一个优先级高的进程进入可执行状态的时候,try_to_wake_up()也会设置这个标志。内核检查该标志,确认其被设置,调用schedule()来切换到一个新的进程。该标志对内核来讲是一个信息,它表示 有其他进程应当被运行了,要尽快调用调度程序。
每个进程都包含一个need_resched标志。
内核抢占发生在:
·中断处理程序正在执行,且返回内核空间之前。
·内核代码再一次具有可抢占性的时候
·内核中的任务显式的调用schedule()的时候
·如果内核中的人物阻塞(这同样也对导致调用schedule())