Linux内核-------同步机制(二)

信号量的分类

内核信号量,由内核控制路径使用

用户态进程使用的信号量,这种信号量又分为POSIX信号量和SYSTEM V信号量。

POSIX信号量又分为有名信号量和无名信号量 。有名信号量,其值保存在文件中, 所以它可以用于线程也可以用于进程间的同步。无名信号量,其值保存在内存中。

信号量操作

P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行

V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.

信号量定义(linux-2.6.4\include\asm-i386\semaphore.h)

struct semaphore {
	atomic_t count;
	int sleepers;
	wait_queue_head_t wait;
#ifdef WAITQUEUE_DEBUG
	long __magic;
#endif
};

内核信号量类似于自旋锁,因为当锁关闭着时,它不允许内核控制路径继续进行。然而,当内核控制路径试图获取内核信号量锁保护的忙资源时,相应的进程就被挂起。只有在资源被释放时,进程才再次变为可运行。 只有可以睡眠的函数才能获取内核信号量;中断处理程序和可延迟函数都不能使用内核信号量。

count:相当于信号量的值,大于0,资源空闲;等于0,资源忙,但没有进程等待这个保护的资源;小于0,资源不可用,并至少有一个进程等待资源

wait :存放等待队列链表的地址,当前等待资源的所有睡眠进程都会放在这个链表中

sleepers: 存放一个标志,表示是否有一些进程在信号量上睡眠。

初始化

static inline void sema_init (struct semaphore *sem, int val)
        {
                atomic_set(&sem->count, val); // 把信号量的值设为原子操作的值。
                sem->sleepers = 0; // 设为等待者为0。
                init_waitqueue_head(&sem->wait); // 初始化等待队列。
        }

该函用于数初始化设置信号量的初值,它设置信号量sem的值为val。

互斥锁

static inline void init_MUTEX (struct semaphore *sem)
{
	sema_init(sem, 1);
}

该函数将count的值初始化为1,从而实现互斥锁的功能。

static inline void init_MUTEX_LOCKED (struct semaphore *sem)
{
	sema_init(sem, 0);
}

该函数也用于初始化一个互斥锁,但它把信号量sem的值设置为0,即一开始就处在已锁状态。

P操作函数

static inline void down(struct semaphore * sem)
{
#ifdef WAITQUEUE_DEBUG
	CHECK_MAGIC(sem->__magic);
#endif
	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:\tcall __down_failed\n\t"
		"jmp 1b\n"
		LOCK_SECTION_END
		:"=m" (sem->count)
		:"c" (sem)
		:"memory");
}

might_sleep()判断当前进程是否需要重新调度。

中间是一段内联汇编:

LOCK "decl %0\n\t"     /* --sem->count */
		"js 2f\n"
		"1:\n"
		LOCK_SECTION_START("")
		"2:\tcall __down_failed\n\t"
		"jmp 1b\n"
		LOCK_SECTION_END
		:"=m" (sem->count)
		:"c" (sem)
		:"memory");

下面详细解释:

LOCK "decl %0\n\t"

x86 处理器使用“lock”前缀的方式提供了在指令执行期间对总线加锁的手段。芯片上有一条引线 LOCK,如果在一条汇编指令(ADD, ADC, AND, BTC, BTR, BTS, CMPXCHG, CMPXCH8B, DEC, INC, NEG, NOT, OR, SBB, SUB, XOR, XADD, XCHG)前加上“lock” 前缀,经过汇编后的机器代码就使得处理器执行该指令时把引线 LOCK 的电位拉低,从而把总线锁住,这样其它处理器或使用DMA的外设暂时无法通过同一总线访问内存。
所以这行汇编的效果就是将count的值减一(count暂存在%0寄存器中)。

"js 2f\n"

如果count的值小于0,则跳转到2:。

其中2:对应的代码:

        LOCK_SECTION_START("")
		"2:\tcall __down_failed\n\t"
		"jmp 1b\n"
		LOCK_SECTION_END

