1 Linux进程简介
- Linux中,进程的优先级是动态的
- Linux 2.6开始,内核是抢占式的
- 内核线程没有自己的地址空间,整个内核共享内核的虚拟地址
- Linux采用对称多处理模型,每个CPU地位相同
2 进程分类
进程可以分为普通进程(包括交互式进程、批处理进程)、实时进程。
交互式进程和实时进程的区别是:
交互式进程是用于用户交互的进程,需要控制响应时间较短而且不能波动—— 不能太短也不能太长;
实时进程需要最高进程优先级和最短的响应时间。
3 普通进程的调度
3.1 静态优先级
内核用100~139表示普通进程的静态优先级,值越大静态优先级越小
静态优先级和nice成正相关,100~130分别对应nice值的-20~19
静态优先级通过影响进程时间片来影响进程执行的“优先级” —— Linux进程基于时间片调度
父进程生成新进程的时候,会把自己的时间片也分一半给子进程;子进程执行完如果时间片没有用完时间片,会返回给父进程!
3.2 动态优先级
动态优先级在调度程序选择执行进程的时候使用
静态优先级(正相关) && 平均睡眠时间(负相关,确定交互式进程/批处理进程) => 动态优先级
3.3 活动进程和过期进程
活动进程:时间片还没用完,应该是被抢占的进程
过期进程:时间片被用完,因为没有被阻塞,所以肯定还是TASK_RUNNING状态,只不过执行完后放在过期进程链表最后了
用完时间片的活动批处理进程总是变成过期进程,而用完时间片的活动交互式进程,会被重新填时间片并仍旧是活动进程!
(交互式进程占用CPU处理量很小,所以优先级很高也没有问题)
运行进程结构体runqueue里面有包括活动进程和过期进程的两个链表:prio_array_t [2] arrays
因为活动进程和过期进程会进行交换,所以需要有两个链表指针来进行操作:prio_array_t *active; prio_array_t *expired
4 实时进程的调度
4.1 实时进程简介
实时进程有一个实时优先级,调度程序总是让实时优先级高的进程执行;
和交互式进程一样,实时进程也总是活动进程;
4.2 实时进程被取代的情况
- 实时进程被另一个更高优先级的实时进程取代
- 进程本身执行了阻塞操作并休眠,或者执行力sche_yield()主动让出CPU
- 进程停止或被杀死
普通进程基于时间片执行,实时进程基于优先级调度。
5 调度程序使用的函数
5.1 实时进程scheduler_tick()
① 每次时钟节拍到来时都会调用 —— 应该指的是Linux的1ms周期的时钟节拍
② 如果当前进程是FIFO,什么都不做;如果是基于时间片的调度策略,往下走
③ 首先递减时间片计数器,并检查是否已经用完时间片 —— 如果用完,就进行进程调度
- 重填进程的时间片
- 设置RIF_NEED_RESCHED标志,准备进程调度
- 把当前进程描述符从当前活动链表头移到当前活动链表的尾部
5.2 普通进程scheduler_tick()
和实时进程的调度步骤基本一致,差别在于最后不是把进程从链表头移到链表尾,而是:
把当前进程插入活动进程集合(交互式进程,执行完时间片仍旧是活动进程)或者过期进程(批处理进程,执行完就过期)。
5.3 try_to_wake_up()
① 禁用本地中断
② 为当前进程寻找合适的CPU及其runqueue
- 如果系统中某些CPU空闲,就选择空闲CPU的rq作为目标
- 如果该进程所属的CPU的工作量远小于本地CPU的工作量,就选择该进程所属CPU的rq
- 如果进程最近被执行过,就选择老的运行队列作为目标
③ 把进程state设置为TASK_RUNNING
④ 打开本地中断并返回
5.4 recalc_task_prio()
更新进程的平均睡眠时间和动态优先级
5.5 schedule()
当前进程被阻塞时调用schedule();
① 直接调用
- 把current插入适当的等待队列
- 把current进程状态改为TASK_INTERRUPTIBLE或TASK_UNINTERRUPTIBLE
- 调用schedule()
- 多次检查资源是否可用,不可用回到2,可用就从等待队列删除当前进程
② 延迟调用
如果把TIF_NEED_RESCHED置位,就会在每次检查TIF_NEED_RESCHED的时候触发进程调度;
③ 步骤
- 禁用内核抢占,关闭本地中断,获取自旋锁
- 从可运行队列中获取next进程
- 进行进程切换:加载next进程,开始运行next
6 与调度相关的系统调用
nice():降低当前进程的优先级(对其它进程很nice);已被setpriority()取代
getpriority():返回 20 - 给定组中所有进程中最低nice值
setpriority():把给定组中所有的进程的基本优先级都设置为一个给定的值
sched_getscheduler():查询由pid参数所表示的当前进程所用的调度策略,包括SCHED_FIFO,SCHED_RR,SCHED_NORMAL;
sched_setscheduler():设置调度策略
sched_yield():进程自愿放弃CPU,但是仍保持TASK_RUNNING状态;
如果是普通进程,调度程序把它放到运行队列的过期进程集合中;
如果是实时进程,调度程序把它放到运行队列链表的末尾