1 进程调度 就绪进程最重要的特征是该进程是非阻塞的。进行用户交互、大量读写文件、响应I/O和网络事件的进程会花费大量时间来等待资源可用,在相当长的时间内无法转为就绪状态(长是相对于指令运行时间而言),因此就绪进程首先应该是非阻塞的。一个就绪进程还必须至少有部分“时间片”(调度器分配给进程的运行时间)。内核用一个就绪队列维护所有的就绪进程,一旦某进程耗光它的时间片,内核就将其移出队列,直到所有就绪进程都耗光时间片才考虑将其放回队列。 多任务操作系统分为两大类:协同式和抢占式。Linux实现了后一种形式的多任务,调度器可以要求一个进程停止运行,处理器转而运行另一个进程。这种中止正在运行的进程的行为称做抢占,类似的,进程在被抢占前所运行的时间称之为进程时间片(得名于调度器分配给每个就绪进程的一小片时间)。 在协同多任务系统中,一个进程持续运行直到它自发停止。我们称进程自发停止的行为为让出。理想情况下,会经常发生进程让出,但操作系统绝不可强制要求其让出。因此,一个拙劣或损坏的程序可能运行很长时间,甚至导致整个系统死掉。由于这个原因,现代操作系统几乎都采用抢占多任务机制,Linux也不例外。 Linux调度算法采用抢占多任务机制,支持多处理器,处理器亲和度,非一致内存访问(NUMA),实时进程和用户自定义优先级等特性。

时间片。如果时间片太长,进程必须等待很长时间才能运行,这减小了运行的并行性,用户会察觉到明显的延迟;相反的,时间片太短,大量时间会花费在进程调度上,程序的时间局部性等也不能得到保证。Linux通过动态分配进程时间片,期望在两方面都做到最好。 进程不一定要在一次运行中耗光所有时间片。一个被分配100ms时间片的进程,可能运行20ms就因为等待键盘输入等资源而阻塞。此时,调度器就会临时地把该进程移出就绪队列;当资源可用后,这个例子中是键盘缓冲区不为空,调度器会唤醒进程。进程会继续运行,耗光剩下的80ms或者又一次阻塞。

持续地消耗所有可用时间片的进程称为“处理器约束进程”。这类进程渴望CPU时间,消耗掉调度器分配的全部时间。最简单的例子就是无限循环,其他的例子包括科学计算,数学演算和图像处理。 多数时间处于等待资源的阻塞状态的进程称为“I/O约束进程”。I/O约束进程经常发起和等待文件I/O,阻塞在键盘输入,或者用户移动鼠标。I/O阻塞程序的例子包括文件实用程序,比如cp或者mv,它们除了请求内核执行I/O操作外,几乎什么也不做;还包括GUI应用程序,大多数时候都在等待用户输入。

当一个进程耗光时间片的时候,调度器会中止其运行,开始运行一个新的进程。如果没有其他的就绪进程,内核会给予所有耗光时间片的进程新的时间片,继续运行。在进程运行时,如果另一个高优先级进程就绪(也许先前此进程阻塞在键盘输入,用户正好敲入一个单词),当前运行进程被直接中止,切换到高优先级进程。因此,不会有就绪却没有运行的较高优先级进程,系统中的运行进程一定是最高优先级的可运行进程。保证就绪队列中,最高优先级的运行。

线程是进程中的运行单元,所有的进程都至少有一个线程。每一个线程都独自占有一个虚拟处理器:独自的寄存器组,指令计数器和处理器状态。虽然多数进程都只有一个线程,但是进程实际可以拥有很多线程,每个线程完成不同的任务,但是共享同一地址空间(也就是同样的动态内存,映射文件,目标代码等等),打开的文件队列和其他内核资源。内核把线程简化为共享资源的进程,也就是说,内核把一个进程中的两个线程,简化为共享一系列内核资源(地址空间,打开的文件列表等)的两个不同进程。

