• 沒有找到結果。

进程调度

在文檔中 嵌 入 式 系 统 应 用 开 发 (頁 131-135)

第 4 章 嵌入式 Linux

4.3 Linux 内核简析

4.3.5 进程调度

系统调用

下一语句

……

用户程序

保护现场

……

下一语句

……

恢复现场 系统调用 预处理程序

入口地址

……

入口地址

……

……

return

……

return 服务程序

服务程序

①:此处语句为 call *SYMBOL_NAME(sys_call_table)(%eax,4);eax 中为系统调用号

图4.4 Linux 系统调用过程

4.3.5 进程调度

Linux 是一个多任务操作系统,它要确保 CPU 时刻处于运行状态。如果正在运行的进 程等待外部设备完成工作,或因其他原因需要放弃 CPU,操作系统就按照调度算法选择 另一个进程运行,从而发挥 CPU 的最大效率。如果说 CPU 是计算机的心脏,那么进程调 度就是计算机的灵魂,因为它决定了如何使用CPU。

(1) 进程调度依据

调度程序运行时,要在所有可运行状态的进程中选择最值得运行的进程投入运行。选 择进程的依据是什么呢?在每个进程的 task_struct 结构中有以下四项:policy,priority,

counter 和 rt_priority。这四项是选择进程的依据。其中,policy 是进程的调度策略,用来 区分实时进程和普通进程,实时进程优先于普通进程运行;priority 是进程(包括实时和普 通)的静态优先级;counter 是进程剩余的时间片,它的起始值就是 priority 的值,由于 counter 在后面计算一个处于可运行状态的进程值得运行的程度(goodness)时起重要作用,

因此,counter 也可以看作是进程的动态优先级;rt_priority 是实时进程特有的,用于实时 进程间的选择。

Linux 用函数 goodness()来衡量一个处于可运行状态的进程值得运行的程度。该函数 综合了以上提到的四项,还结合了一些其他的因素,给每个处于可运行状态的进程赋予一 个权值(weight),调度程序以这个权值作为选择进程的惟一依据。

(2) 进程调度策略

首先,Linux 根据 policy 从整体上区分实时进程和普通进程,因为实时进程和普通进 程的调度是不同的。实时进程先于普通进程运行。然后,对于同一类型的不同进程,采用 不同的标准来选择进程。

对于普通进程,Linux 采用动态优先调度,选择进程的依据就是进程 counter 的大小。

进程创建时,优先级 priority 被赋一个初值,一般为 0~70 之间的数字,这个数字同时也 是(计数器)counter 的初值,就是说进程创建时两者是相等的。从字面上看,priority 是

“优先级”、counter 是“计数器“的意思,然而实际上,它们表达的是同一个意思,即 进程的“时间片”。priority 代表分配给该进程的时间片,counter 表示该进程剩余的时间 片。在进程运行过程中,counter 不断减少,而 priority 保持不变,以便在 counter 变为 0 的时候(该进程用完了所分配的时间片)对 counter 重新赋值。当一个普通进程的时间片用 完以后,并不马上用priority 对 counter 进行赋值,只有所有处于可运行状态的普通进程的 时间片(p->counter==0)都用完了以后,才用 priority 对 counter 重新赋值,这个普通进程才 有了再次被调度的机会。这说明,普通进程运行过程中,counter 的减小给了其他进程得 以运行的机会,直至counter 减为 0 时才完全放弃对 CPU 的使用,这就相当于优先级在动 态变化,所以称之为动态优先调度。至于时间片这个概念,和其他操作系统一样,Linux 的时间单位也是“时钟滴答”,只是不同操作系统对一个时钟滴答的定义不同而已(Linux 为 10ms)。进程的时间片就是指多少个时钟滴答。比如,若 priority 为 20,则分配给该进 程的时间片就为 20 个时钟滴答,也就是 20×10ms=200ms。Linux 中某个进程的调度策略 (policy)、优先级(priority)等可以作为参数由用户决定,具有相当的灵活性。内核创建新进 程时分配给进程的时间片默认为 200ms(更准确地说,应为 210ms),用户可以通过系统调 用改变它。

