博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Linux内核-------同步机制(二)
阅读量:4179 次
发布时间:2019-05-26

本文共 5805 字,大约阅读时间需要 19 分钟。

信号量的分类

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

用户态进程使用的信号量,这种信号量又分为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()是一样的。

转载地址:http://lnrai.baihongyu.com/

你可能感兴趣的文章
微信悄悄新出了个野心很大的App
查看>>
微信红包封面制作小程序开放,人人都可免费制作了!!!
查看>>
13000亿!目瞪口呆!
查看>>
腾讯,搞了一个大事!
查看>>
入职腾讯第九年,我辞职了
查看>>
17 张程序员壁纸(使用频率很高)
查看>>
送一台全新手机,手慢无~
查看>>
全球顶级的14位程序员!膜拜!
查看>>
太赛博朋克!华为天才少年自制B站百大Up奖杯,网友:技术难度不高,侮辱性极强...
查看>>
华为正式宣布养猪,网友沸腾:支持华为自救!
查看>>
真的有必要读研究生吗?
查看>>
一个员工的离职成本到底有多恐怖!
查看>>
微软骂人:请勿TM关闭......
查看>>
B站python+数据分析精华汇总!速领,免费,一会删!
查看>>
一个中科大差生的8年程序员工作总结
查看>>
新功能!微信可以开“小号”了
查看>>
墙裂推荐!一款 VM 大规模集群管理工具
查看>>
知乎万赞:计算机应届生月薪大多是多少?
查看>>
试用期没过,因在公司上了1024网站...
查看>>
终于有人把如何精通C++讲明白了!
查看>>