2 让出处理器 一般来说Unix程序倾向于使用建立在可阻塞文件描述符基础上的事件驱动机制。当进程IO阻塞时,内核将抢占处理器而不需要该进程显式让出处理器。Linux是一个抢占多任务操作系统,很少有合理使用sched_yield()的机会,但是它也提供了一个系统调用来允许进程主动让出处理器。内核完全有能力作出最优化和最有效率的调度决策,这是因为,内核显然比一个独立的应用程序更有资格决定何时抢占哪个进程,协同多任务和抢占多任务两种不同机制来源于对此的不同理解。 当一个线程试图请求另一个线程已经拥有的锁的时候,该线程需要显式让出处理器直到锁可用。在内核不支持用户空间锁的时候,这种方法最简单高效。然而,现代Linux线程实现(the New POSIX Threading Library,or NPTL)迎来了一个基于快速用户互斥锁的优化方案,即在内核中提供用户空间锁的支持。 2.6版本之后,调用sched_yield()的实际作用就和进程耗光时间片一样,这不同于早期内核的处理,那时sched_yield()的效果很轻微,而且有乒乓问题。

3 进程优先级 int nice (int inc); Nice Value [-20, 19],默认值为0,Nice Value越小优先级越高,时间片越长。在运行进程的时候指定,Linux调度器总是优先运行高优先级线程。非Root用户只能使用正值inc来降低优先级。

int getpriority (int which, int who); int setpriority (int which, int who, int prio); which指定作用对象是进程、进程组或者用户,who指定对应的ID,为0时表示当前作用对象。同nice一样,非Root用户只能使用正值inc来降低优先级。

int ioprio_get (int which, int who) int ioprio_set (int which, int who, int ioprio) 缺省情况下,I/O调度器用进程友好度决定I/O优先级,因此,设置优先级自动改变I/O优先级。作为进程优先级的补充,Linux还允许进程指定I/O优先级,内核I/O调度器总是优先响应来自于高I/O优先级的请求。

4 处理器亲和度 对称多处理SMP,是指在一个计算机上汇集了一组处理器(多CPU),各CPU之间共享内存子系统以及总线结构,由一个操作系统控制。系统将任务队列对称地分布于多个CPU之上,从而极大地提高了整个系统的数据处理能力。所有的处理器都可以平等地访问内存、I/O和外部中断。在对称多处理系统中,系统资源被系统中所有CPU共享,工作负载能够均匀地分配到所有可用处理器之上。一般来讲,SMP结构的机器可扩展性较差,很难做到100个以上多处理器,常规的一般是8个到16个。SMP系统对硬件有基本的要求: 1、CPU内部必须内置APIC(Advanced Programmable Interrupt Controllers)单元。Intel多处理规范的核心就是高级可编程中断控制器(Advanced Programmable Interrupt Controllers--APICs)的使用。CPU通过彼此发送中断来完成它们之间的通信。通过给中断附加动作(actions),不同的CPU可以在某种程度上彼此进行控制。每个CPU有自己的APIC(成为那个CPU的本地APIC),并且还有一个I/O APIC来处理由I/O设备引起的中断,这个I/O APIC是安装在主板上的,但每个CPU上的APIC则不可或缺,否则将无法处理多CPU之间的中断协调。 2、相同的产品型号,同样类型的CPU核心。例如,虽然Athlon和Pentium III各自都内置有APIC单元,想要让它们一起建立SMP系统是不可能的,当然,即使是Celeron和Pentium III,那样的可能性也为0,甚至Coppermine核心的Pentium III和Tualatin的Pentium III也不能建立SMP系统--这是因为他们的运行指令不完全相同,APIC中断协调差异也很大。 3、完全相同的运行频率。如果要建立双Pentium III系统,必须两颗866MHz或者两颗1000MHz处理器,不可以用一颗866MHz,另一颗1000MHz来组建,否则系统将无法正常点亮。 4、尽可能保持相同的产品序列编号。即使是同样核心的相同频率处理器,由于生产批次不同也会造成不可思议的问题。两个生产批次的CPU作为双处理器运行的时候,有可能会发生一颗CPU负担过高,而另一颗负担很少的情况,无法发挥最大性能,更糟糕的是可能导致死机,因此,应该尽可能选择同一批生产的处理器来组建SMP系统。

