由于内核是抢占性的,内核中的进程在任何时刻都可能停下来以便另一个具有更高优先级的进程运行。这意味着一个任务与被抢占的任务可能会在同一个临界区内运行。为了避免这样的情况,内核抢占代码使用自旋锁作为非抢占区域的标记。如果一个自旋锁被持有,内核便不能进行抢占。因为内核抢占和SMP面对相同的并发问题,并且内核已经是SMP安全的,因此,这种简单的变化使得内核也是抢占安全的。
实际中,某些情况并不需要自旋锁,但是仍然需要关闭内核抢占。出现得最频繁的情况就是每个处理器上的数据。如果数据对每个处理器是唯一的,那么这样的数据可能就不需要使用锁来保护,因为数据只能被一个处理器访问。如果自旋锁没有被持有,内核有是抢占式的,那么一个新调度的任务就可能访问同一个变量。为了解决这个问题,可以通过preempt_disable()禁止内核抢占。这是一个可以嵌套调用的函数,可以调用任意次。每次调用都必须有一个相应的preempt_enable()调用。当最后一次preempt_enable()被调用后,内核抢占才重新启用。
- /*
- * include/linux/preempt.h - macros for accessing and manipulating
- * preempt_count (used for kernel preemption, interrupt count, etc.)
- */
- #ifdef CONFIG_PREEMPT
- asmlinkage void preempt_schedule(void);
- #define preempt_disable() /
- do { /
- inc_preempt_count(); /
- barrier(); /
- } while (0)
- #define preempt_enable_no_resched() /
- do { /
- barrier(); /
- dec_preempt_count(); /
- } while (0)
- #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(); /
- barrier(); /
- preempt_check_resched(); /
- } while (0)
- #else
- #define preempt_disable() do { } while (0)
- #define preempt_enable_no_resched() do { } while (0)
- #define preempt_enable() do { } while (0)
- #define preempt_check_resched() do { } while (0)
- #endif
抢占计数存放着被持有锁的数量和preempt_disable()的调度次数,如果计数是0,那么内核可以进行抢占,如果为1或更大的值,那么内核就不会进行抢占。这个计数非常有用,它是一种对原子操作和睡眠很有效的调试方法。函数preempt_count()返回这个值。
- /*
- * include/linux/preempt.h - macros for accessing and manipulating
- * preempt_count (used for kernel preemption, interrupt count, etc.)
- */
- #ifdef CONFIG_DEBUG_PREEMPT
- extern void fastcall add_preempt_count(int val);
- extern void fastcall sub_preempt_count(int val);
- #else
- # define add_preempt_count(val) do { preempt_count() += (val); } while (0)
- # define sub_preempt_count(val) do { preempt_count() -= (val); } while (0)
- #endif
- #define inc_preempt_count() add_preempt_count(1)
- #define dec_preempt_count() sub_preempt_count(1)
- #define preempt_count() (current_thread_info()->preempt_count)
- 在<Thread_info.h(include/asm-i386)>中
- struct thread_info {
- struct task_struct *task; /* main task structure */
- struct exec_domain *exec_domain; /* execution domain */
- unsigned long flags; /* low level flags */
- unsigned long status; /* thread-synchronous flags */
- __u32 cpu; /* current CPU */
- int preempt_count; /* 0 => preemptable, <0 => BUG */
- mm_segment_t addr_limit; /* thread address space:
- 0-0xBFFFFFFF for user-thead
- 0-0xFFFFFFFF for kernel-thread
- */
- void *sysenter_return;
- struct restart_block restart_block;
- unsigned long previous_esp; /* ESP of the previous stack in case
- of nested (IRQ) stacks
- */
- __u8 supervisor_stack[0];
搞定!