lock_kernel()

 

In the 2.6 Linux kernel, the default configuration is to have a preemptible kernel. A preemptible kernel means that the kernel itself can be interrupted by a higher priority task, such as a hardware interrupt, and control is passed to the higher priority task. The kernel must save enough state so that it can return to executing when the higher priority task finishes.

Early versions of Linux implemented kernel preemption and SMP locking by using the Big Kernel Lock (BKL). Later versions of Linux correctly abstracted preemption into various calls, such as preempt_disable(). The BKL is still with us in the initialization process. It is a recursive spinlock that can be taken several times by a given CPU. A side effect of using the BKL is that it disables preemption, which is an important side effect during initialization.

Locking the kernel prevents it from being interrupted or preempted by any other task. Linux uses the BKL to do this. When the kernel is locked, no other process can execute. This is the antithesis of a preemptible kernel that can be interrupted at any point. In the 2.6 Linux kernel, we use the BKL to lock the kernel upon startup and initialize the various kernel objects without fear of being interrupted. The kernel is unlocked on line 493 within the rest_init() function. Thus, all of start_kernel() occurs with the kernels locked. Let's look at what happens in lock_kernel():

#define lock_kernel() do {                    \
    _lock_kernel(__func__, __FILE__, __LINE__);        \
} while (0)



/*
 * Getting the big kernel lock.
 *
 * This cannot happen asynchronously, so we only need to
 * worry about other CPU's.
 */
void __lockfunc _lock_kernel(const char *func, const char *file, int line)
{
    int depth = current->lock_depth + 1;

    trace_lock_kernel(func, file, line);

    if (likely(!depth)) {
        might_sleep();
        __lock_kernel();
    }
    current->lock_depth = depth;
}




/*
 * These are the BKL spinlocks - we try to be polite about preemption.
 * If SMP is not on (ie UP preemption), this all goes away because the
 * do_raw_spin_trylock() will always succeed.
 */
#ifdef CONFIG_PREEMPT
static inline void __lock_kernel(void)
{
    preempt_disable();
    if (unlikely(!do_raw_spin_trylock(&kernel_flag))) {
        /*
         * If preemption was disabled even before this
         * was called, there's nothing we can be polite
         * about - just spin.
         */
        if (preempt_count() > 1) {
            do_raw_spin_lock(&kernel_flag);
            return;
        }

        /*
         * Otherwise, let's wait for the kernel lock
         * with preemption enabled..
         */
        do {
            preempt_enable();
            while (raw_spin_is_locked(&kernel_flag))
                cpu_relax();
            preempt_disable();
        } while (!do_raw_spin_trylock(&kernel_flag));
    }
}

#else

/*
 * Non-preemption case - just get the spinlock
 */
static inline void __lock_kernel(void)
{
    do_raw_spin_lock(&kernel_flag);
}
#endif

static inline void __unlock_kernel(void)
{
    /*
     * the BKL is not covered by lockdep, so we open-code the
     * unlocking sequence (and thus avoid the dep-chain ops):
     */
    do_raw_spin_unlock(&kernel_flag);
    preempt_enable();
}



Kernel lock kernel_flag defined in kernel_lock.c

/*
 * The 'big kernel lock'
 *
 * This spinlock is taken and released recursively by lock_kernel()
 * and unlock_kernel(). It is transparently dropped and reacquired
 * over schedule(). It is used to protect legacy code that hasn't
 * been migrated to a proper locking design yet.
 *
 * Don't use in new code.
 */
static __cacheline_aligned_in_smp DEFINE_RAW_SPINLOCK(kernel_flag);


作者: letmego163   发布时间: 2010-10-15