在对称多处理机(SMP)上,进程调度器必须决定每个CPU上运行哪个进程,因此,必须解决两个问题:调度器必须充分利用系统的处理器,尽量避免处理器空闲。然而,如果一个进程曾在某一CPU上运行,进程调度器还应该尽量把它放在同一CPU上,因为处理器间的进程迁移会带来性能损失。最大的性能损失来自于迁移带来的缓存效应。现代SMP系统的设计中,每个处理器的缓存是各自独立的,也就是说,处理器并不共享缓存中的数据。 决定何时移动进程来避免不平衡,称为负载均衡,对SMP机器的性能至关重要。处理器亲和度表明一个进程停留在同一处理器上的可能性。术语“软亲和度”(soft affinity)表明调度器持续调度进程到同一处理器上的自然倾向。Linux调度器尽可能地这样做,只有当负载极端不平衡的时候,才考虑迁移进程。 然而有些时候,用户或者应用程序需要保证进程和处理器间的绑定,这通常发生在进程非常依赖缓存,期望停留在同一处理器的情况下。术语“硬亲和度”(hard affinity)描述了强制内核保证进程到处理器的绑定。进程从父进程继承处理器亲和度;在默认情况下,可能运行在任何CPU上。Linux提供两个系统调用来获取和设定进程的硬亲和度: int sched_setaffinity (pid_t pid, size_t setsize, const cpu_set_t *set); int sched_getaffinity (pid_t pid, size_t setsize, const cpu_set_t *set);

5 实时系统 如果一个系统受到操作期限——请求和响应之间的最小量和命令次数的支配,就称该系统是“实时”的。实时系统分为软硬实时系统两大类。硬实时系统对于操作期限要求非常严格,超过期限就会产生失败,后果很严重。另一方面,软实时系统却不认为超过期限是一个严重的失败。硬实时系统很容易分辨:防抱死系统、军用武器系统、医疗设备、信号处理都是比较典型的例子。软实时系统则不太容易分辨,一个比较明显的例子是视频处理程序:如果超过了操作时限,用户会注意到一些质量下降,但是少量的丢帧还是可以忍受的。

Linux对进程的调度行为依赖于进程的调度策略,也称之为调度类别。Linux提供了两类实时调度策略作为正常默认策略的补充。头文件<sched.h>中的预定义宏表示各个策略:分别为SCHED_FIFO,SCHED_RR和SCHED_OTHER。每一个进程都有一个与nice值无关的静态优先级,对于普通程序,值为0;对于实时程序,它为1到99。Linux调度器始终选择最高优先级的进程运行(静态优先级数值最大的进程)。 1,“先进先出”策略:先进先出(FIFO)策略是没有时间片的非常简单的实时策略。只要没有高优 先级进程就绪,FIFO类型进程就会持续运行。特别的,一旦FIFO类进程就绪,它就会直接抢占普通进程。FIFO型进程持续运行直到阻塞或者调用sched_yield(),或者高优先级进程就绪。当FIFO型进程阻塞时,调度器将其移出就绪队列。 2,轮转策略,类似于FIFO类型,仅仅引入了处理同等优先级进程的附加规则,以SCHED_RR表示。RR型的时间片仅在相同优先级的进程间相关。 3,普通调度策略,SCHED_OTHER代表标准调度策略,适用于默认的非实时进程。所有这些进程的静态优先级都为0,因此,任意就绪FIFO或RR形进程都会抢占他们。 4,批调度策略,SCHED_BATCH是批调度或空闲调度的策略,它在某种程度上是实时调度的对立面:这种类型的进程只在系统中没有其他就绪进程时才会运行,即使那些进程已经耗光时间片。