对于实时进程,Linux 采用了两种调度策略,即 FIFO(先入先出,也称先来先服务调 度)和 RR(时间片,循环调度)。因为实时进程具有一定程度的紧迫性,所以衡量一个实时 进程是否应该运行,Linux 采用了一个比较固定的标准。实时进程的 counter 只是用来表示 该进程的剩余时间片,并不作为衡量它是否值得运行的标准,这和普通进程是有区别的。

如前所述,每个进程有两个优先级,实时优先级用来衡量实时进程是否值得运行。

普通进程有两种优先级,一种是静态优先级,另一种是动态优先级。实时进程又增加 了第三种优先级,实时优先级。优先级是一些简单的整数,为了决定应该允许哪一个进程 使用 CPU 的资源,用优先级代表相对权值,优先级越高,它得到 CPU 时间的机会也就 越大。

l 静态优先级(priority)。不随时间而改变,只能由用户进行修改。它指明了在被迫 和其他进程竞争 CPU 之前,该进程应该允许的时间片的最大值(但很可能,在该 时间片用完之前,进程就被迫交出了CPU)。

l 动态优先级(counter)。只要进程拥有 CPU,它就随着时间不断减小;当它小于 0 时,标记进程重新调度。它指明了在这个时间片中所剩余的时间量。

l 实时优先级(rt_priority)。指明这个进程自动把 CPU 交给哪一个其他进程。较高 权值的进程总是优先于较低权值的进程。如果一个进程不是实时进程,其优先级 就是0,所以实时进程总是优先于非实时进程的(但实际上,实时进程也会主动放 弃CPU)。

进程按调度策略可分成三类,用policy 中的值来描述。当 policy 分别为以下值时:

l SCHED_OTHE。普通的用户进程,进程的默认类型,采用动态优先调度策略。

选择进程的依据主要是根据进程 goodness 值的大小。这种进程在运行时,可以

被高goodness 值的进程抢先。

l SCHED_FIFO。实时进程,遵守 POSIX1.b 标准的 FIFO(先入先出)调度规则。它 会一直运行,直到进程因 I/O 阻塞,或者主动释放 CPU,或者是 CPU 被另一个 具有更高rt_priority 的实时进程抢先。在 Linux 实现中,SCHED_FIFO 进程仍然 拥有时间片,只有当时间片用完时它们才被迫释放 CPU。因此,如同 POSIX1.b 标准一样,这样的进程就像没有时间片(不是采用分时)一样运行。Linux 中进程 仍然保持对其时间片的记录(不修改 counter)主要是为了实现的方便,同时避免在 调 度 代 码 的 关 键 路 径 上 出 现 条 件 判 断 语 句 if

(!(current->policy&SCHED_FIFO)){...},因为其他大量非 FIFO 进程都需要记录时间片,这 种多余的检测会浪费CPU 资源。(一种优化措施,不该将执行时间占 10%的代码 的 运行 时间减少到 50%,而是将执行时间占 90%的代码的运行时间减少到 95%。0.9+0.1×0.5=0.95>0.1+ 0.9×0.9=0.91)

l SCHED_RR。也是一种实时进程,遵守 POSIX1.b 标准的 RR(循环 round-robin)调 度 规 则 。 除 了 时 间 片 有 些 不 同 外 , 这 种 策 略 与 SCHED_FIFO 类 似 。 当 SCHED_RR 进程的时间片用完后,就被放到 SCHED_FIFO 和 SCHED_RR 队列 的末尾。

