您的位置:首页 > 运维架构 > Linux

Linux内核分析 读书笔记 (第四章)

2016-04-12 19:52 465 查看

第四章 进程调度

调度程序负责决定将哪个进程投入运行,何时运行以及运行多长时间。进程调度程序可看做在可运行态进程之间分配有限的处理器时间资源的内核子系统。只有通过调度程序的合理调度,系统资源才能最大限度地发挥作用,多进程才会有并发执行的效果。

4.1 多任务

1.多任务操作系统就是能同时并发地交互执行多个进程的操作系统。无论在单处理或者多处理器机器上,多任务操作系统都能使多个进程处于拥塞或者睡眠状态,也就是实际上不被投入执行,直到工作确实就绪。

2.多任务系统可以划分为两类:非抢占式多任务和抢占式多任务。

3.Linux提供了抢占式的多任务模式。进程在被抢占之前能够运行的时间是预先设置好的,叫进程的时间片。时间片实际上就是分配给每个可运行进程的处理器时间段。

4.3 策略

策略决定调度程序在何时让什么进程运行。

4.3.1 I/O消耗型和处理器

1.进程可以被分为I/O消耗型(进程的大部分时间用来提交I/O请求或是等待I/O请求)和处理器消耗型(把时间用在执行代码上)。

2.划分方式不是绝对的,进程可以同时展现这两种行为,比如X Window服务器。

3.调度策略通常要在两个矛盾目标中间寻找平衡:进程响应迅速(响应时间短)和最大系统利用率(高吞吐量)。

4.3.2 进程优先级

1.调度算法中最基本的一类就是基于优先级的调度。这是一种根据进程的价值和其对处理器时间的需求来对进程分级的想法。通常做法是优先级高的进程先运行,低的后运行,相同优先级的进程按轮转方式进行调度(一个接一个,重复进行)。调度程序总是选择时间片未用尽而且优先级最高的进程运行。用户和系统都可以通过设置进程的优先级来影响系统的调度。

2.优先级分为两类

nice值(从-20——+19):默认值为0;数值越大意味着优先级越低;可以通过 ps-el查看系统进程列表并找到NI标记列对应的优先级

实时优先级(从0——99):越高的实时优先级级数意味着进程优先级越高

二者互不交互

4.3.3.时间片

1.时间片表示进程在被抢占之前所能够持续运行的时间;调度策略必须确定一个默认的时间片;

2.Linux的CFS调度器并没有直接划分时间片到进程,而是将处理器的使用比例划分给了进程。也就是说,其抢占时机取决于新的可执行程序消耗了多少处理器使用比,如果消耗的使用比比当前进程小,则新进程立即投入运行抢占当前进程。

4.4 Linux调度算法

4.4.1 调度器类

Linux调度器是以模块方式提供的(也就是调度器类),目的是允许不同类型的进程可以有针对性地选择调度算法

调度器类允许多种不同的可动态添加的调度算法并存,调度属于自己范畴的进程;

调度器代码会按照优先级顺序遍历调度类,拥有一个可执行进程的最高优先级的调度器类胜出,去选择下面要执行的那一个程序;

4.4.2 Unix中系统调度问题

将nice值映射到时间片的话,就必须将nice值对应到处理器的绝对时间;这样会导致进程切换无法最优进行;

如果使用相对nice值,所带来的效果将会极大取决于其nice的初始值;

如果执行nice值到时间片的映射,时间片极大受制于定时器。

4.4.3 公平调度

CFS基于一个简单的理念:进程调度的效果应当如同系统具备一个理想中的完美任务处理器。CFS的做法如下:

允许每个进程运行一段时间、循环轮转、选择运行最少的进程作为下一个运行进程;

nice值作为进程获得的处理器运行比的权重(而不是完全由nice决定时间片);

每个进程都按照其权重在全部的可运行进程中所占的比例对应的“时间片”来运行

【所谓“鱼与熊掌不可得兼”即如此——越小的调度周期就会表现出越好的交互性,也更接近于“同时完成多任务”这一孜孜追求的目标;然而系统必须承受更高的切换代价和更差的系统吞吐量——甚至将绝大多数精力耗费在这种来回倒腾上】

4.5 Linux调度的实现

4.5.1 时间记账

所有的调度器都必须对进程的运行时间做记账;

CFS使用调度器实体结构来追踪运行记账