进程调度相关的系统调用: int sched_get_priority_min (int policy); int sched_get_priority_max (int policy); int sched_getparam (pid_t pid, struct sched_param *sp); int sched_setparam (pid_t pid, const struct sched_param *sp); int sched_rr_get_interval (pid_t pid, struct timespec *tp);

实时进程乐于看到确定性。在实时计算中,如果给予相同的输入,一个动作总是在相同的时间内产生相同的结果,我们就说这个动作是确定的。现代计算机可以说是不确定的集合体:多级缓存(命中与否不可预测),多处理器,分页,交换,和多任务都使估计一个动作需要多长时间变得不可能。确定性实时应用一般会尽量限制不可预测性,和最坏情况下的延时。达到目标有两种方法。 1,数据故障预测和内存锁定,对于分页和交换给实时进程带来的不确定性,“通过锁定”或者“硬连接“来将地址空间中的页提前放入物理内存,阻止其被交换出去。一旦页被锁定,内核就不会将起交换出去,任何访问都不会引起页错误,大多数实时应用都锁定部分和全部页面到物理内存。 2,CPU亲和度和实时进程,实时应用的第二个难点在于多任务。虽然Linux内核是抢占式的,但是调度器并不总能直接调度另一个进程。有时,当前进程运行在内核中的临界区,调度器就必须等待它退出临界区,如果此时有一个实时进程要运行,延时将不可接受,很快就会超出操作期限。因此,多任务和分页一样也带来了相似的不确定。对于多任务的解决方案也一样:消除它。如果你的系统中有多个处理器,可以指定一个或多个专门用于实时进程。从实际效果上讲,你把实时进程和多任务分离开来。一个潜在的对实时进程的优化是为每一个实时进程保留一个处理器,剩下的处理器由其他进程共享。

6资源限制 Linux内核有对进程的资源限制,明确规定了进程可以消耗的内核资源的上限,比如打开文件的数目,内存页数,未处理的信号等等。限制是强制性的,内核不会允许进程的超过这一硬性限制。结构定义了两个上限:软限制和硬限制。内核对进程强制施行软限制,但进程自身可以修改软限制,可以是0到硬限制之间的任意值。不具备CAP_SYS_RESOURCE能力的进程(比如,非root进程),只能调低硬限制。非特权程序不能提升硬限制,包括恢复为之前的较高的值;因此,调低硬限制是不可逆的。特权进程则可以设置硬限制为任意合法值。目前Linux提供了15种资源限制: ResourceLimit SoftLimit HardLimit Comments RLIMIT_AS RLIM_INFINITY RLIM_INFINITY 进程地址空间上限 RLIMIT_CORE 0 RLIM_INFINITY CoreDump文件最大值 RLIMIT_CPU RLIM_INFINITY RLIM_INFINITY 进程最长CPU时间 RLIMIT_DATA RLIM_INFINITY RLIM_INFINITY 进程数据段和堆的大小 RLIMIT_FSIZE RLIM_INFINITY RLIM_INFINITY 创建的最大文件 RLIMIT_LOCKS RLIM_INFINITY RLIM_INFINITY 文件锁的最大数量 RLIMIT_MEMLOCK 8Pages 8Pages 内存锁定最大字节数 RLIMIT_MSGQUEUE 800KB 800KB 消息队列中分配的最大空间 RLIMIT_NICE 0 0 降底NICE值(提升优先级)的最大值 RLIMIT_NOFILE 1024 1024 可打开的最多文件数 RLIMIT_NPROC 0(Impliesnolimit) 0(Impliesnolimit) 系统任意时刻允许的最多进程数 RLIMIT_RSS RLIM_INFINITY RLIM_INFINITY 进程可以驻留在内存中的最多页数 RLIMIT_RTPRIO 0 0 最大实时优先级 RLIMIT_SIGPENDING 0 0 用户消息队列中最多信号数 RLIMIT_STACK 8MB RLIM_INFINITY 栈的最大字节长度