内核同步机制-信号量(semaphore)

  Linux的信号量是一种睡眠锁,这个不同于自旋锁.如果有一个任务试图获得一个已经被占用的信号量时,信号量会将其推进一个等待队列,然后让其睡眠,此时处理器能重获自由,而去执行其他代码.当持有信号量的进程将信号量释放后,处于等待队列中的那个进程会被唤醒,并获得该信号量.
  信号量的睡眠特性,使得信号量适用于锁会被长时间持有的情况;只能在进程上下文中使用,因为中断上下文中是不能被调度的(不是很确定,保留,待学到一定深度再来确定);另外当代码持有信号量时,不可以再持有自旋锁
信号量在创建时需要设置一个初始值,表示同时可以有几个任务可以访问该信号量保护的共享资源,初始值为1就变成互斥锁(Mutex),即同时只能有一个任务可以访问信号量保护的共享资源。 
  一个任务要想访问共享资源,首先必须得到信号量,获取信号量的操作将把信号量的值减1,若当前信号量的值为负数,表明无法获得信号量,该任务必须挂起在该信号量的等待队列等待该信号量可用;若当前信号量的值为非负数,表示可以获得信号量,因而可以立刻访问被该信号量保护的共享资源。

  当任务访问完被信号量保护的共享资源后,必须释放信号量,释放信号量通过把信号量的值加1实现,如果信号量的值为非正数,表明有任务等待当前信号量,因此它也唤醒所有等待该信号量的任务。

访问信号量的函数有一下几种

函数定义 功能说明
sema_init(struct semaphore *sem, int val) 初始化信号量,将信号量计数器值设置val。
down(struct semaphore *sem) 获取信号量,不建议使用此函数。
down_interruptible(struct semaphore *sem) 可被中断地获取信号量,如果睡眠被信号中断,返回错误-EINTR。
down_killable (struct semaphore *sem) 可被杀死地获取信号量。如果睡眠被致命信号中断,返回错误-EINTR。
down_trylock(struct semaphore *sem) 尝试原子地获取信号量,如果成功获取,返回0,不能获取,返回1。
down_timeout(struct semaphore *sem, long jiffies) 在指定的时间jiffies内获取信号量,若超时未获取,返回错误-ETIME。
up(struct semaphore *sem) 释放信号量sem。

信号量的使用:

下面三个都是用于定义并初始化互斥锁(同时只有一个任务可以访问的信号量)的宏

init_MUTEX(sem)           //定义并初始化信号量为1
init_MUTEX_LOCKED(sem)    //定义定初始化信号量为0
DECLARE_MUTEX(name)       //定义并初始化信号量为 

其中第一个和第二个相比于第三个增加了,初始化一个锁的实例,用于获取信号量的调试信息。

具体的实现可以对比下面。增加了一个__key,

/* Please don't access any members of this structure directly */
struct semaphore {
	spinlock_t		lock;
	unsigned int		count;
	struct list_head	wait_list;
};

#define __SEMAPHORE_INITIALIZER(name, n)				\
{									\
	.lock		= __SPIN_LOCK_UNLOCKED((name).lock),		\
	.count		= n,						\
	.wait_list	= LIST_HEAD_INIT((name).wait_list),		\
}

#define DECLARE_MUTEX(name)	\
	struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)

static inline void sema_init(struct semaphore *sem, int val)
{
	static struct lock_class_key __key;
	*sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val);
	lockdep_init_map(&sem->lock.dep_map, "semaphore->lock", &__key, 0);
}

#define init_MUTEX(sem)		sema_init(sem, 1)
#define init_MUTEX_LOCKED(sem)	sema_init(sem, 0)

下面几个是对信号量操作的几个函数。

void down(struct semaphore *sem);
int __must_check down_interruptible(struct semaphore *sem);
int __must_check down_killable(struct semaphore *sem);
int __must_check down_trylock(struct semaphore *sem);
int __must_check down_timeout(struct semaphore *sem, long jiffies);
void up(struct semaphore *sem);

void down(struct semaphore * sem);

该函数用于获得信号量sem,它会导致睡眠,因此不能在中断上下文(包括IRQ上下文和softirq上下文)使用该函数。该函数将把sem的值减1,如果信号量sem的值非负,就直接返回,否则调用者将被挂起,直到别的任务释放该信号量才能继续运行。

int down_interruptible(struct semaphore * sem);

该函数功能与down类似,不同之处为,down不会被信号(signal)打断,但down_interruptible能被信号打断,因此该函数有返回值来区分是正常返回还是被信号中断,如果返回0,表示获得信号量正常返回,如果被信号打断,返回-EINTR。

int down_trylock(struct semaphore * sem);

该函数试着获得信号量sem,如果能够立刻获得,它就获得该信号量并返回0,否则,表示不能获得信号量sem,返回值为非0值。因此,它不会导致调用者睡眠,可以在中断上下文使用。

int down_timeout(struct semaphore *sem, long jiffies);

该函数尝试获得信号量sem在一定时间内,如果超时还没有获得,就返回一个-ETIME

void up(struct semaphore * sem);

该函数释放信号量sem,即把sem的值加1,如果sem的值为非正数,表明有任务等待该信号量,因此唤醒这些等待者。

下面说一下信号量的api函数

(1)信号量结构semaphore

信号量用结构semaphore描述,它在自旋锁的基础上改进而成,它包括一个自旋锁、信号量计数器和一个等待队列。用户程序只能调用信号量API函数,而不能直接访问信号量结构,其列出如下(在include/linux/semaphore.h中)

要说明的有两点:

1.count的初值代表被这个信号量锁住的东西最多能被引用几次。比如,一个进程中最多能同时打开10个文件,则初始化信号量的时候的初值就应该为10,每次打开文件前都需要调用获取信号量的函数。

