内核如何为不同的请求提供服务
内核看作是必须满足两种不同请求的侍者:一种来自顾客,一种来自数量有限的老板,策略如下:
- 老板提出请求时,如果侍者空闲,则侍者开始为老板服务
- 如果老板提出请求时侍者正为顾客服务,那么侍者停止为老板服务,开始为老板服务
- 如果一个老板提出请求时侍者正为另一个老板服务,那么侍者停止为第一个老板服务,为第二个老板服务,服务完毕继续为第一个老板服务
- 一个老板可能命令侍者停止正为顾客提供的服务,侍者完成对老板最近请求后,可能会暂时不理会原来的顾客而为新的顾客服务。
内核抢占
无论在抢占内核还是非抢占内核中,运行在内核态的进程都可以自动放弃CPU,比如,进程由于等待资源不得不转入睡眠状态,我们将这种切换称为计划性进程切换。抢占式内核在响应引起进程切换的异步事件的方式上与非抢占内核不同,我们把这种进程切换叫做强制性进程切换。
所有的进程切换都是由宏switch_to完成的。
抢占内核的特点是:一个在内核态运行的进程,可能在执行内核函数期间被另一个进程取代。
使内核可抢占的目的是减少用户态进程的分派延迟(dispatch latency),即从进程变为可执行状态到他实际开始运行之间的时间间隔。
thread_info描述符的preempt_count字段大于0时,就禁止内核抢占。在如下三种情况中,取值都会大于0:
- 内核正在执行中断服务例程
- 可延迟函数被禁止
- 通过把抢占式计数器设置为正数而显示的禁用内核抢占
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相同
#define preempt_count() (current_thread_info()->preempt_count)
#define preempt_disable() \
do { \
inc_preempt_count(); \
barrier(); \
} while (0)
#define preempt_enable_no_resched() \
do { \
barrier(); \
dec_preempt_count(); \
} while (0)
#define preempt_enable() \
do { \
preempt_enable_no_resched(); \
preempt_check_resched(); \
} while (0)
#define preempt_check_resched() \
do { \
if (unlikely(test_thread_flag(TIF_NEED_RESCHED))) \
preempt_schedule(); \
} while (0)
#define get_cpu() ({ preempt_disable(); smp_processor_id(); })
#define put_cpu() preempt_enable()
#define put_cpu_no_resched() preempt_enable_no_resched()
asmlinkage void __sched preempt_schedule(void)
{
struct thread_info *ti = current_thread_info();
#ifdef CONFIG_PREEMPT_BKL
struct 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_BKL
saved_lock_depth = task->lock_depth;
task->lock_depth = -1;
#endif
schedule();
#ifdef CONFIG_PREEMPT_BKL
task->lock_depth = saved_lock_depth;
#endif
sub_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;
}
临界区是 一段代码,在其他的内核控制路径能够进入临界区前,进入临界区的内核控制路径必须全部执行完这段代码。
什么时候同步是不必要的
中断处理程序和tasklet不必编写成可重入的函数
仅被软中断和tasklet访问的每CPU变量不需要同步。
仅被一种tasklet访问的数据结构不需要同步。
同步原语
技术 | 说明 | 适用范围 |
每CPU变量 | 在CPU之间复制数据结构 | 所有CPU |
原子操作 | 对一个计数器原子的“读-修改-写”的指令 | 所有CPU |
内存屏障 | 避免指令重新排序 | 本地CPU或所有CPU |
自旋锁 | 加锁时忙等 | 所有CPU |
信号量 | 加锁时阻塞等待(睡眠) | 所有CPU |
顺序锁 | 基于访问计数器的锁 | 所有CPU |
本地中断的禁止 | 禁止单个CPU上的中断处理 | 本地CPU |
本地软中断的禁止 | 禁止单个CPU上的可延迟函数处理 | 本地CPU |
读-拷贝-更新(RCU) | 通过指针而不是锁来访问共享数据结构 | 所有CPU |
#define DEFINE_PER_CPU(type, name) \//静态分配一个每CPU数组,数组名为name,结构类型为type
__attribute__((__section__(".data.percpu"))) __typeof__(type) per_cpu__##name
#define __get_cpu_var(var) per_cpu(var, smp_processor_id())//选择每CPU数组var的本地CPU元素
#define get_cpu_var(var) (*({ preempt_disable(); &__get_cpu_var(var); }))//先禁用内核抢占,然后在每CPU数组var中,为本地CPU选择元素
#define put_cpu_var(var) preempt_enable()//启用内核抢占
#define alloc_percpu(type) \//动态分配type类型数据结构的每CPU数组,并返回他的地址
((type *)(__alloc_percpu(sizeof(type), __alignof__(type))))
static inline void *__alloc_percpu(size_t size, size_t align)
{
void *ret = kmalloc(size, GFP_KERNEL);
if (ret)
memset(ret, 0, size);
return ret;
}
static inline void free_percpu(const void *ptr)//释放动态分配的每CPU数组,ptr指示其地址
{
kfree(ptr);
}
#define per_cpu_ptr(ptr, cpu) \//返回每CPU数组中与参数cpu对应的CPU元素地址,参数ptr给出数组地址
({ \
struct percpu_data *__p = (struct percpu_data *)~(unsigned long)(ptr); \
(__typeof__(ptr))__p->ptrs[(cpu)]; \
})
#define per_cpu(var, cpu) (*RELOC_HIDE(&per_cpu__##var, __per_cpu_offset[cpu]))//为CPU选择一个每CPU数组元素,CPU由参数cpu指定,数组名称为var
- 进行零次或一次对齐内存访问的汇编指令是原子的
- 如果在读操作之后、写操作之前没有其他处理器占用内存总线,那么从内存中读取数据、更新数据并把更新后的数据写回内存中的这些“读-修改-写”汇编指令是原子。
- 操作码前缀是lock字节的“读-修改-写”汇编语言指令在多处理器系统中是原子的。
- 操作码前缀是一个rep字节的汇编语言指令不是原子的
#define atomic_read(v) ((v)->counter)
#define atomic_set(v,i) (((v)->counter) = (i))
static __inline__ void atomic_add(int i, atomic_t *v)
{
__asm__ __volatile__(
LOCK "addl %1,%0"
:"=m" (v->counter)
:"ir" (i), "m" (v->counter));
}
static __inline__ void atomic_sub(int i, atomic_t *v)
{
__asm__ __volatile__(
LOCK "subl %1,%0"
:"=m" (v->counter)
:"ir" (i), "m" (v->counter));
}
static __inline__ int atomic_sub_and_test(int i, atomic_t *v)
{
unsigned char c;
__asm__ __volatile__(
LOCK "subl %2,%0; sete %1"
:"=m" (v->counter), "=qm" (c)
:"ir" (i), "m" (v->counter) : "memory");
return c;
}
static __inline__ void atomic_inc(atomic_t *v)
{
__asm__ __volatile__(
LOCK "incl %0"
:"=m" (v->counter)
:"m" (v->counter));
}
static __inline__ void atomic_dec(atomic_t *v)
{
__asm__ __volatile__(
LOCK "decl %0"
:"=m" (v->counter)
:"m" (v->counter));
}
static __inline__ int atomic_dec_and_test(atomic_t *v)
{
unsigned char c;
__asm__ __volatile__(
LOCK "decl %0; sete %1"
:"=m" (v->counter), "=qm" (c)
:"m" (v->counter) : "memory");
return c != 0;
}
static __inline__ int atomic_inc_and_test(atomic_t *v)
{
unsigned char c;
__asm__ __volatile__(
LOCK "incl %0; sete %1"
:"=m" (v->counter), "=qm" (c)
:"m" (v->counter) : "memory");
return c != 0;
}
static __inline__ int atomic_add_negative(int i, atomic_t *v)
{
unsigned char c;
__asm__ __volatile__(
LOCK "addl %2,%0; sets %1"
:"=m" (v->counter), "=qm" (c)
:"ir" (i), "m" (v->counter) : "memory");
return c;
}
static __inline__ int atomic_add_return(int i, atomic_t *v)
{
int __i;
#ifdef CONFIG_M386
if(unlikely(boot_cpu_data.x86==3))
goto no_xadd;
#endif
/* Modern 486+ processor */
__i = i;
__asm__ __volatile__(
LOCK "xaddl %0, %1;"
:"=r"(i)
:"m"(v->counter), "0"(i));
return i + __i;
#ifdef CONFIG_M386
no_xadd: /* Legacy 386 processor */
local_irq_disable();
__i = atomic_read(v);
atomic_set(v, i + __i);
local_irq_enable();
return i + __i;
#endif
}
static __inline__ int atomic_sub_return(int i, atomic_t *v)
{
return atomic_add_return(-i,v);
}
#define atomic_inc_return(v) (atomic_add_return(1,v))
#define atomic_dec_return(v) (atomic_sub_return(1,v))
static inline int constant_test_bit(int nr, const volatile unsigned long *addr)
{
return ((1UL << (nr & 31)) & (addr[nr >> 5])) != 0;
}
static inline int variable_test_bit(int nr, const volatile unsigned long * addr)
{
int oldbit;
__asm__ __volatile__(
"btl %2,%1\n\tsbbl %0,%0"
:"=r" (oldbit)
:"m" (ADDR),"Ir" (nr));
return oldbit;
}
#define barrier() __asm__ __volatile__("": : :"memory")
volatile关键字禁止编译器把asm指令与程序中的其他指令重新组合。memory关键字强制编译器假定RAM中的所有内存单元已经被汇编语言指令修改。
- 对I/O端口进行操作的所有指令
- 有lock前缀的所有指令
- 写控制寄存器、系统寄存器或调试寄存器的所有指令
- 在Pentium 4微处理器中引入的汇编语言指令lfence、sfence和mfence,有效的实现了读内存屏障、写内存屏障和读-写内存屏障。
- 少数专门的汇编语言指令,终止中断处理程序或异常处理程序的iret指令
#define mb() alternative("lock; addl $0,0(%%esp)", "mfence", X86_FEATURE_XMM2)
#define rmb() alternative("lock; addl $0,0(%%esp)", "lfence", X86_FEATURE_XMM2)
#ifdef CONFIG_X86_OOSTORE
/* Actually there are no OOO store capable CPUs for now that do SSE,
but make it already an possibility. */
#define wmb() alternative("lock; addl $0,0(%%esp)", "sfence", X86_FEATURE_XMM)
#else
#define wmb() __asm__ __volatile__ ("": : :"memory")
#endif
#ifdef CONFIG_SMP
#define smp_mb() mb()
#define smp_rmb() rmb()
#define smp_wmb() wmb()
#endif
mb,rmb,wmb,适用于MP和UP的内存屏障,读内存屏障,写内存屏障。
typedef struct {
volatile unsigned int slock;//表示自旋锁的状态,值为1表示“未加锁”状态,任何负数和0表示“加锁”状态
#ifdef CONFIG_DEBUG_SPINLOCK
unsigned magic;
#endif
#ifdef CONFIG_PREEMPT
unsigned int break_lock;//表示进程正在忙等自旋锁(只在SMP和内核抢占的情况下使用这个标志)
#endif
} spinlock_t;
自旋锁宏
#define SPIN_LOCK_UNLOCKED (spinlock_t) { 1 SPINLOCK_MAGIC_INIT }
#define spin_lock_init(x) do { *(x) = SPIN_LOCK_UNLOCKED; } while(0)//把自旋锁置为1(未锁)
#define spin_lock(lock) _spin_lock(lock)//循环,直到自旋锁变为1,然后把自旋锁置为0
#define _spin_lock(lock) \
do { \
preempt_disable(); \/* 禁用内核抢占 */
_raw_spin_lock(lock); \
__acquire(lock); \
} while(0)
#define spin_unlock(lock) _spin_unlock(lock)//把自旋锁置为1
#define _spin_unlock(lock) \
do { \
_raw_spin_unlock(lock); \
preempt_enable(); \
__release(lock); \
} while (0)
#define spin_unlock_wait(x) do { barrier(); } while(spin_is_locked(x))//等待,直到自旋锁变为1
#define spin_is_locked(x) (*(volatile signed char *)(&(x)->slock) <= 0)//如果自旋锁巍被置为1,返回0,否则,返回1
#define spin_trylock(lock) __cond_lock(_spin_trylock(lock))//把自旋锁置为0,如果原来锁的值是1,则返回1,否则,返回0
#define _spin_trylock(lock) ({preempt_disable(); _raw_spin_trylock(lock) ? \
1 : ({preempt_enable(); 0;});})
具有内核抢占的spin_lock宏
_raw_spin_lock,对自旋锁的slock字段执行原子性的测试和设置操作,该函数等价于下列汇编语言片段:
movb $0, %a1
xchgb %a1, slp->slock
xchg指令原子性的交换8位寄存器%a1和slp->slock指示的内存单元的内容。
读写自旋锁
这个锁的引入是为了增加内核的并发能力,只要没有对数据结构进行修改,读/写自旋锁就允许多个内核控制路径同时读同一数据结构。如果内核控制路径想对这个结构进行修改,必须首先获得写锁,写锁授权独占这个资源。
typedef struct {
volatile unsigned int lock;
#ifdef CONFIG_DEBUG_SPINLOCK
unsigned magic;
#endif
#ifdef CONFIG_PREEMPT
unsigned int break_lock;
#endif
} rwlock_t;
每个读写锁都是一个rwlock_t结构,lock字段是一个32位的字段,分为两个不同的部分:
- 24位计数器,表示对受保护的数据结构并发的进行读操作的内核控制路径的书目。存放在字段的0~23位。
- “未锁”标志字段,当没有内核控制路径在读或写时设置该位,否则清0.这个标志在第24位。
#define RW_LOCK_UNLOCKED (rwlock_t) { RW_LOCK_BIAS RWLOCK_MAGIC_INIT }
#define rwlock_init(x) do { *(x) = RW_LOCK_UNLOCKED; } while(0)
#define RW_LOCK_BIAS 0x01000000
如果自旋锁为空,那么lock字段的值为0x1000000,如果写者已经获得自旋锁,那么lock字段为0x00000000,如果一个、两个读者获取了自旋锁,那么lock字段的值为读者个数,读者个数的二进制补码在0~23位。
为读获取和释放一个锁
read_lock宏实现为读获取锁,
为写获取和释放一个锁
顺序锁
读写自旋锁,读/写锁具有相同的优先级,读者必须等待写锁,同样,写者必须等待读锁。linux2.6引入了顺序锁,只是为写者赋予了较高的优先级。这样做的优点是写者永远不用等待读锁,缺点是读者不得不反复读相同的数据直到获取有效的副本。
每个顺序锁是包括两个字段的seqlock_t结构:
typedef struct {
unsigned sequence;//顺序计数器,每个读者必须在读数据前后读顺序计数器,并检查值,如果不同,则暗示读者刚才读到的值是无效的
spinlock_t lock;
} seqlock_t;
#define SEQLOCK_UNLOCKED { 0, SPIN_LOCK_UNLOCKED }
#define seqlock_init(x) do { *(x) = (seqlock_t) SEQLOCK_UNLOCKED; } while (0)
通过seqlock_init把seqlock_t初始化为“未上锁”。
写者通过write_seqlock和write_sequnlock获取和释放顺序锁。
static inline void write_seqlock(seqlock_t *sl)
{
spin_lock(&sl->lock);
++sl->sequence;
smp_wmb();
}
static inline void write_sequnlock(seqlock_t *sl)
{
smp_wmb();
sl->sequence++;
spin_unlock(&sl->lock);
}
write_seqlock和write_sequnlock都增加计数器,保证写者在写的过程中,计数器的值是奇数,并且在没有写者在改变数据的时候,计数器的值是偶数。
读者调用read_seqbegin返回顺序锁的当前顺序号,如果是奇数或seq的值与顺序锁的顺序计数器的当前值不匹配,read_seqretry就返回1。
static inline unsigned read_seqbegin(const seqlock_t *sl)
{
unsigned ret = sl->sequence;
smp_rmb();
return ret;
}
static inline int read_seqretry(const seqlock_t *sl, unsigned iv)
{
smp_rmb();
return (iv & 1) | (sl->sequence ^ iv);
}
注意,读者进入临界区时,不必禁用内核抢占;写者由于获取自旋锁,所以它进入临界区时自动禁用内核抢占。
读-拷贝-更新(RCU)
这是为了保护在多数情况下被多个CPU读的数据结构而设计的一种同步技术。RCU允许多个读者和写者并发执行。RCU是不使用锁的。
RCU如何不使用动态数据结构而实现多个CPU同步?关键的思想包括限制RCU的范围,如下:
- RCU只保护被动态分配并通过指针引用的数据结构
- 在被RCU保护的临界区中,任何内核控制路径都不能睡眠。
写者修改指针时,不能立即释放数据结构的旧副本。只有在CPU上的所有读者都执行完rcu_read_unlock宏之后 ,才可以释放旧副本。写者通过call_rcu来释放数据结构的旧副本。
void fastcall call_rcu(struct rcu_head *head,
void (*func)(struct rcu_head *rcu))
{
unsigned long flags;
struct rcu_data *rdp;
head->func = func;
head->next = NULL;
local_irq_save(flags);
rdp = &__get_cpu_var(rcu_data);
*rdp->nxttail = head;
rdp->nxttail = &head->next;
local_irq_restore(flags);
}
RCU是linux2.6中新加的功能,用在网络层和虚拟文件系统中。
信号量
实际上,linux提供了两种信号量:
- 内核信号量,由内核控制路径使用
- system V IPC 信号量,由用户态进程使用
struct semaphore {
atomic_t count;//如果该值大于0,资源就是空闲的,如果是0,则信号量是忙的,但是没有等待进程;如果是负数,资源不可用,至少有一个进程等待资源
int sleepers;//表示是否有一些进程在信号量上睡眠。
wait_queue_head_t wait;//存放等待队列链表的地址,当前等待资源的所有睡眠进程都放在这个链表中。如果count大于或等于0,这个链表为空
};
static inline void sema_init (struct semaphore *sem, int val)
{
atomic_set(&sem->count, val);
sem->sleepers = 0;
init_waitqueue_head(&sem->wait);
}
static inline void init_MUTEX (struct semaphore *sem)//初始化互斥访问所需的信号量为空闲
{
sema_init(sem, 1);
}
static inline void init_MUTEX_LOCKED (struct semaphore *sem)初始化互斥访问信号为忙
{
sema_init(sem, 0);
}
struct rw_semaphore {
signed long count;//存放两个16位的计数器,高16位以二进制补码形式存放非等待写者进程的总数和等待内核控制路径数。低16存放非等待的读者和写者进程的总数。
#define RWSEM_UNLOCKED_VALUE 0x00000000
#define RWSEM_ACTIVE_BIAS 0x00000001
#define RWSEM_ACTIVE_MASK 0x0000ffff
#define RWSEM_WAITING_BIAS (-0x00010000)
#define RWSEM_ACTIVE_READ_BIAS RWSEM_ACTIVE_BIAS
#define RWSEM_ACTIVE_WRITE_BIAS (RWSEM_WAITING_BIAS + RWSEM_ACTIVE_BIAS)
spinlock_t wait_lock;
struct list_head wait_list;//指向等待进程的链表。链表中的每个元素都是一个rwsem_waiter结构
#if RWSEM_DEBUG
int debug;
#endif
};
每个读/写信号量都是由rw_semaphore结构描述的。
struct rwsem_waiter {
struct list_head list;
struct task_struct *task;//指针指向睡眠进程的描述符
unsigned int flags;//标志表示进程是为读需要信号还是为写需要信号量
#define RWSEM_WAITING_FOR_READ 0x00000001
#define RWSEM_WAITING_FOR_WRITE 0x00000002
};
static inline void init_rwsem(struct rw_semaphore *sem)
{
sem->count = RWSEM_UNLOCKED_VALUE;
spin_lock_init(&sem->wait_lock);
INIT_LIST_HEAD(&sem->wait_list);
#if RWSEM_DEBUG
sem->debug = 0;
#endif
}
init_rwsem函数初始化rw_semaphore结构,把count置为0,初始化wait_lock为未锁,把wait_list置为空链表。
down_read和down_write函数分别为读或写获取读/写信号量,同样,up_read, up_write为读和写释放以前获取的读/写信号量。down_read_trylock和down_write_trylock函数分别类似于down_read和down_write,不同的是,信号量忙的情况下,不会阻塞进程。downgrade_write把写锁转换成读锁。
补充原语
为了解决多处理器上,up和down在同一信号量的并发执行,实现了补充。completion结构体:
truct completion {
unsigned int done;
wait_queue_head_t wait;
};
与up对应的函数叫做complete()
void fastcall complete(struct completion *x)
{
unsigned long flags;
spin_lock_irqsave(&x->wait.lock, flags);//自旋锁调用,
x->done++;//递增done字段
__wake_up_common(&x->wait, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE,
1, 0, NULL);//唤醒wait等待队列上睡眠的互斥进程
spin_unlock_irqrestore(&x->wait.lock, flags);
}
与down对应的函数是wait_for_completion。
void fastcall __sched wait_for_completion(struct completion *x)
{
might_sleep();
spin_lock_irq(&x->wait.lock);
if (!x->done) {//如果done的值大于0,就终止,说明complete已经在另一个CPU上运行。
DECLARE_WAITQUEUE(wait, current);
wait.flags |= WQ_FLAG_EXCLUSIVE;
__add_wait_queue_tail(&x->wait, &wait);
do {
__set_current_state(TASK_UNINTERRUPTIBLE);
spin_unlock_irq(&x->wait.lock);
schedule();
spin_lock_irq(&x->wait.lock);
} while (!x->done);
__remove_wait_queue(&x->wait, &wait);
}
x->done--;
spin_unlock_irq(&x->wait.lock);
}
禁止本地中断
确保一组内核语句被当作一个临界区处理的主要机制之一就是中断禁止。在多处理器系统上,禁止本地中断经常与自旋锁结合使用。
保存和恢复eflags的内容是通过local_irq_save和local_irq_restore来实现的。
禁止和激活可延迟函数
宏local_bh_disable给本地的软中断计数器加1,而函数local_bh_enable从本地CPU的中断计数器减1,
对内核数据的同步访问
内核开发者采用下述由经验得到的法则:把系统中的并发度保持在尽可能高的程度。
系统中的并发度取决于两个主要因素:
- 同时运转的I/O设备数
- 进行有效工作的CPU数
在自旋锁、信号量及中断禁止之间选择
一般来说,同步原语的选取取决于访问数据结构的内核控制路径的种类。记住,只要内核控制路径获得自旋锁,就禁用本地中断或本地软中断,自动禁用内核抢占。
访问数据结构的内核控制路径 | 单处理器保护 | 多处理器进一步保护 |
异常 | 信号量 | 无 |
中断 | 本地中断禁止 | 自旋锁 |
可延迟函数 | 无 | 无或自旋锁 |
异常与中断 | 本地中断禁止 | 自旋锁 |
异常与可延迟函数 | 本地软中断禁止 | 自旋锁 |
中断与可延迟函数 | 本地中断禁止 | 自旋锁 |
异常、中断与可延迟函数 | 本地中断禁止 | 自旋锁 |
异常处理程序,常见的产生同步问题的异常就是系统调用服务例程,竞争条件可以通过信号量避免,信号量工作方式在单处理器和多处理器系统上完全相同。只有在访问每CPU变量的情况下,必须显式的禁用内核抢占。
保护中断所访问的数据结构
============================================
在多处理器上,不能通过禁止本地中断来避免竞争,即使在一个CPU上禁止了中断,中断处理程序还可以在其他CPU上执行。linux使用了几个宏,把中断激活/禁止与自旋锁结合起来。void __lockfunc lock_kernel(void)
{
struct task_struct *task = current;
int depth = task->lock_depth + 1;
if (likely(!depth))
/*
* No recursion worries - we set up lock_depth _after_
*/
down(&kernel_sem);
task->lock_depth = depth;
}
void __lockfunc unlock_kernel(void)
{
struct task_struct *task = current;
BUG_ON(task->lock_depth < 0);
if (likely(--task->lock_depth < 0))
up(&kernel_sem);
}