只要系统中有一个实时进程在运行,则任何 SCHED_OTHER 进程都不能在任何 CPU 运行。每个实时进程有一个 rt_priority,因此,可以按照 rt_priority 在所有 SCHED_RR 进 程之间分配 CPU。其作用与 SCHED_OTHER 进程的 priority 一样。只有 root 用户能够用 系统调用sched_setscheduler 来改变当前进程的类型(sys_nice,sys_setpriority)。

此外,内核还定义了 SCHED_YIELD,这并不是一种调度策略,而是截取调度策略的 一个附加位。如同前面说明的一样,如果有其他进程需要 CPU,它就提示调度程序释放 CPU。特别要注意,就是这甚至会引起实时进程把 CPU 释放给非实时进程。

4.3.6 内存管理

内存管理是操作系统的核心任务之一,它负责管理计算机系统的存储器。为了克服物 理内存的局限,Linux 采用了页式管理、多级表页和动态地址转换的虚拟内存技术,为用 户提供了透明的、灵活有效和安全可靠的内存使用方式。

因为 Linux 支持多种 CPU,内存管理把 CPU 的细节抽象成一种统一的接口,对所有 CPU 内存管理单元(MMU)的存取都通过这个抽象来实现。内存管理利用 CPU 的 MMU 把 虚拟地址映射到物理内存。当用户进程要存取一个内存单元时,MMU 把这个虚拟地址转 换成一个物理地址,然后,用物理地址进行实际存取。

Linux 的内存管理包含了一个守护进程(kswapd),保证系统中具有足够的空闲页,从 而使内存管理程序能够有效运行。进程调度程序调度守护进程的方式和用户进程一样,但 守护进程可以直接存取内核数据单元。

1. 基本知识 (1) 内存地址

l MMU 的作用。辅助操作系统进行内存管理,提供虚实地址转换等硬件支持。

l x86 的地址。

逻辑地址:出现在机器指令中,用来制定操作数的地址。由一个段(segment)和偏 移量(offset)组成。偏移量指明了从段开始的地方到实际地址之间的距离。

线性地址:逻辑地址经过分段单元处理后得到线性地址,这是一个 32 位的无符 号整数,可用于定位4GB 个存储单元。

物理地址:线性地址经过页表查找后得出物理地址,这个地址将被送到地址总线 上指示所要访问的物理内存单元。

l x86 的段。保护模式下的段:选择子+描述符。不仅仅是一个基地址的原因,是 为了提供更多的信息,如保护、长度限制、类型等。描述符存放在一张表(GDT 或 LDT)中,选择子可以认为是表的索引。段寄存器中存放的是选择子,在段寄 存器装入的同时,描述符中的数据被装入一个不可见的寄存器以便 CPU 快速 访问。

l 专用寄存器。

GDTR,存放全局描述表的首地址。

LDTR,存放当前进程的段描述表首地址。

TSR,存放当前进程的任务状态段。

(2) Linux 使用的段

__KERNEL_CS:内核代码段。范围 0~4GB,可读、执行,DPL=0。

__KERNEL_DS:内核代码段。范围 0~4GB,可读、写,DPL=0。

__USER_CS:内核代码段。范围 0~4GB,可读、执行,DPL=3。

__USER_DS:内核代码段。范围 0~4GB,可读、写,DPL=3。

TSS(任务状态段):存储进程的硬件上下文,进程切换时使用 (因为 x86 硬件对 TSS 有一定支持,所有有这个特殊的段和相应的专用寄存器)。

default_ldt:理论上每个进程都可以同时使用很多段,这些段可以存储在自己的 ldt 段 中,但实际Linux 极少利用 x86 的这些功能,多数情况下所有进程共享这个段,它只包含 一个空描述符。

(3) x86 的分页机制

x86 硬件支持两级页表,奔腾 pro 以上的型号还支持 Physical Address Extension Mode

x86 硬件支持两级页表,奔腾 pro 以上的型号还支持 Physical Address Extension Mode

在文檔中 嵌 入 式 系 统 应 用 开 发 (頁 131-135)