2.等待列表起始就是一个用链表实现的等待队列。所以也就满足队列“先进先出”的特性。即先申请信号量失败进入等待列表的进程先被唤醒。

/* Please don't access any members of this structure directly */
struct semaphore {
	spinlock_t		lock;
	unsigned int		count;
	struct list_head	wait_list;
};

(2)初始化函数sema_init

语法比较简单,通过宏的简单展开实现的初始化。唯一说明的是,linux内核中大量使用了头文件里面定义静态内联函数,linline保证既没增加函数调用开销,static又保证放在头文件中被多个c文件包含不会出现重复定义。

static inline void sema_init(struct semaphore *sem, int val)
{
	static struct lock_class_key __key;
	*sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val);
	lockdep_init_map(&sem->lock.dep_map, "semaphore->lock", &__key, 0);
}

(3)获取信号量函数down

使用这个函数如果没有获取信号量,会使进程进入睡眠。要唤醒这个进程的唯一方式就是得到了这个信号量。否则该进程就一直睡眠下去。所以内核实现者并不建议使用这个函数。

/**
 * down - acquire the semaphore
 * @sem: the semaphore to be acquired
 *
 * Acquires the semaphore.  If no more tasks are allowed to acquire the
 * semaphore, calling this function will put the task to sleep until the
 * semaphore is released.
 *
 * Use of this function is deprecated, please use down_interruptible() or
 * down_killable() instead.
 */
void down(struct semaphore *sem)
{
	unsigned long flags;

	spin_lock_irqsave(&sem->lock, flags);
	if (likely(sem->count > 0))
		sem->count--;
	else
		__down(sem);
	spin_unlock_irqrestore(&sem->lock, flags);
}

(3)可中断获取信号量函数down_interruptible

函数down_interruptible获取信号量,存放在参数sem中。它尝试获取信号量,如果其他线程被允许尝试获取此信号量,则将本线程睡眠等待。如果有一个信号中断睡眠,则它返回错误-EINTR。如果成功获取信号量,函数返回0。

/**
 * down_interruptible - acquire the semaphore unless interrupted
 * @sem: the semaphore to be acquired
 *
 * Attempts to acquire the semaphore.  If no more tasks are allowed to
 * acquire the semaphore, calling this function will put the task to sleep.
 * If the sleep is interrupted by a signal, this function will return -EINTR.
 * If the semaphore is successfully acquired, this function returns 0.
 */
int down_interruptible(struct semaphore *sem)
{
	unsigned long flags;
	int result = 0;

	spin_lock_irqsave(&sem->lock, flags);
	if (likely(sem->count > 0))
		sem->count--;
	else
		result = __down_interruptible(sem);
	spin_unlock_irqrestore(&sem->lock, flags);

	return result;
}

(4)可中断获取信号量函数down_killable

注释很明确,调用这个函数的任务睡眠后。收到到一个致命信号,该函数返回错误值。
/**
 * down_killable - acquire the semaphore unless killed
 * @sem: the semaphore to be acquired
 *
 * Attempts to acquire the semaphore.  If no more tasks are allowed to
 * acquire the semaphore, calling this function will put the task to sleep.
 * If the sleep is interrupted by a fatal signal, this function will return
 * -EINTR.  If the semaphore is successfully acquired, this function returns
 * 0.
 */
int down_killable(struct semaphore *sem)
{
	unsigned long flags;
	int result = 0;

	spin_lock_irqsave(&sem->lock, flags);
	if (likely(sem->count > 0))
		sem->count--;
	else
		result = __down_killable(sem);
	spin_unlock_irqrestore(&sem->lock, flags);

	return result;
}

(5)可中断获取信号量函数down_trylock

这个函数就更简单了,直接判断有有没有上锁,已经上锁则直接返回1表示资源在用,没上锁返回0表示资源可用。唯一要除以的是,上面的其它锁会睡眠任务,所以不能放在中断函数。而该函数可以放在中断和任务使用。
/**
 * down_trylock - try to acquire the semaphore, without waiting
 * @sem: the semaphore to be acquired
 *
 * Try to acquire the semaphore atomically.  Returns 0 if the mutex has
 * been acquired successfully or 1 if it it cannot be acquired.
 *
 * NOTE: This return value is inverted from both spin_trylock and
 * mutex_trylock!  Be careful about this when converting code.
 *
 * Unlike mutex_trylock, this function can be used from interrupt context,
 * and the semaphore can be released by any task or interrupt.
 */
int down_trylock(struct semaphore *sem)
{
	unsigned long flags;
	int count;

	spin_lock_irqsave(&sem->lock, flags);
	count = sem->count - 1;
	if (likely(count >= 0))
		sem->count = count;
	spin_unlock_irqrestore(&sem->lock, flags);

	return (count < 0);
}

(5)可中断获取信号量函数down_trylock

/**
 * up - release the semaphore
 * @sem: the semaphore to release
 *
 * Release the semaphore.  Unlike mutexes, up() may be called from any
 * context and even by tasks which have never called down().
 */
void up(struct semaphore *sem)
{
	unsigned long flags;

	spin_lock_irqsave(&sem->lock, flags);  

	if (likely(list_empty(&sem->wait_list))) /*判断是否有任务等待在此信号量上,即判断等待队列是否为空*/

		sem->count++;            /*没有线程等待此信号量,释放信号量,将信号量计数器加1,表示增加了1个空闲资源*/

	else
		__up(sem);                        /*将本任务从等待队列删除,唤醒等待此信号量的其他线程*/

	spin_unlock_irqrestore(&sem->lock, flags);
}



猜你喜欢

转载自blog.csdn.net/qq_16777851/article/details/80877979