信号量(Semaphore)简单介绍
信号量也是一种用来保护 临界区(共享数据) 的常用方法,它的使用方法和自旋锁类似。和自旋锁类似之处是,信号量使用时,只有得到信号量的进程才能够执行临界区代码。和自旋锁不同的地方是,在没有得到信号量的时候,进程不会像自旋锁那样原地旋转,而是进入休眠等待的状态。因此当信号量阻塞时小号的系统资源(主要是CPU资源)并不多,也不会出现死机的现象。
信号量的使用方法
struct semaphore sem;
信号量需要使用struct semaphore 结构体来定义。
static inline void sema_init(struct semaphore *sem, int val)
sem 参数是semaphore结构体的指针;
val 是信号量的初始值,表示同时只能有val个进程访问共享资源(即执行临界区代码)。例如,如果val等于1,表示同时只能有一个进程访问共享资源,如果有多个进程同时获取到信号量,除了当前正在访问共享资源的进程外,其他的进程都将进入休眠状态,知道当前访问共享资源的进程释放信号量。如果想让信号量达到和自旋锁同样的效果(共享数据只能由一个任务线程或进程访问),val 参数的值应该设置为1。如果 val 参数的值大于1,信号量就相当于一个计数器。知道信号量为0,试图获取信号量的进程才会被阻塞。
extern void down(struct semaphore *sem);
down 函数用于获取信号量sem,如果未能获取信号量,会导致当前的进程进入休眠,中断也不能唤醒,所以不能在中断上下文中使用这个函数。
extern int _must_check down_interruptible(struct semaphore *sem);
down_interruptible 函数与 down 函数类似,在没有获得信号量之前,会使当前的进程进入休眠,但和 down 函数不同的是,在睡眠过程中,它可以被中断信号打断。如果被打断,则这个函数会返回一个非0值,正常结束(成功获取了信号量)则返回0.一般使用 down_interruptible 函数时都会对他的返回值进行判断,代码实现如下:
if(down_interruptible(&sem)) {
return -ERESTARTSYS;
}
使用 down_trylock 函数获取信号量时,不管是否成功获取信号量,这个函数都会立刻返回。如果成功获取信号量,返回0,否则返回非0值。
extern int _must_check down_trylock(struct semaphore *sem);
down_trylock 函数不会进入休眠,所以可以在中断上下文中使用。
extern void up(struct semaphore *sem);
up 函数用于释放信号量,唤醒等待线程。
/* 定义信号量 */
struct semaphore sem;
/* 初始化信号量 */
sema_init(&sem, 1);
/* 获取信号量 */
down(&sem);
... /* 临界区代码 */
/* 释放信号量 */
up(*sem);
信号量用于同步代码实现
本示例利用对设备文件的异步读写来演示了获取和释放信号量的方法。信号量的初始值为1,当读取设备文件时,执行down函数成功获取信号量,并延长5s来模拟执行临界区代码的过程,最后会执行up函数释放信号量。在5s之内会向设备文件写入数据,这时会再执行down函数获取信号量,但down函数将会休眠。知道5s后,读设备文件的时候执行up函数来释放信号量。写设备文件的write函数中的down函数才能被激活。代码如下:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/semaphore.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>
/* 定义设备文件名 */
#define DEVICE_NAME "semaphore"
/* 定义信号量 */
struct semaphore sem;
/* 读取设备文件时调用的函数 */
static ssize_t demo_read(struct file *file, char _user *buf, size_t count, loff_t *ppos)
{
struct timeval tv;
/* 获取当前的秒数 */
do_gettimeofday(&tv);
/* 获取当前的秒数 */
printk("semaphore read:start:%ld\n", tv.tv_sec);
/* 获取信号量 */
down(&sem);
/* 延迟5s,为了模拟临界区代码的执行过程 */
mdelay(5000);
/* 释放信号量 */
up(&sem);
/* 输出当前的秒数 */
printk("semaphore read:end:%ld\n", tv.tv_sec);
return 0;
}
/* 想设备写入数据的时候调用的函数 */
static ssize_t demo_write(struct file *file, const char _user *buf, size_t count, loff_t *ppos)
{
struct timeval tv;
/* 获取当前的秒数 */
do_gettimeofday(&tv);
/* 输出当前的秒数 */
printk("semaphore write:start:%ld\n", tv.tv_sec);
/* 获取信号量 */
down(&sem);
/* 释放信号量 */
up(&sem);
/* 获取当前的秒数 */
do_gettimeofday(&tv);
/* 输出当前的秒数 */
printk("semaphore write:end:%ld\n", tv.tv_sec);
return count;
}
static struct file_operation dev_fops = {
.owner = THIS_MODULE,
.read = demo_read,
.write = demo_write,
}
static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &dev_fops
};
/* 初始化Linux驱动 */
static int _init demo_init(void)
{
/* 创建设备文件 */
int ret = misc_register(&misc);
/* 将信号量初始化为1 */
sema_init(&sem, 1);
printk("demo_init_success\n");
return ret;
}
/*卸载Linux驱动 */
static int _init demo_exit(void)
{
printk("demo_exit_success\n");
/* 注销设备文件 */
misc_deregister(&misc);
return ret;
}
/* 注册初始化Linux驱动的函数 */
module_init(demo_init);
/* 注册卸载Linux驱动的函数 */
module_exit(demo_exit);