内核抢占

来源:互联网 发布:网络统考系统 编辑:程序博客网 时间:2024/05/16 11:23

内核抢占

即当进程位于内核空间时,有一个更高优先级的任务出现时,如果当前内核允许抢占,则可以将当前任务挂起,执行优先级更高的进程。抢占内核的主要特点是:一个在内核态运行的进程,可能在执行内核函数期间被另外一个进程取代。

举例说明抢占内核和非抢占内核的区别:

在进程A执行异常处理程序时(肯定是内核态),一个具有较高优先级的进程B变为可执行状态。如果内核是抢占的,就会发生强制性进程切换,让进程B取代进程A。异常处理程序的执行被暂停,直到调度程序再次选择进程A时才恢复它的执行。相反,如果内核是非抢占的,在进程A完成异常处理程序的执行之前是不会发生进程切换的,除非进程A主动放弃CPU。

在以下几种情况Linux内核不应该被抢占:

1、内核正在执行中断服务例程(也即中断上下文)。

2、内核正在进行中断上下文的Bottom Half(中断的底半部)处理(当内核正在执行软中断或tasklet时)。

3、内核的代码段正持有spinlock自旋锁、writelock/readlock读写锁等锁,处干这些锁的保护状态中。内核中的这些锁是为了在SMP系统中短时间内保证    不同CPU上运行的进程并发执行的正确性。当持有这些锁时,内核不应该被抢占,否则由于抢占将导致其他CPU长期不能获得锁而死等。目前这些锁在加锁    前就已经显式调用preempt_disable()禁止内核抢占。

4、内核正在对每个CPU“私有”的数据结构操作(Per-CPU date structures)。在SMP中,对于per-CPU数据结构未用spinlocks保护,因为这些数据结    构隐含地被保护了(不同的CPU有不一样的per-CPU数据,其他CPU上运行的进程不会用到另一个CPU的per-CPU数据)。但是如果允许抢占,但一个进程被    抢占后重新调度,有可能调度到其他的CPU上去,这时定义的Per-CPU变量就会有问题,这时应禁抢占。

处理抢占计数器的宏

preempt_count()      在thread_info描述符中选择preempt_count字段

preempt_disable()    使抢占计数器的值加1

preempt_enable_no_resched()  是抢占计数器的值减1

preempt_enable()     是抢占计数器的值减1,并在thread_info描述符的TIF_NEED_RESCHED标志被置为1的情况下,调用preempt_schedule()

get_cpu()            与preempt_disable()相似,但要返回本地CPU的数量

put_cpu()            与preempt_enable()相同

put_cpu_no_resched() 与preempt_enable_no_resched()相同

/* * this is is the entry point to schedule() from in-kernel preemption * off of preempt_enable.  Kernel preemptions off return from interrupt * occur there and call schedule directly. */asmlinkage void __sched preempt_schedule(void){struct thread_info *ti = current_thread_info();#ifdef CONFIG_PREEMPT_BKLstruct task_struct *task = current;int saved_lock_depth;#endif/* * If there is a non-zero preempt_count or interrupts are disabled, * we do not want to preempt the current task.  Just return.. */if (unlikely(ti->preempt_count || irqs_disabled()))return;need_resched:add_preempt_count(PREEMPT_ACTIVE);/* * We keep the big kernel semaphore locked, but we * clear ->lock_depth so that schedule() doesnt * auto-release the semaphore: */#ifdef CONFIG_PREEMPT_BKLsaved_lock_depth = task->lock_depth;task->lock_depth = -1;#endifschedule();#ifdef CONFIG_PREEMPT_BKLtask->lock_depth = saved_lock_depth;#endifsub_preempt_count(PREEMPT_ACTIVE);/* we could miss a preemption opportunity between schedule and now */barrier();if (unlikely(test_thread_flag(TIF_NEED_RESCHED)))goto need_resched;}#define preempt_check_resched() \do { \if (unlikely(test_thread_flag(TIF_NEED_RESCHED))) \preempt_schedule(); \} while (0)#define preempt_enable() \do { \preempt_enable_no_resched(); \preempt_check_resched(); \} while (0)
preempt_schedule()会检查是否允许本地中断,以及当前进程的preempt_count字段是否为0,如果两个条件都为真,它就调用schedule()选择另外一个进程来运行。

内核抢占时机

1、内核抢占可能在结束内核控制路径(通常是一个中断处理程序)时发生

2、当内核代码再一次具有可抢占性的时候,如解锁及使能软中断等


交叉内核控制路径使内核开发者的工作变得复杂了:必须特别小心地识别出异常处理程序、中断处理程序、可延迟函数和内核线程中的临界区。一旦临界区被确定,就必须对其采用适当的保护措施,以确保在任意时刻只有一个内核控制路径处于临界区。

0 0