六. 其他核心应用的调度相关部分
系统中很多技术都和调度器相关,这里仅就其中几个稍作展开,并且不涉及该技术的细节,仅就其中与调度器相关的部分进行讨论,假定读者对于该技术有初步的了解。
1. IDLE进程
系统最初的引导进程(init_task)在引导结束后即成为cpu 0上的idle进程。在每个cpu上都有一个idle进程,正如上文所言,这些进程登记在init_tasks[]数组中,并可用idle_task()宏访问(见上"相关数据结构")。idle进程不进入就绪队列,系统稳定后,仅当就绪队列为空的时候idle进程才会被调度到。
init_task的task_struct是静态配置的,定义在[include/linux/sched.h]中的INIT_TASK()宏中,其中与调度相关的几个属性分别是:
state:TASK_RUNNING;
counter:10*HZ/100;i386上大约100ms
nice:0;缺省的优先级
policy:SCHED_OTHER;非实时进程
cpus_runnable:-1;全1,未在任何cpu上运行
cpus_allowed:-1;全1,可在任何cpu上运行
在smp_init()中(实际上是在[arch/i386/kernel/smpboot.c]中的smp_boot_cpus()中),init_task的processor属性被设为0,对应的schedule_data也设置好相应的值。在创建了一个核心线程用于执行init()函数之后([/init/main.c]rest_init()),init_task设置自己的need_resched等于1,然后调用cpu_idle()进入IDLE循环。
在cpu_idle()中,init_task的nice值被设为20(最低优先级),counter为-100(无意义的足够小),然后cpu_idle()进入无限循环:
/* 节选自[arch/i386/kernel/processs.c] cpu_idle() */
while (1) {
void (*idle)(void) = pm_idle;
if (!idle)
idle = default_idle;
while (!current->need_resched)
idle();
schedule();
check_pgt_cache();
}
初始化过程中第一次执行cpu_idle(),因need_resched为1,所以直接启动schedule()进行第一次调度。如上文所述,schedule()会清掉need_resched位,因此,之后本循环都将执行idle()函数,直至need_resched再被设置为非0(比如在reschedule_idle()中,见上"调度器工作时机")。
idle()函数有三种实现可能:
default_idle(),执行hlt指令;
poll_idle(),如果核心参数上定义了"idle=poll",则pm_idle会指向poll_idle(),它将need_resched设置为特殊的-1,然后反复循环直到need_resched不等于-1。因为poll_idle()采用更高效的指令,所以运行效率比default_idle()要高;
电源管理相关的idle过程,例如APM和ACPI模块中定义的idle过程。
因为仅当就绪队列为空的时候才会调度到idle进程,所以,只有在系统完全空闲时才会执行check_pgt_cache()操作,清理页表缓存。
2. 进程创建
系统中除了init_task是手工创建的以外,其他进程,包括其他CPU上的idle进程都是通过do_fork()创建的,所不同的是,创建idle进程时使用了CLONE_PID标志位。
在do_fork()中,新进程的属性设置为:
state:TASK_UNINTERRUPTIBLE
pid:如果设置了CLONE_PID则与父进程相同(仅可能为0),否则为下一个合理的pid
cpus_runnable:全1;未在任何cpu上运行
processor:与父进程的processor相同;子进程在哪里创建就优先在哪里运行
counter:父进程counter值加1的一半;同时父进程自己的counter也减半,保证进程不能通过多次fork来偷取更多的运行时间(同样,在子进程结束运行时,它的剩余时间片也将归还给父进程,以免父进程因创建子进程而遭受时间片的损失)
其他值与父进程相同
子进程通过SET_LINKS()链入进程列表,然后调用wake_up_process()唤醒(见上"调度器工作时机")。