大家如果有学习过 FreeRTOS 或者 UCOS 的话就应该对信号量很熟悉,因为信号量是同步的一种方式。Linux 内核也提供了信号量机制,信号量常常用于控制对共享资源的访问。举一个很常见的例子,某个停车场有 100 个停车位,这 100 个停车位大家都可以用,对于大家来说这100 个停车位就是共享资源。假设现在这个停车场正常运行,你要把车停到这个这个停车场肯定要先看一下现在停了多少车了?还有没有停车位?当前停车数量就是一个信号量,具体的停车数量就是这个信号量值,当这个值到 100 的时候说明停车场满了。停车场满的时你可以等一会看看有没有其他的车开出停车场,当有车开出停车场的时候停车数量就会减一,也就是说信号量减一,此时你就可以把车停进去了,你把车停进去以后停车数量就会加一,也就是信号量加一。这就是一个典型的使用信号量进行共享资源管理的案例,在这个案例中使用的就是计数型信号量。
相比于自旋锁,信号量可以使线程进入休眠状态,比如 A 与 B、C 合租了一套房子,这个房子只有一个厕所,一次只能一个人使用。某一天早上 A 去上厕所了,过了一会 B 也想用厕所,因为 A 在厕所里面,所以 B 只能等到 A 用来了才能进去。B 要么就一直在厕所门口等着,等 A 出来,这个时候就相当于自旋锁。B 也可以告诉 A,让 A 出来以后通知他一下,然后 B 继续回房间睡觉,这个时候相当于信号量。可以看出,使用信号量会提高处理器的使用效率,毕竟不用一直傻乎乎的在那里“自旋”等待。但是,信号量的开销要比自旋锁大,因为信号量使线程进入休眠状态以后会切换线程,切换线程就会有开销。总结一下信号量的特点:
①、因为信号量可以使等待资源线程进入休眠状态,因此适用于那些占用资源比较久的场合。
②、因此信号量不能用于中断中,因为信号量会引起休眠,中断不能休眠。
③、如果共享资源的持有时间比较短,那就不适合使用信号量了,因为频繁的休眠、切换线程引起的开销要远大于信号量带来的那点优势。
信号量有一个信号量值,相当于一个房子有 10 把钥匙,这 10 把钥匙就相当于信号量值为10。因此,可以通过信号量来控制访问共享资源的访问数量,如果要想进房间,那就要先获取一把钥匙,信号量值减 1,直到 10 把钥匙都被拿走,信号量值为 0,这个时候就不允许任何人进入房间了,因为没钥匙了。如果有人从房间出来,那他要归还他所持有的那把钥匙,信号量值加 1,此时有 1 把钥匙了,那么可以允许进去一个人。相当于通过信号量控制访问资源的线程数,在初始化的时候将信号量值设置的大于 1,那么这个信号量就是计数型信号量,计数型信号量不能用于互斥访问,因为它允许多个线程同时访问共享资源。如果要互斥的访问共享资源那么信号量的值就不能大于 1,此时的信号量就是一个二值信号量。
Linux 内核使用 semaphore 结构体表示信号量,结构体内容如下所示:
struct semaphore {
raw_spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};
要想使用信号量就得先定义,然后初始化信号量。有关信号量的 API 函数如下所示
DEFINE_SEAMPHORE(name) //定义一个信号量,并且设置信号量的值为 1。
void sema_init(struct semaphore *sem, int val) //初始化信号量 sem,设置信号量值为 val。
void down(struct semaphore *sem)//获取信号量,因为会导致休眠,因此不能在中断中使用。
int down_trylock(struct semaphore *sem)//尝试获取信号量,如果能获取到信号量就获取,并且返回 0。如果不能就返回非 0,并且不会进入休眠。
int down_interruptible(struct semaphore *sem)//获取信号量,和 down 类似,只是使用 down 进入休眠状态的线程不能被信号打断。而使用此函数进入休眠以后是可以被信号打断的。
void up(struct semaphore *sem) //释放信号量
信号量的使用如下所示:
struct semaphore sem; /* 定义信号量 */
sema_init(&sem, 1); /* 初始化信号量 */
down(&sem); /* 申请信号量 */
/* 临界区 */
up(&sem); /* 释放信号量 */
我们在驱动的 open 函数申请信号量,然后在release 函数中释放信号量。
驱动文件semaphore.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define GPIOLED_CNT 1
#define GPIOLED_NAME "gpioled"
#define LEDOFF 0
#define LEDON 1
/*gpioled 设备结构体*/
struct gpioled_dev
{
dev_t devid;
int major;
int minor;
struct cdev cdev;
struct class *class;
struct device *device;
struct device_node *nd;
int led_gpio;
struct semaphore sem; /* 信号量 */
//struct mutex lock;
};
struct gpioled_dev gpioled; /*LED*/
static int led_open(struct inode *inode, struct file *filp)
{
filp->private_data = &gpioled;
// mutex_lock(&gpioled.lock); /*获取信号量*/
/* 获取信号量,进入休眠状态的进程可以被信号打断 */
if (down_interruptible(&gpioled.sem))
{
return -ERESTARTSYS;
}
#if 0
down(&gpioled.sem); /* 不能被信号打断 */
#endif
return 0;
}
static int led_release(struct inode *inode, struct file *filp)
{
struct gpioled_dev *dev = filp->private_data;
// mutex_unlock(&dev->lock);
up(&dev->sem); /* 释放信号量,信号量值加 1 */
return 0;
}
static ssize_t led_write(struct file *filp, const char __user *buf,
size_t count, loff_t *ppos)
{
int ret;
unsigned char databuf[1];
struct gpioled_dev *dev = filp->private_data;
ret = copy_from_user(databuf, buf, count);
if (ret < 0)
{
return -EINVAL;
}
if (databuf[0] == LEDON)
{
gpio_set_value(dev->led_gpio, 0);
}
else if (databuf[0] == LEDOFF)
{
gpio_set_value(dev->led_gpio, 1);
}
return 0;
}
/*操作集*/
static const struct file_operations led_fops = {
.owner = THIS_MODULE,
.write = led_write,
.open = led_open,
.release = led_release,
};
/*驱动入口函数*/
static int __init led_init(void)
{
int ret = 0;
/*1, 初始化信号量*/
// mutex_init(&gpioled.lock);
sema_init(&gpioled.sem, 1);
/*1,注册字符设备驱动*/
gpioled.major = 0;
if (gpioled.major)
{ /*给定主设备号*/
gpioled.devid = MKDEV(gpioled.major, 0);
register_chrdev_region(gpioled.devid, GPIOLED_CNT, "GPIOLED_NAME");
}
else
{
alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, "GPIOLED_NAME");
gpioled.major = MAJOR(gpioled.devid);
gpioled.minor = MINOR(gpioled.devid);
}
printk("gpioled major =%d, minor =%d \r\n", gpioled.major, gpioled.minor);
/*2,初始化cdev*/
gpioled.cdev.owner = THIS_MODULE;
cdev_init(&gpioled.cdev, &led_fops);
/*3,添加cdev*/
cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
/*4,创建类*/
gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
if (IS_ERR(gpioled.class))
{
return PTR_ERR(gpioled.class);
}
/*5,创建设备*/
gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
if (IS_ERR(gpioled.device))
{
return PTR_ERR(gpioled.device);
}
/*1,获取设备节点*/
gpioled.nd = of_find_node_by_path("/gpioled");
if (gpioled.nd == NULL)
{
ret = -EINVAL;
goto fail_findnode;
}
/*2,获取LED所对应的GPIO*/
gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpios", 0);
if (gpioled.led_gpio < 0)
{
printk("can't find led gpio\r\n");
ret = -EINVAL;
goto fail_findnode;
}
printk("led gpio num =%d\r\n", gpioled.led_gpio);
/*3,申请IO*/
ret = gpio_request(gpioled.led_gpio, "led-gpio");
if (ret)
{
printk("Failed to request the led gpio\r\n");
ret = -EINVAL;
goto fail_findnode;
}
/*4,使用IO,设置为输出*/
ret = gpio_direction_output(gpioled.led_gpio, 1);
if (ret)
{
goto fail_setoutput;
}
/*5,输出低电平,点亮led灯*/
gpio_set_value(gpioled.led_gpio, 0);
return 0;
fail_setoutput:
gpio_free(gpioled.led_gpio);
fail_findnode:
return ret;
}
/*驱动出口函数*/
static void __exit led_exit(void)
{
/*关灯*/
gpio_set_value(gpioled.led_gpio, 1);
/*注销字符设备驱动*/
cdev_del(&gpioled.cdev);
unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
device_destroy(gpioled.class, gpioled.devid);
class_destroy(gpioled.class);
/*释放IO*/
gpio_free(gpioled.led_gpio);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("supersamrt");
第一步:在驱动入口函数中调用 sema_init 函数初始化信号量 sem 的值为 1,相当于 sem是个二值信号量。
第二步:在 open函数中申请信号量,可以使用 down 函数,也可以使用 down_interruptible函数。如果信号量值大于等于 1 就表示可用,那么应用程序就会开始使用 LED 灯。如果信号量值为 0 就表示应用程序不能使用 LED 灯,此时应用程序就会进入到休眠状态。等到信号量值大于 1 的时候应用程序就会唤醒,申请信号量,获取 LED 灯使用权。
第三步:在 release 函数中调用 up 函数释放信号量,这样其他因为没有得到信号量而进入休眠状态的应用程序就会唤醒,获取信号量。
当信号量 sem 为 1 的时候表示 LED 灯还没有被使用,如果应用程序 A 要使用LED 灯,先调用 open 函数打开/dev/gpioled,这个时候会获取信号量 sem,获取成功以后 sem 的值减 1 变为 0。如果此时应用程序 B 也要使用 LED 灯,调用 open 函数打开/dev/gpioled 就会因为信号量无效(值为 0)而进入休眠状态。当应用程序 A 运行完毕,调用 close 函数关闭/dev/gpioled的时候就会释放信号量 sem,此时信号量 sem 的值就会加 1,变为 1。信号量 sem 再次有效,表示其他应用程序可以使用 LED 灯了,此时在休眠状态的应用程序 B 就会获取到信号量 sem,获取成功以后就开始使用 LED 灯。
测试APP semaApp.c
#include
#include
#include
#include
#include
#include
#include
#define LEDOFF 0
#define LEDON 1
int main(int argc, char *argv[])
{
int fd, retvalue;
char *filename;
unsigned char databuf[1];
unsigned char cnt;
if (argc != 3)
{
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if (fd < 0)
{
printf("file %s open failed!\r\n", filename);
return -1;
}
databuf[0] = atoi(argv[2]); /*将字符转化为数字*/
retvalue = write(fd, databuf, sizeof(databuf));
if (retvalue < 0)
{
printf("LED Control Failed ! \r\n");
close(fd);
return -1;
}
/*模拟应用占用驱动25s*/
while(1)
{
sleep(5);
cnt++;
printf("App runing times:%d\r\n",cnt);
if(cnt>=5) break;
}
printf("App runing finish!\r\n");
close(fd);
return 0;
}
1、编译驱动程序(略)
2、编译测试 APP (略)
3、运行测试
加载驱动
测试驱动
两个命令都是运行在后台,第一条命令先获取到信号量,因此可以操作 LED 灯,将LED 灯打开,并且占有 25S。第二条命令因为获取信号量失败而进入休眠状态,等待第一条命令运行完毕并释放信号量以后才拥有 LED 灯使用权,将 LED 灯关闭,运行结果如上图所示