LOCK_SECTION_START,LOCK_SECTION_END中间的内容是把这一段的代码汇编到一个叫.text.lock的节中,并且这个节的属性是可重定位和可执行的,这样在代码的执行过程中,因为不同的节会被加载到不同的页去,所以如果前面不出现jmp,就在1: 处结束了。
进程在 __down_failed()中会进入睡眠,一直要等到被唤醒才会返回。其中__down_failed()的具体实现:(linux-2.6.4\arch\i386\kernel\semaphore.c)

asm(
".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 %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"
#if defined(CONFIG_FRAME_POINTER)
	"movl %ebp,%esp\n\t"
	"popl %ebp\n\t"
#endif
	"ret"
);

上面的__down函数的代码比较复杂:

asmlinkage void __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;
}

add_wait_queue_exclusive_locked把代表当前进程的等待队列元素wait链入到由队列头sem_wait代表的等待队列的尾部。

spin_lock_irqsave既禁止本地中断,又禁止内核抢占,保存中断状态寄存器的标志位。

for循环中终止循环的代码:

if (!atomic_add_negative(sleepers - 1, &sem->count)) {
			sem->sleepers = 0;
			break;
	}

在for循环中,sem->sleepers表示有几个进程正在等待(包括当前进程),atomic_add_negative函数的作用是将(sleepers-1)加到sem_count上。如果相加结果为负数,返回1,否则,返回0。

假设当前进程进入__down时,count的值为0,此时还没调用spin_lock_irqsave函数,那么,由于调度或中断,就有可能被改变为1。sleepers - 1 =-1,count=1。atomic_add_negative的返回值为0,从而终止该循环,使得当前进程不需要休眠就可以进入临界区。

假设没有被修改:sleepers - 1 =-1,count=0。atomic_add_negative的返回值为1。从而继续执行schedule()函数调度当前进程。直到终止条件为真跳出循环。

    remove_wait_queue_locked(&sem->wait, &wait);
	wake_up_locked(&sem->wait);
	spin_unlock_irqrestore(&sem->wait.lock, flags);
	tsk->state = TASK_RUNNING;

循环终止,恢复中断,恢复内核调度,重新执行当前进程。

:"=m" (sem->count)
:"c" (sem)
:"memory");

第一行表示从内存中读取sem->count的值并存入%0中,第二行表示将sem的地址加载到%ecx中(即sem->count的地址)。

V操作函数

static inline void up(struct semaphore * sem)
{
#ifdef 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:\n"
		LOCK_SECTION_START("")
		"2:\tcall __up_wakeup\n\t"
		"jmp 1b\n"
		LOCK_SECTION_END
		".subsection 0\n"
		:"=m" (sem->count)
		:"c" (sem)
		:"memory");
}

直接分析中间的内联汇编:

        LOCK "incl %0\n\t"     /* ++sem->count */
		"jle 2f\n"
		"1:\n"
		LOCK_SECTION_START("")
		"2:\tcall __up_wakeup\n\t"
		"jmp 1b\n"
		LOCK_SECTION_END
		".subsection 0\n"
		:"=m" (sem->count)
		:"c" (sem)
		:"memory");

具体分析:

 LOCK "incl %0\n\t"     /* ++sem->count */

上面的代码比较简单,简单的将count的值加一。

        "jle 2f\n"
		"1:\n"

如果count的值小于等于0,表示有进程在睡眠,因此跳转到2:。

其中的2:。

        LOCK_SECTION_START("")
		"2:\tcall __up_wakeup\n\t"
		"jmp 1b\n"
		LOCK_SECTION_END

唤醒一个进程,然后返回。其中__up_wakeup的具体实现:(linux-2.6.4\arch\i386\kernel\semaphore.c)

asm(
".text\n"
".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"
);

其中调用了__up函数:

asmlinkage void __up(struct semaphore *sem)
{
	wake_up(&sem->wait);
}

上面的代码调用wake_up函数将进程唤醒。

后面三行与前面的down()是一样的。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

计算机的小粽子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值