- 策略
1.1 I/O消耗型和处理器消耗型的进程
进程的大部分时间用来提交I/O请求,等待请求,进程可以经常处于可运行状态,等待更多请求时会阻塞
处理器消耗型大多放在执行代码上,调度策略是尽量降低他们的运行频率。
调度策略通常要在两个矛盾的目标中间寻找平衡,进程响应迅速,最大系统利用率。倾向优先调度I/O消耗型进程
1.2 进程优先级
高优先级先运行,低的后运行,相同优先级的进程按轮转方式进行调度,如果一个进程在io等待上耗费时间多于其运行时间,属于io消耗型进程,相反一个进程的时间片一下被耗尽,属于处理器消耗型进程,它的优先级被动态地降低
nice -20 – 19 默认是0 实时优先级,他是可配置的,默认情况下它的变化范围是0到99,任何实时进程的优先级都高于普通进程。
1.3 时间片
时间片表明进程在被抢占前所能维持运行的时间,处理器消耗型需要长的时间片,io消耗型不需要长的时间片
1.4 进程抢占
当一个进程进入task_running状态内核会检查它的优先级是否高于正在执行的进程,调度程序挥别唤醒,抢占当前正在运行的进程并运行新的可运行进程。
1.5 调度策略的活动
文字编辑程序和视频编码程序,文字编辑高优先级,视频编码可以在所有剩余时间中独享处理器,提高了应用的性能 - 调度算法
实现调度目标:1.充分实现O1调度,实现smp的可拓展型,每个处理器拥有自己的锁和可执行队列
加强交互性能,即使系统处于相当负载的情况下,也能保证系统的响应,并立即调度交互式进程。保证公平
2.1 可执行队列
调度程序中最基本的数据结构是运行队列,给定处理器上的可执行进程的链表每个处理器一个,
struct runqueue{
spinlock_t lock; 保护运行队列的自旋锁
unsigned long nr_running; 可运行任务数目
unsigned long nr_switches; 上下文切换数目
unsigned long expired_timestamp; 队列最后被换出时间
unsigned long nr_uninterruptible; 处于不可中断睡眠状态的任务数目
unsigned long timestamp_last_tick; 最后一个调度程序的节拍
struct task_struct *curr;当前运行任务
struct task_struct *idle; 空任务
struct mm_struct *prev_mm; 最后运行任务的结构体
struct prio_array *active; 活动优先级队列
struct prio_array *expired; 超时优先级队列
struct prio_array *arrays[2];实际优先级数组
struct task_struct *migration_thread;移出线程
struct task_struct migration_queue;移出队列
atomic_t nr_iowait;等待I/O操作的任务数目
};
cpu_rq(processor)返回给定处理器可执行队列的指针,this_rq返回当前处理器的可执行队列,task_rq(task)返回给定处理器可执行队列的指针
按照执行队列地址从低向高的顺序释放添加锁
2.2 优先级数组
每个运行队列都有两个优先级数组,一个活跃的一个过期的,
struct prio_array{
int nr_array; 任务数目
unsigned long bitmap[BITMAP_SIZE]; 优先级位图
stuct list_head queue[MAX_PRIO]; 优先级队列
};
MAX_PRIO 定义了系统拥有优先级的个数,默认值为140,bitmap_size 是优先级位图数组的大小,长度32位,140个优先级需要5个长整型数才能表示 325 160,优先级的个数是个定值,查找时间恒定,不受系统到底有多少可执行进程的影响,sched_find_first_bit();每个优先级数组还包含一个给定的优先级,每个链表都包含该处理器队列上相应优先级的全部可运行进程,对于给定的优先级按轮转方式调度任务
2.3 重新计算时间片
显示方式重新计算每个进程的时间片 重新计算优先级,计算时间片
存在问题:耗费相当长的时间,n个进程的系统复杂度可能达到On,重新计算时间片的时机是不确定的,给确定性要求很高的实时程序带来麻烦,实现比较粗糙
为每个处理器维护两个优先级数组,既有活动数组也有过期数组,活动数组内的可执行队列上的进程还有时间片剩余,过期数组内的可执行队列上的进程都耗尽了时间片,会被移至过期数组,重新计算时间片变得非常简单,在活动和过期数组之间来回切换就可以,
struct prio_array *arry = rq->active;
if(!array->nr_active){
rq->active = rq->expired;
rq->expired = array;
}
2.4 schedule()
struct task_struct *prev,*next;
struct list_head *queue;
struct prio_array *array;
int idx;
prev = current;
array = rq->active;
idx = sched_find_first_bit(array->bitmap);
queue = array->queue + idx;
next = list_entry(queue->next, struct task_struct, run_list);
如果prev和next不等,说明选中的进程不是当前进程,context-switch被调用,prev---->next
2.5 计算优先级和时间片
effctive_prio返回一个进程的动态优先级,举例,交互性强的nice可能优先级高,奖励机制
当一个进程从休眠状态恢复到执行状态时,sleep_avg会根据它的休眠长短而增长,直到达到max_sleep_avg为止,
2.6 睡眠和唤醒
休眠的进程处于一个特殊不可执行状态,将自己标记成休眠状态,把自己移出可执行队列,放入等待队列,调用schedule选择和执行一个其他进程
等待队列是由等待事件发生的进程组成的简单链表,内核用wake_queue_head_t来代表等待队列,DECLARE_WAITQUEUE静态创建,init_waitqueue_head动态创建,注意休眠和唤醒的实现。
load_balance
find busiest group
find busiest queue
if busiest->nr running > 1
then double lock balance (rq,business)
move tasks
busiest->push_cpu = this_cpu - 抢占和上下文切换
3.1 用户抢占
内核即将返回用户空间的时候,如果need_resched标志被设置,导致schedule被调用,发生用户抢占,从系统调返回用户空间,从中断处理程序返回用户空间
3.2 内核抢占
不支持抢占的内核中各任务是协作方式调度的,不具备抢占性
什么时候可以重新调度?没有持有锁,锁是非抢占区域的标志,支持smp没有持有锁,正在执行的代码就是可重新导入的,也就是可抢占的。
thread_info 引入preempt_count计数器,值为0则表示可以抢占,如果need_reched被设置,count为0则表示有个更重要的任务需要执行并且可以安全地抢占
从中断处理程序在执行且返回内核空间之前
当内核代码再一次具有可抢占性的时候
内核显式的调用schedule
内核中的任务阻塞 - 实时
SCHED_FIFO 先入先出的调度算法,不使用时间片,可执行状态会一直执行,直到受阻或显式地释放处理器,他不基于时间片,只有高级别的进程才可以抢占
SCHED_RR 在耗尽事先分配的时间就不再不再接着执行了,实时轮流调度算法,静态优先级,不为实时进程计算动态优先级。
软实时工作方式,内核调度进程,使进程在它的限定时间到来前运行,硬实时可以严格满足时间要求 - 与调度相关的系统调用
5.1 与调度策略和优先级相关的系统调用
sched_setscheduler sched_getscheduler 设置和获取进程的调度策略和实时优先级,
sched_setparam/getparam 设置和获取进程的实时优先级
5.2 与处理器绑定有关的系统调用
sched_setaffinity设置一个不同的一个或几个组合的位掩码,get返回cpus_allowd位掩码
5.3 放弃处理器时间 - 调度程序小结
NUMA机器功能变得越来越重要
Linux 进程调度
最新推荐文章于 2022-08-24 16:30:18 发布
2399

被折叠的 条评论
为什么被折叠?



