分类: LINUX
2011-05-09 17:59:27
native linux(下称nlinux)中,实时进程的调度相对简单,而对于普通进程的调度比较复杂
2.6.23前用的是O(1) 调度,2.6.23 开始和谐了,使用了CFS完全公平调度,关于CFS调度的关键点在于
虚拟运行时钟,网上关于这方面的文章很多,写得都很不错,具体不敢乱讲
oklinux base 于2.6.24 它的调度算法也是和nlinux一样,但是l4 kernel 作为
一个实时内核,它也有自己的调度算法,在关注他们之间是如何协调的问题前先来看下l4 kernel
的调度:
api-slides-3.0.pdf 中关于l4 thread 的调度描述如下:
OKL4 uses 256 hard priorities (0 - 255):
* Priorities are strictly observer
* The highest-priority runnable thread will always be scheduled
Round-robin scheduling among threads of highest prio
Aim is real-time scheduling, not fairness
* Kernel on its own will never change the priority of a thread
* Achieving fairness (if desired) is the job of user-level servers
* Can use Schedule() syscall to adjust priorities
大概意思就是有256个硬优先级, 可运行的最高优先级线程总是被调度(就是说总是占有cpu,真是弱肉强食啊,不过实时os 的要求就是这样),
可运行的一样优先级的线程按Round-robin (轮流运行),然后又说了目标是实时而不是公平
内核不会自己改变thread 的优先级,可以通过系统调用(l4 kernel的)调整优先级,如果
希望公平调度,这个可以是用户级服务来完成这个任务
上面先放这,再转过来看下oklinux 的调度,首先一点可以肯定,对于普通进程,肯定还是使用
CFS调度,但l4 kernel 的thread 是实时调度,上面pdf中 Achieving fairness (if desired)
is the job of user-level servers 这句话可以看出要公平要自己(l4 user)来完成,
但如果把linux 的优先级设置到l4 thread 中,那肯定不行,这样就变成real time 调度了
从oklinux 进程创建的代码上看, 每个进程或者内核线程在创建l4 thread后,都调用
r = L4_Set_Priority(task_thread_info(p)->user_tid, 98); /* prio都设置为98 */
按上面pdf 中关于相同prio调度的描述:可运行的一样优先级的线程按Round-robin (轮流运行)
但实际并不是轮流运行的,仍然按照CFS 的调度算法运行,也就是说按nlinux 优先级运行
看下进程的切换就可以了解原因:
context_switch 开始,
switch_mm 什么也不用做(除fass外),不用切换页全局目录 ,
l4 thread 切换时,l4 kernel 会做这件事情
user 访问不到硬件mmu
switch_to =>__switch_to=> arch_switch
static inline struct thread_info* arch_switch(struct thread_info *prev, struct thread_info *next)
{
/* printk("switch to ip=%p, sp = %p\n", (void*)next->context.pc, (void*)next->context.sp); { */
register __u32 from asm("r0") = (__u32)prev;
register __u32 save asm("r1") = (__u32)&prev->context;
register __u32 rest asm("r2") = (__u32)&next->context;
__asm__ __volatile__ (
"add lr, pc, #4 \n"
"stmia %[save], {r4-r11, sp, lr} \n"
"ldmia %[rest], {r4-r11, sp, pc} \n"
: "+r" (from)
: [rest] "r" (rest), [save] "r" (save)
: "r3", "lr", "ip"
);
return (struct thread_info*)from;
/*}*/
}
按上前面关于进程创建中所说的,创建进程的单位是l4 thread ,那么切换
进程就是切换l4 thread ,那么应该call l4 thread 切换api ,比如thread_swith,
或者L4_Start_SpIp ,虽然进程创建是有arch_setup_ipsp,但其实只是保持ip,sp
到上下文,实际代码如上,却不是这样的,而是保存prev的上下文,恢复next上下文,
返回点根据add lr, pc, #4, 恢复时运行到return (struct thread_info*)from;
这句,看起来就和nlinux 基本相似, 这样做的原因就是为了解决
l4 kernel 调度和linux 调度的协调问题,解决的方法就是,l4 thread 作为运行单位
但并没有call thread start 去运行他(不运行就不会有l4 kernel 的调度),
而是通过linux 调度切换时,直接运行其地址来实现的
所以看上去很简单,基本没变化,这个也是虚拟化所要求的,如果改变很大,那么虚拟
一个系统很复杂,那估计选择的人就少更少了
修正一下:
上面讲的不完全对, 虽然没有thread_switch ,但是L4_Start_XXX 调用还是有的
但是L4_Start_SpIpFlags 在main thread中,因为main_thread的进程优先级别为99 ,高于user的98
所以L4_Start_Sp_Ip 不会引起main thread 让出执行权,
L4_Start_XXX 发生在两个点
1.Syscall loop 的
return_to_user:
...
if (user_need_restart(curinfo)) {
L4_Start_SpIpFlags
这之后prior 99的main_thread一l4 wait ,就切到L4_Start_SpIpFlags 设置的点去运行了
2. l4_work_pending_preempt 需要重调度的那个情况
如果需要重调度, 然后调用schede,返回时current 已经变了,然后L4_Start current
然后go to retry 回到l4_wait 去让出main thread 运行权,然后运行 前面L4_Start current
的那个
这样就全了
这样前面(不运行就不会有l4 kernel 的调度的讲法)就错了,那一运行,就同样98优先级的
就要被l4 kernel Round-robin了, 那么办法就是L4_Stop_xxx,由oklinux 调度来
L4_Stop_XXX 其他同一优先级别的thread,而选择oklinux 调度运行进程的l4 thread 来执行l4 api
l4_start_xxx 运行,这样也就受控了
L4_Stop_xxx的发生点:
1.new_process_handler 创建进程时copy thread设置的stub
任何新创建的进程的l4 thread 一出场就被停掉了,(l4 kernel 调度器没办法了)
2. 其他在signal 相关部分,以后分析siganl 的时候再分析