信号量

来源:互联网 发布:家用 网络安全设备 编辑:程序博客网 时间:2024/06/06 13:05

linux上有很多地方可能导致并发竞态的现象 比如smp多核的时候 不同的处理器上同时执行我们的代码 多个用户进程也能在一起访问我们的代码 还要很多可延迟机制 比如workqueue tasklet以及timer都可能会使得代码在任何时候执行 所以就会导致并发执行代码 访问公共资源导致出现很多问题
对这个问题最明显的应用就是避免使用全局变量 所以为了只能让一个操作在一个时间内只能有一个执行线程操作 出现了锁机制

锁机制也有很多区别 比如一些锁机制不允许休眠 一些锁机制允许休眠 比如信号量

信号量就是建立一个临界区 在给定的任意时刻 只有一个线程执行

信号量分析
信号量在创建时需要设置一个初始值,表示同时能有几个任务能访问该信号量保护的共享资源,初始值为1就变成互斥锁(Mutex),即同时只能有一个任务能访问信号量保护的共享资源。
一个任务要想访问共享资源,首先必须得到信号量,获取信号量的操作将把信号量的值减1,若当前信号量的值为负数,表明无法获得信号量,该任务必须挂起在 该信号量的等待队列等待该信号量可用;若当前信号量的值为非负数,表示能获得信号量,因而能即时访问被该信号量保护的共享资源。
  当任务访问完被信号量保护的共享资源后,必须释放信号量,释放信号量通过把信号量的值加1实现,如果信号量的值为非正数,表明有任务等待当前信号量,因此他也唤醒所有等待该信号量的任务

信号量的数据结构
struct semaphore {
raw_spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};

常见信号量的API
DECLARE_MUTEX(name)
  该宏声明一个信号量name并初始化他的值为1,即声明一个互斥锁。
DECLARE_MUTEX_LOCKED(name)
  该宏声明一个互斥锁name,但把他的初始值设置为0,即锁在创建时就处在已锁状态。因此对于这种锁,一般是先释放后获得。

但是这两个宏在2.6内核后就已经不使用了 取而代之的是
DEFINE_SEMAPHORE(name)
用来声明一个互斥锁 初始化信号量name的值为1

也可以直接创建信号量 linux提供了一个函数
static inline void sema_init(struct semaphore *sem, int val);

初始化信号量如上 下面是操作信号量 比如
extern void down(struct semaphore *sem);
down函数就是获取信号量 将信号量里的值减少1 但是会导致睡眠 所以不能在中断上下文使用

extern int __must_check down_interruptible(struct semaphore *sem);
这个函数功能和down相似 区别就是down进入睡眠状态不能被信号打断 但是down_interruptible在进程进入睡眠状态可以被信号打断 并且会返回一个非0的值

extern int __must_check down_killable(struct semaphore *sem);
和down_interruptible类似 区别就是睡眠时如果被fatal signal中断的话 就会返回-EINTR
如果成功的获得了信号量的话 就会返回0

extern int __must_check down_trylock(struct semaphore *sem);
down_trylock接口用于试着获取一个信号量,但是,此接口不会引起调用者
的睡眠。不管有无可用信号量,都马上进行返回,如果返回0,则获取信号量成功,如果返回1,则获取失败。所以,在调用此接口时,必须进行返回的值的查看,看是否获取成功

extern int __must_check down_timeout(struct semaphore *sem, long jiffies);
如果获取不到信号量的话也会进入睡眠 但是加了一个时间限制 在规定时间内 得不搞锁的话就会返回-ETIME 获取到锁的话就返回0

在linux中 推荐使用的是down_interruptible 和down_trylock 这个API

当一个线程使用down系列API获取到了信号量的时候 也就相当于获得了被赋予了访问由该信号量保护的临界区的权利。 互斥操作完成后 必须返回该信号量 别的执行进程获取不到这个信号量获取不到会休眠等等情况

void up(struct semaphore *sem); 这个函数其实也就是把信号量的值增加1

任何拿到信号量的线程都必须通过一次up的调用释放信号量

信号量的使用方法

先定义一个
DEFINE_SEMAPHORE(mysem);

然后需要的时候就
down_interruptible(&mysem);
xxx;

up(&mysem);

这样就完成了一个信号量的一个操作

当然 内核为了提升内核的性能提供了读写者/写入者信号量 也就是当一些任务 只要读取受保护的数据结构的时候 linux内核也支持多个读取者并发的读取内容 这样的话可以大大的提高性能
struct rw_semaphore {
long count;
struct list_head wait_list;
raw_spinlock_t wait_lock;
#ifdef CONFIG_RWSEM_SPIN_ON_OWNER
struct optimistic_spin_queue osq; /* spinner MCS lock */
/*
* Write owner. Used as a speculative check to see
* if the owner is running on the cpu.
*/
struct task_struct *owner;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
};

初始化rw_semaphore结构
init_rwsem(struct rw_semaphore *sem);

然后就可以获取信号量 并发的只读访问资源
extern void down_read(struct rw_semaphore *sem);

/*
* trylock for reading – returns 1 if successful, 0 if contention
*/
extern int down_read_trylock(struct rw_semaphore *sem);

/*
* release a read lock
*/
extern void up_read(struct rw_semaphore *sem);

同时也提供写入者拥有信号量 按时同时只允许一个写入者或者多个读取者拥有该信号量
当然写入者拥有更高的优先权 也就是当某个写入者试图进入临界区 在所有写入者工作完成之前 不会允许读取者访问 。
内核提供了写入者的API函数
extern void down_write(struct rw_semaphore *sem);
/*
* trylock for writing – returns 1 if successful, 0 if contention
*/
extern int down_write_trylock(struct rw_semaphore *sem);

extern void up_write(struct rw_semaphore *sem);

/*
* downgrade write lock to read lock
*/
extern void downgrade_write(struct rw_semaphore *sem);
这个函数比较陌生 也就是写入者完成操作后 调用这个API来允许读取者读取内容

其实信号量可以用来当做互斥访问 但是新的linux内核还是多使用mutex作为互斥手段 所以信号量多用于同步吧

原创粉丝点击