进程调度
1、策略
策略决定调度程序在何时让什么进程运行。调度器的策略往往决定系统的整体印象,并且,还要负责优化使用处理器时间。
1.1 I/o消耗型和处理器消耗型。
进程可以被分为I/O消耗型和处理器消耗型。前者指进程的大部分时间用来提交I/O请求或者等待I/O请求。因此,这样的进程经常处于可运行状态,但通常都是运行短短的一会儿,I/O请求时最后总会阻塞。
1.2 进程优先级
调度算法中最基本的一类就是基于优先级的调度。Linux内核提供了两组独立的优先级范围。第一种是nice值,范围从-20到+19,默认值是0,。nice的值越大优先级越低,nice值小的进程在nice直达的进城之前执行。第二个是实时优先级,其值是可配置的,默认情况下他的变化范围是从0到99.任何实施进程的优先级度高于普通的进程。
1.3 时间片
1.4 进程抢占
1.5 调度策略的活动
2 Linux调度算法
设计新的调度算法是为了实现下列目标:
- 充分实现O(1)调度,不管有多少进程,新的调度采用的每个算法都能在恒定时间内完成。
- 全面实现SMP的可扩展性。每个村里起拥有自己的锁和自己的可执行队列。
- 强化SMP的亲和力,尽量将相关一组任务分配给一个CPU进行连续的执行。只有在需要平衡任务队列的大小时才在CPU之间移动进程。
- 加强交互性能。即使在系统处于相当负载的情况下,也能保证系统的相应,冰粒机调度交互式进程。
- 保证公平,在合理设定的时间范围内,没有进程会处于饥饿状态。同样的,也没有进程能够显示公平地得到大量的时间片。
- 虽然最常见的优化情况是系统中只有1~2个可运行进程,但是优化也完全有能力扩展到具有多处理器且每个处理器上运行多个进程的系统中。
2.1 可执行队列
调度程序中最基本的数据结构是运行队列。可执行队列定义域kernel/sechd.c中,由runqueue表示。
struct runqueue{ spinlock_t lock; /* 保护运行队列的自旋锁 */ unsigned long nr_running; /* 可运行任务数目 */ unsigned long nr_switches; /* 上下文切换数目 */ unsigned long expired_timestamp; /* 队列最后被换出时间 */ unsigned long nr_uninterruprible; /*处于不可中断睡眠状态的任务数目 */ unsigned long long timestamp_last_tick; /* 最后一个调度程序的节拍 */ struct task_struct *curr; /* 当前运行任务 */ struct task_struct *idle; /* 该处理器的空任务 */ struct mm_struct *prev_mm; /*最后运行任务的mm_struct结构体 */ struct prio_array *active; /* 活动优先级队列 */ struct prio_array *expired; /*超时优先级队列 */ struct prio_array array[2]; /*实际优先级数组 */ struct task_struct *migration_thread; /* 移出线程 */ struct list_head *migration_queue; /*移出队列 */ atomic_t nr_iowait; /* 等待I/O操作的任务数量 */ };
由于可执行队列是调度程序的核心数据结构体,所以有一组宏定义用于获取与给定处理器或进程相关的可执行队列。cpu_rq(processor)宏用于返回给定处理器可执行队列的指针。this_rq()宏用来返回当前处理器的可执行队列。最后,宏task_rq(task)返回给定任务多在的队列指针。
在对可执行队列晋城操作以前,应该先锁住它。因为每个可执行队列唯一的对应一个处理器,所以很少出现一个处理器需要锁其他处理器的可执行队列的情况。在其拥有者读取或改写队列成员的时候,可执行队列包含的锁用来放置队列被其他代码改动。锁住额运行队列的最常见情况发生在你想锁住的运行队列上恰巧有一个特定的任务在运行,此时需要用到task_rq_lock()和task_rq_unlock()
2.2 优先级数组
每个运行队列都有两个优先级数组,一个活跃的和一个过期的。
2.3 重新计算时间片