linux内核情景分析之内核中的互斥操作
信号量机制:
struct sempahore是其结构,定义如下
struct semaphore {
atomic_t count;//资源数目
int sleepers;//等待进程数目
wait_queue_head_t wait;//等待队列
#if WAITQUEUE_DEBUG
long __magic;
#endif
};
down操作成功(减后结果非负数)那就在标号1处结束down操作,转到临界区.
如果减为负数,跳转到2标号,并且调用call_down_failed,进入睡眠,一直要到唤醒并拿到资源才返回跳转到1标号,结束down操作进入临界区
/*
* This is ugly, but we want the default case to fall through.
* "__down_failed" is a special asm handler that calls the C
* routine that actually waits. See arch/i386/kernel/semaphore.c
*/
static inline void down(struct semaphore * sem)
{
#if WAITQUEUE_DEBUG
CHECK_MAGIC(sem->__magic);
#endif
__asm__ __volatile__(
"# atomic down operation\n\t"
LOCK "decl %0\n\t" /* --sem->count lock字段把总线锁住,防止其他cpu干扰*/
"js 2f\n" /*如果小于0,那就跳转到2号*/
"1:\n" /*成功拿到,从此处进入临界区*/
".section .text.lock,\"ax\"\n"
"2:\tcall __down_failed\n\t" /*count--后为负值,休眠*/
"jmp 1b\n"/*失败睡眠,但经过一段时间被唤醒,并且进入临界区,就跳转到1*/
".previous"
:"=m" (sem->count)
:"c" (sem)
:"memory");
}
__down_failed源码,这里的目的只是为了调用__down函数
asm(
".align 4\n"
".globl __down_failed\n"
"__down_failed:\n\t"
"pushl %eax\n\t"
"pushl %edx\n\t"
"pushl %ecx\n\t"
"call __down\n\t"
"popl %ecx\n\t"
"popl %edx\n\t"
"popl %eax\n\t"
"ret"
);
__down将判断资源是否存在,不存在睡眠,如果被唤醒那就从等待队列删除,并且唤醒其他等待队列进程
void __down(struct semaphore * sem)
{
struct task_struct *tsk = current;
DECLARE_WAITQUEUE(wait, tsk);//wait代表tsk
tsk->state = TASK_UNINTERRUPTIBLE;//设置为睡眠状态
add_wait_queue_exclusive(&sem->wait, &wait);//把当前进程的等待队列元素wait加入到sem->wait等待队列队尾
spin_lock_irq(&semaphore_lock);
sem->sleepers++;//等待进程数目+1
for (;;) {
int sleepers = sem->sleepers;//禁止本地中断并获取指定的锁
/*返回非0,表示进程需要等待
* 假设有2个进程,进程资源已经被占用,当前进程执行down失败,跳转到这里,等待调度
结郭前一个进程归还了资源,count变为0(之前down2次为-1),sleeper-1也为0,相加等于0,于是可以进入临界区
*/
if (!atomic_add_negative(sleepers - 1, &sem->count)) {//返回0表示可以进入临界区
sem->sleepers = 0;//睡眠的进程为0,因为要唤醒这进程了
break;
}
sem->sleepers = 1; /* 没法到临界区,那就需要阻塞,执行到这,设置为1,那就只有us - see -1 above */
spin_unlock_irq(&semaphore_lock);
schedule();//调度
tsk->state = TASK_UNINTERRUPTIBLE;//将当前进程设置为睡眠状态
spin_lock_irq(&semaphore_lock);
}
spin_unlock_irq(&semaphore_lock);//解锁
remove_wait_queue(&sem->wait, &wait);//当前进程可以进入临界区后,从wait队列移除
tsk->state = TASK_RUNNING;//设置为可执行状态
wake_up(&sem->wait);//唤醒等待队列(然而等待队列很多进程依旧无法进入临界区)
}
缺陷:优先级倒转,优先级高进程在外等待,在临界区的进程优先级很低,一旦优先级低的进程在临界区受阻睡眠,也得不到及时调度,优先级高进程会被拖累,解决办法:把高优先级借给进入临界区的进程
接下来分析up函数
/*
* Note! This is subtle. We jump to wake people up only if
* the semaphore was negative (== somebody was waiting on it).
* The default case (no contention) will result in NO
* jumps for both down() and up().
*/
static inline void up(struct semaphore * sem)
{
#if WAITQUEUE_DEBUG
CHECK_MAGIC(sem->__magic);
#endif
__asm__ __volatile__(
"# atomic up operation\n\t"
LOCK "incl %0\n\t" /* ++sem->count */
"jle 2f\n"//如果资源充足(也就是递增结果为正数,直接从1:跳出临界区,不用唤醒阻塞进程(应该说没有阻塞临界区的进程)
"1:\n"
".section .text.lock,\"ax\"\n"
"2:\tcall __up_wakeup\n\t" /*递增结果为负数或者非0值,就唤醒阻塞进程*/
"jmp 1b\n"
".previous"
:"=m" (sem->count)
:"c" (sem)
:"memory");
}
__up_wakup的目的也是调用__up
asm(
".align 4\n"
".globl __up_wakeup\n"
"__up_wakeup:\n\t"
"pushl %eax\n\t"
"pushl %edx\n\t"
"pushl %ecx\n\t"
"call __up\n\t"
"popl %ecx\n\t"
"popl %edx\n\t"
"popl %eax\n\t"
"ret"
);
/*
* Semaphores are implemented using a two-way counter:
* The "count" variable is decremented for each process
* that tries to acquire the semaphore, while the "sleeping"
* variable is a count of such acquires.
*
* Notably, the inline "up()" and "down()" functions can
* efficiently test if they need to do any extra work (up
* needs to do something only if count was negative before
* the increment operation.
*
* "sleeping" and the contention routine ordering is
* protected by the semaphore spinlock.
*
* Note that these functions are only called when there is
* contention on the lock, and as such all this is the
* "non-critical" part of the whole semaphore business. The
* critical part is the inline stuff in <asm/semaphore.h>
* where we want to avoid any extra jumps and calls.
*/
/*
* Logic:
* - only on a boundary condition do we need to care. When we go
* from a negative count to a non-negative, we wake people up.
* - when we go from a non-negative count to a negative do we
* (a) synchronize with the "sleeper" count and (b) make sure
* that we're on the wakeup list before we synchronize so that
* we cannot lose wakeup events.
*/
void __up(struct semaphore *sem)
{
wake_up(&sem->wait);//唤醒等待队列中的进程
}
#define wake_up(x) __wake_up((x),TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE,WQ_FLAG_EXCLUSIVE)
void __wake_up(wait_queue_head_t *q, unsigned int mode, unsigned int wq_mode)
{
__wake_up_common(q, mode, wq_mode, 0);
}
posix信号量与内核信号量概念基本一样,不过posix信号量可以用于位于内核外临界区的不同进程.而内核信号量只可以用于临界区位于内核
互斥锁(也就是mutex.一般用于线程互斥),不过可以通过设置线程锁属性用于不同进程通信,为了达到多进程共享的需要,互斥锁对象需要创建在共享内存中
文件锁用于2个进程访问一个文件
自旋锁,读写锁只可用于线程互斥
信号量分为匿名信号量与有名信号量,前一个用于线程互斥,后一个用于进程同步
大内核锁也是用来保护临界区资源,避免出现多个处理器上的进程同时访问同一区域的