Linux提供两种信号量:
1、内核信号量,由内核控制路径使用
2、System V IPC信号量,由用户态进程使用
从本质上说,它们实现了一个加锁原语,即让等待者睡眠,直到等待的资源变为空闲。
内核信号量类似于自旋锁,因为当锁关闭着的时候,它不允许内核控制路径继续运行。然而,当内核内核控制路径试图获取内核信号量所保护的忙资源时,相应的进程被挂起。只有在资源被释放时,进程才再次变为可运行的。
因此,只有可以睡眠的函数才能获取内核信号量;中断处理程序和可延迟处理函数都不能使用内核信号量。
struct semaphore {
atomic_t count;
int sleepers;
wait_queue_head_t wait;
};count 为一个atomic_t类型的值。如果该值大于0,那么资源就是空闲的,也就是说,该资源现在可以使用。相反,如果count等于0,那么信号量是忙的,但没有进程等待这个被保护的资源。最后如果count为负数,则资源时不可用的,并至少有一个进程等待资源。
sleepers 存放一个标志,表示是否有一些进程在信号量上睡眠
wait 存放等待队列链表的地址,当前等待资源的所有睡眠进程都放在这个链表中。当然,如果count大于或等于0,等待队列就为空。
可以使用init_MUTEX和init_MUTEX_LOCKED函数来初始化互斥访问所需的信号量:这两个宏分别把count字段设置成1(互斥访问的资源空闲)和0(对信号量进行初始化的进程当前互斥访问的资源忙等)
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);
}获取信号量
当进程希望获取内核信号量时,就调用down()函数。
/arch/i386/kernel/semaphore.c
fastcall void __sched __down(struct semaphore * sem)
{
struct task_struct *tsk = current;
DECLARE_WAITQUEUE(wait, tsk);
unsigned long flags;
tsk->state = TASK_UNINTERRUPTIBLE;
spin_lock_irqsave(&sem->wait.lock, flags);
add_wait_queue_exclusive_locked(&sem->wait, &wait);
sem->sleepers++;
for (;;) {
int sleepers = sem->sleepers;
/*
* Add "everybody else" into it. They aren‘t
* playing, because we own the spinlock in
* the wait_queue_head.
*/
if (!atomic_add_negative(sleepers - 1, &sem->count)) {
sem->sleepers = 0;
break;
}
sem->sleepers = 1; /* us - see -1 above */
spin_unlock_irqrestore(&sem->wait.lock, flags);
schedule();
spin_lock_irqsave(&sem->wait.lock, flags);
tsk->state = TASK_UNINTERRUPTIBLE;
}
remove_wait_queue_locked(&sem->wait, &wait);
wake_up_locked(&sem->wait);
spin_unlock_irqrestore(&sem->wait.lock, flags);
tsk->state = TASK_RUNNING;
}
asm(
".section .sched.text\n"
".align 4\n"
".globl __down_failed\n"
"__down_failed:\n\t"
#if defined(CONFIG_FRAME_POINTER)
"pushl %ebp\n\t"
"movl %esp,%ebp\n\t"
#endif
"pushl %edx\n\t"
"pushl %ecx\n\t"
"call __down\n\t"
"popl %ecx\n\t"
"popl %edx\n\t"
#if defined(CONFIG_FRAME_POINTER)
"movl %ebp,%esp\n\t"
"popl %ebp\n\t"
#endif
"ret"
);/include/asm-i386/semaphore.h
static inline void down(struct semaphore * sem)
{
might_sleep();
__asm__ __volatile__(
"# atomic down operation\n\t"
LOCK "decl %0\n\t" /* --sem->count */
"js 2f\n"
"1:\n"
LOCK_SECTION_START("")
"2:\tlea %0,%%eax\n\t"
"call __down_failed\n\t"
"jmp 1b\n"
LOCK_SECTION_END
:"=m" (sem->count)
:
:"memory","ax");
}分析一下down的实现
首先decl 递减sem->count,然后检查该值是否为负。如果count大于或等于0,当前进程获得资源并继续正常执行。否则,count为负,当前进程必须挂起。把一下寄存器内容保存在栈中,然后调用__down()
从本质上说,__down()函数把当前进程的状态从TASK_RUNNING改变为TASK_UNINTERRUPTIBLE,并把进程放在信号量的等待队列。该函数在访问信号量结构之前,要获得用来保护信号量队列的sem->wait.lock自旋锁(因为等待队列是由中断处理程序和主要内核函数修改的,因此必须对其双向链表进行保护以避免对其同时访问),并禁止本地中断。
最后,__down()函数的主要任务是挂起当前进程(通过调用schedule),直到信号量释放。要牢记如果没有进程在信号量等待队列上睡眠,则信号量的sleeper字段通常是0,否则被置为1。
只有异常处理程序,特别是系统调用服务历程,才可以调用down()函数。
JS Jump if sign (negative)
JMP Jump
LEA Load effective address
释放信号量
fastcall void __up(struct semaphore *sem)
{
wake_up(&sem->wait);
}
asm(
".section .sched.text\n"
".align 4\n"
".globl __up_wakeup\n"
"__up_wakeup:\n\t"
"pushl %edx\n\t"
"pushl %ecx\n\t"
"call __up\n\t"
"popl %ecx\n\t"
"popl %edx\n\t"
"ret"
);
static inline void up(struct semaphore * sem)
{
__asm__ __volatile__(
"# atomic up operation\n\t"
LOCK "incl %0\n\t" /* ++sem->count */
"jle 2f\n"
"1:\n"
LOCK_SECTION_START("")
"2:\tlea %0,%%eax\n\t"
"call __up_wakeup\n\t"
"jmp 1b\n"
LOCK_SECTION_END
".subsection 0\n"
:"=m" (sem->count)
:
:"memory","ax");
}up()函数增加count字段的值,然后检查它的值是否大于0。如果count大于0,说明没有进程在等待队列上睡眠,因此什么事情也不做。否则调用__up()函数以唤醒一个睡眠进程。
Linux中提供信号量的宏
sema_init 初始化信号量
down 获取信号量
down_interruptible 获取信号量,该函数广泛应用在驱动设备程序中,因为如果进程收到了一个信号但在信号量上被阻塞,就允许进程放弃down操作。
如果睡眠进程在获得需要的资源之前被一个信号唤醒,那么该函数就会增加count字段的值并返回-EINTR。因此返回-EINTR时, 设备驱动程序可以放弃I/O操作
down_trylock 获取信号量,非阻塞接口,与down()不同之处在于系统资源繁忙时,该函数会立即返回,而不是让进程去睡眠
up 释放信号量
读写信号量类似前面的读写锁,有一点不同:在信号量再次变为打开之前,等待进程挂起而不是自旋。
struct rw_semaphore {
signed long count;
#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;
#if RWSEM_DEBUG
int debug;
#endif
};count 存放两个16位的计数器。其中最高16位计数器以二进制补码形式存放非等待写者进程的总数(0或1)和等待的写内核控制路径数。最低16位计数器存 放非等待的读者和写者进程的总数。
wait_list 指向等待进程的链表。
wait_lock 一个自旋锁,用于保护等待队列链表和rw_semaphore结构本身。
Linux中提供读写信号量的宏
init_rwsem 初始化读写信号量
down_read 获取读信号量
down_read_trylock 尝试获取读信号量,非阻塞接口
down_write 获取写信号量
down_write_trylock 尝试获取写信号量,非阻塞接口
up_read 释放读信号量
up_write 释放写信号量
downgrade_write 自动把写锁转换为读锁
struct completion {
unsigned int done;
wait_queue_head_t wait;
};
void fastcall complete(struct completion *x)
{
unsigned long flags;
spin_lock_irqsave(&x->wait.lock, flags);
x->done++;
__wake_up_common(&x->wait, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE,
1, 0, NULL);
spin_unlock_irqrestore(&x->wait.lock, flags);
}void fastcall __sched wait_for_completion(struct completion *x)
{
might_sleep();
spin_lock_irq(&x->wait.lock);
if (!x->done) {
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);
}原文:http://blog.csdn.net/wangpeihuixyz/article/details/26085205