本文共 4632 字,大约阅读时间需要 15 分钟。
转自:
一、什么叫抢占
所谓抢占,说白了就是进程切换。 linux的用户空间,进程A在执行中,来(硬?)中断打断A,从中断处理程序返回时,如果有更高优先级进程B在排队的话,那么执行进程B。 用户空间下进程总是可抢占的 在linux的内核空间就不一定了,linux 2.4是不可抢占的,实时性就会降低,如下面这个样子:二、抢占的API preempt_enable() 开启抢占 preempt_disable() 禁止抢占 内核中每个进程数据结构里有一个计数器preempt_count 抢占的开启与禁止,操作当前进程的preempt_count 内核在进行进程调度的时候,只要prempt_count为0,内核就可以进行抢占。 struct thread_info { struct task_struct *task; /* main task structure */ ............//省略 int cpu; /* cpu we're on */ int preempt_count; /* 0 => preemptable, <0 => BUG */ }; #define preempt_enable() \ do { \ preempt_enable_no_resched(); \ barrier(); \ preempt_check_resched(); \ } while (0) #define preempt_disable() \ do { \ inc_preempt_count(); \ barrier(); \ } while (0) #define preempt_enable_no_resched() \ do { \ barrier(); \ dec_preempt_count(); \ } while (0) #define inc_preempt_count() add_preempt_count(1) #define dec_preempt_count() sub_preempt_count(1) #define add_preempt_count(val) do { preempt_count() += (val); } while (0) #define sub_preempt_count(val) do { preempt_count() -= (val); } while (0) #define preempt_count() (current_thread_info()->preempt_count)三、发生抢占的时机 linux进程调度的核心函数是 schedule(),进程调度就是在这里做的。 schedule的调用分为主动调用和被动调用。 主动调用是指内核显示的直接去调用shedule(),如当前进程调用了可休眠函数,里面会调用schedule 被动调用是指在系统调用、中断处理或异常处理结束之后,由相应的回调函数调用schedule 判断完当前进程是否可抢占,才会接着去调用schedule() 只看了看中断返回时schedule被动调用的情况 至于主动调用的地方就太多了,什么进程结束,pause等等,没耐心看了。。。3.1 从中断返回时 首先是从中断处理程序do_IRQ()返回后,会调用ret_from_except() (看) ret_from_except()里要先check一下,判定前面被中断的执行体是运行在用户空间还是内核空间, 在决定返回到用户空间或内核空间 用户空间的话:(现在知道为什么用户空间程序总是可抢占了吧) ret_from_except --> user_exc_return --> do_work --> 调用 do_signal 和 schedule 内核空间的话:(编译内核时要打开可抢占选项才行) ret_form_except --> resume_kernel --> preempt_schedule_irq --> schedule.globl ret_from_except
ret_from_except: LOAD_MSR_KERNEL(r10,MSR_KERNEL) //将MSR_KERNEL常量设置到MSR,以禁止外部中断 SYNC //Some chip revs have problems here... MTMSRD(r10) //disable interrupts lwz r3,_MSR(r1) //读栈中的MSR[PR],Returning to user mode? andi. r0,r3,MSR_PR beq resume_kernel user_exc_return: //r10 contains MSR_KERNEL here rlwinm r9,r1,0,0,(31-THREAD_SHIFT) //Check current_thread_info()->flags lwz r9,TI_FLAGS(r9) andi. r0,r9,(_TIF_SIGPENDING|_TIF_RESTORE_SIGMASK|_TIF_NEED_RESCHED) bne do_work restore_user: #ifdef CONFIG_PREEMPT b restore resume_kernel: rlwinm r9,r1,0,0,(31-THREAD_SHIFT) /* check current_thread_info->preempt_count */ lwz r0,TI_PREEMPT(r9) cmpwi 0,r0,0 /* if non-zero, just restore regs and return */ bne restore lwz r0,TI_FLAGS(r9) andi. r0,r0,_TIF_NEED_RESCHED beq+ restore andi. r0,r3,MSR_EE /* interrupts off? */ beq restore /* don't schedule if so */ 1: bl preempt_schedule_irq rlwinm r9,r1,0,0,(31-THREAD_SHIFT) lwz r3,TI_FLAGS(r9) andi. r0,r3,_TIF_NEED_RESCHED bne- 1b #else resume_kernel: #endif /* CONFIG_PREEMPT */ do_work: /* r10 contains MSR_KERNEL here */ andi. r0,r9,_TIF_NEED_RESCHED beq do_user_signal do_resched: /* r10 contains MSR_KERNEL here */ ori r10,r10,MSR_EE SYNC MTMSRD(r10) /* hard-enable interrupts */ bl schedule recheck: LOAD_MSR_KERNEL(r10,MSR_KERNEL) SYNC MTMSRD(r10) /* disable interrupts */ rlwinm r9,r1,0,0,(31-THREAD_SHIFT) lwz r9,TI_FLAGS(r9) andi. r0,r9,_TIF_NEED_RESCHED bne- do_reschedandi. r0,r9,_TIF_SIGPENDING beq restore_user do_user_signal: /* r10 contains MSR_KERNEL here */ asmlinkage void __sched preempt_schedule_irq(void){ struct thread_info *ti = current_thread_info(); BUG_ON(ti->preempt_count || !irqs_disabled()); do { add_preempt_count(PREEMPT_ACTIVE); local_irq_enable(); schedule(); local_irq_disable(); sub_preempt_count(PREEMPT_ACTIVE); barrier(); } while (unlikely(test_thread_flag(TIF_NEED_RESCHED))); } asmlinkage void __sched preempt_schedule(void){ struct thread_info *ti = current_thread_info(); //preempt_cout非0的话,就不调用schedule if (likely(ti->preempt_count || irqs_disabled())) return; do { add_preempt_count(PREEMPT_ACTIVE); schedule(); sub_preempt_count(PREEMPT_ACTIVE); barrier(); } while (unlikely(test_thread_flag(TIF_NEED_RESCHED))); }#########################################################################################; 内核中的执行路径主要有: 1 用户进程的内核态,此时有进程context,主要是代表进程在执行系统调用等。 还包括,内核中自己的进程,如 ksoftirqd 等等 2 中断或者异常或者自陷等,从概念上说,此时没有进程context,不能进行context switch。 3 bottom_half,从概念上说,此时也没有进程context。 4 同时,相同的执行路径还可能在其他的CPU上运行。 Linux2.6中网络代码中的preempt_enable/disable移到softirqd调用的地方原因是这样的. 一、部分softirq是isr处理之后调用的, 对于这部分代码,由于是在底半处理中运行,必须是是在运行进程系统调用之前返回的. 所以实际上preempt_disable(); preempt_enable();代码对于他们来说是没有意义的. 二、部分softirq是在ksoftirqd的内核线程运行的, 因为这个相当于运行在进程的内核空间,由于软中断都是对中断上半部的继续, 所以这些工作都需要尽快的完成.所以在softirqd运行的时候,禁止了preempt, 这样就可以保证softirq运行完之后才会调度下一个进程,因为softirq里面的所有函数都不会睡眠.本文转自张昺华-sky博客园博客,原文链接:http://www.cnblogs.com/sky-heaven/p/5391887.html,如需转载请自行联系原作者