4.5.2 虚拟实时

vrntime变量【也就是在上面所说的实体结构中】存放虚拟运行时间。虚拟时间以ns为单位,和节拍定时器无关;

update_curr()函数实现了记账功能;计算了当前进程的执行时间并将其存放在data_exec中;然后将运行时间传递给了_update_curr(),由后者再根据当前可运行进程总数对运行时间进行计算,最终确定上述的权重值与当前运行进程的vrntime。



4.5.3 进程选择

CFS算法核心:选择具有最小vrntime的任务

具体做法:利用红黑树rbtree(以节点形式存储数据的二叉树)

举例:

选择下一个任务:从根节点中序遍历二叉树,一直到叶子节点(也就是vrntime最小的进程);

向树中加入进程:在进程变为可执行状态或者通过fork()调用第一次创建进程;

从树中删除进程:发生在进程阻塞或者终止的时候

【由此我们可以看到,二叉树中存储的全部是可执行进程】

4.5.4 进程调度入口

进程调度的主要入口点是函数schedule(),定义在kernel/sched.c中;这正是内和其他部分用于调度进程调度器的入口

这一函数最重要的工作就是调用pick_next_state(),依次检查每一个调度类,并从最高优先级的调度类中,选择最高优先级进程

4.5.5 和唤醒

进程休眠一定是为了等待一些事件

进程把自己标记成休眠状态,从可执行红黑树中移除;

放入等待队列——由等待某些时间发生的进程组成的链表,内核用wake_queue_head_t来代表等待队列



唤醒操作由函数wake_up()进行

它会调用函数try_to _wake_up()将进程设置为TASK_RUNNING状态,调用enqueue_task()将进程放入红黑树中

当然,也存在虚假唤醒进程的状态

4.6 抢占和上下文切换

4.6.1 用户抢占

上下文切换由定义在kernel/sched.c中的context_switch()函数负责,每当一个新的进程被选出来准备运行的时候,schedule()就会调用该函数:

调用switch_mm(),负责把虚拟内存从上一个进程映射切换到新的进程中;

调用switch_to(),负责从上一个进程的处理器状态切换到新进程的处理器状态

4.6.2 内核抢占

只要没有锁,内核就可以进程抢占;

为了支持抢占,每个进程的thread_info都加入了preempt_count计数器(初值为0,每当使用锁的时候就加1,释放锁的时候数值减1),当数值为0的时候,内核就可以抢占

内核抢占发生在:

中断处理程序正在执行且返回内核空间之前;

内核代码再一次具有可抢占性的时候;

内核中的任务显式地调用schedule函数

4.7 实时策略调度

1.linux 提供两种实时调度策略SCHED_FIFO和SCHED_RR。具体的实现定义在kernel/sched_rt.c中。





两种实时算法实现的都是静态优先级。

2.Linux的实时调度算法提供一种软实时工作方式,软实时的含义是:内核调度进程,尽力使进程在他的限定时间到来前运行,但内核不保证总能满足这些进程的要求。硬实时系统则可以满足任何调度的要求。

3.实时优先级范围从0到MAX_RT_PRIO减1。MAX_RT_PRIO默认为100

4.8 与调度相关的系统调用

linux 提供一个系统调用族,用于管理与调度程序相关参数。这些系统调用可以用来操作和处理进程优先级、调度策略和处理器绑定,还提供了显式地将处理器交给其他进程的机制。



4.8.1 与调度策略和优先级相关的系统调用

1.Sched_setscheduler():设置进程的调度策略和实时优先级

2.Sched_getscheduler():获取进程的调度策略和实时优先级

最重要的工作在于读取或改写进程tast_struct的policy和rt_priority的值。

3.Sched_setparam():设置进程的实时优先级

4.Sched_getparam():获取进程的实时优先级



4.8.2与调度策略和优先级相关的系统调用

用户可以通过sched_setaffinity()设置不同的一个或者几个位组合的位掩码,而调用sched_getaffinity()则返回当前的cpus_allowed位掩码。



4.8.3 放弃处理器时间

Linux通过sched_yield()系统调用,提供一种让进程显式地将处理器时间让给其他等待执行进程的机制。内核代码为了方便,可直接调用yield(),先确定给定进程确实处于可执行状态,然后再调用sched_yield()。用户空间的应用程序直接使用sched_yield()系统调用就可以了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: