信号量是Linux内核的一种同步机制,信号量按照初始值的不同可以分为两种。一种是计数信号量,一种是二值信号量。当信号量的初始值大于等于2时就称为计数信号量,当信号量的初始值等于1时就称为二值信号量。
// 信号量结构体
struct semaphore {
raw_spinlock_t lock; // 自旋锁
unsigned int count; // 信号量的计数值
struct list_head wait_list; // 信号量等待链表
};
/*
信号量初始化函数
semaphore :信号量指针
val:信号量初始值
*/
void sema_init(struct semaphore *sem, int val)
void down(struct semaphore *sem); // 获取信号量,如果获取不到就进入睡眠
int __must_check down_interruptible(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); // 释放信号量
下面来写一个驱动程序和应用程序来进行简单的信号量测试。
驱动程序
#define CHRDEV_MAJOR 240 // 主设备号
#define CHRDEV_MAION 0 // 次设备号
#define CHRDEV_COUNT 1 // 次设备号个数
#define CHRDEV_NAME "testchrdev"
struct led_cdev
{
struct cdev chrdevcdev;
int major;
dev_t dev;
struct class *led_dev_class;
struct semaphore led_sem;// 定义信号量
};
static struct led_cdev leddev;
ssize_t chrdev_read (struct file *file, char __user *usr, size_t size, loff_t *loff)
{
printk("%s\r\n",__func__);
return 0;
}
int chrdev_open (struct inode *inode, struct file *file)
{
if (down_trylock(&leddev.led_sem) != 0) { // 当应用程序打开文件时会尝试申请信号量
return -EBUSY; // 当信号量已经申请完时,就返回错误码
}
file->private_data = &leddev;
return 0;
}
int chrdev_release (struct inode *inode, struct file *file)
{
struct led_cdev *led_private_data = (struct led_cdev *)file->private_data;
up(&led_private_data->led_sem); // 当应用程序关闭文件时释放信号量
return 0;
}
struct file_operations fops =
{
.open = chrdev_open,
.read = chrdev_read,
.release = chrdev_release,
};
static int __init chrdev_init(void)
{
int ret = 0,error = 0;
struct device *devices;
//DEBUG_SFLR("%s\r\n",__func__);
error = alloc_chrdev_region(&leddev.dev,CHRDEV_MAION,CHRDEV_COUNT,CHRDEV_NAME); // 注册设备号
printk("MAJOR = %d MINOR = %d\r\n",MAJOR(leddev.dev),MINOR(leddev.dev));
if(error < 0){
printk("alloc_chrdev_region error\r\n");
ret = -EBUSY;
goto fail;
}
leddev.major = MAJOR(leddev.dev);
cdev_init(&leddev.chrdevcdev, &fops); // 绑定字符设备操作函数集
error = cdev_add(&leddev.chrdevcdev,leddev.dev,CHRDEV_COUNT); // 添加字符设备
if(error < 0){
printk("cdev_add error\r\n");
ret = -EBUSY;
goto fail1;
}
// 创建类,类名为testledclass
leddev.led_dev_class = class_create(THIS_MODULE, "testledclass");
if (IS_ERR(leddev.led_dev_class)){
printk("class_create error\r\n");
ret = -EBUSY;
goto fail2;
}
// 创建设备
devices = device_create(leddev.led_dev_class, NULL, MKDEV(leddev.major,0), NULL, "testled");
if(NULL == devices){
printk("device_create error\r\n");
ret = -EBUSY;
goto fail3;
}
sema_init(&leddev.led_sem,2); // 初始化信号量
return 0;
fail3:
class_destroy(leddev.led_dev_class);/* 删除类 */
fail2:
cdev_del(&leddev.chrdevcdev);/* 删除cdev */
fail1:
unregister_chrdev_region(leddev.dev,CHRDEV_COUNT);
fail:
return ret;
}
static void __exit chrdev_exit(void)
{
//DEBUG_SFLR("%s\r\n",__func__);
device_destroy(leddev.led_dev_class,MKDEV(leddev.major,0));/* 卸载设备 */
class_destroy(leddev.led_dev_class);/* 删除类 */
cdev_del(&leddev.chrdevcdev);/* 删除cdev */
unregister_chrdev_region(leddev.dev,CHRDEV_COUNT);
}
module_init(chrdev_init);
module_exit(chrdev_exit);
MODULE_DESCRIPTION("xxxxxx");
MODULE_AUTHOR("xxxxxx");
MODULE_LICENSE("GPL");
在第82行中,利用sema_init函数初始化一些信号量,初始值为2,是一个计数信号量。在chrdev_open 函数中获取信号量,在chrdev_release函数中释放信号量。
下面是对应的应用程序
应用程序1
#include
#include
#include
#include
#include
#include
#define FILE_NAME "/dev/testled"
void *pthread_func1 (void *arg)
{
int fd = -1;
fd = open(FILE_NAME,O_RDONLY);
if(fd < 0){
printf("%s open %s error\r\n",__func__,FILE_NAME);
pthread_exit(0);
}
printf("%s open %s success\r\n",__func__,FILE_NAME);
while(1);
}
void *pthread_func2(void *arg)
{
int fd = -1;
fd = open(FILE_NAME,O_RDONLY);
if(fd < 0){
printf("%s open %s error\r\n",__func__,FILE_NAME);
pthread_exit(0);
}
printf("%s open %s success\r\n",__func__,FILE_NAME);
while(1);
}
void *pthread_func3(void *arg)
{
int fd = -1;
fd = open(FILE_NAME,O_RDONLY);
if(fd < 0){
printf("%s open %s error\r\n",__func__,FILE_NAME);
pthread_exit(0);
}
printf("%s open %s success\r\n",__func__,FILE_NAME);
while(1);
}
int main(void)
{
pthread_t pth1,pth2 ,pth3;
int err = 0;
pthread_create(&pth1,NULL,pthread_func1,NULL);
sleep(2);
pthread_create(&pth2,NULL,pthread_func2,NULL);
sleep(2);
pthread_create(&pth3,NULL,pthread_func3,NULL);
sleep(2);
while(1);
return 0;
}
在main函数中会每隔2秒创建一条线程,总共创建3条线程。每条线程都会去打开上述驱动中创建的设备文件,然后进入死循环。将驱动程序和应用程序编译到开发板中观察运行结果。
可以看到线程1和线程2都能正常打开设备文件,但是线程3打开失败。那是因为线程1和线程2打开设备文件后,信号量的计数值已经变成了0,但是线程1和线程2在打开完文件后没有关闭文件就进入了死循环,并没有释放信号量。当线程3想打开设备文件时,信号量会先减1,变成-1,导致没有多余的信号量可以使用,于是驱动就返回错误码,导致线程3打开文件失败。
下面再来看一段代码
应用程序2
#include
#include
#include
#include
#include
#include
#define FILE_NAME "/dev/testled"
void *pthread_func1 (void *arg)
{
int fd = -1;
fd = open(FILE_NAME,O_RDONLY);
if(fd < 0){
printf("%s open %s error\r\n",__func__,FILE_NAME);
pthread_exit(0);
}
printf("%s open %s success\r\n",__func__,FILE_NAME);
close(fd);
while(1);
}
void *pthread_func2(void *arg)
{
int fd = -1;
fd = open(FILE_NAME,O_RDONLY);
if(fd < 0){
printf("%s open %s error\r\n",__func__,FILE_NAME);
pthread_exit(0);
}
printf("%s open %s success\r\n",__func__,FILE_NAME);
close(fd);
while(1);
}
void *pthread_func3(void *arg)
{
int fd = -1;
fd = open(FILE_NAME,O_RDONLY);
if(fd < 0){
printf("%s open %s error\r\n",__func__,FILE_NAME);
pthread_exit(0);
}
printf("%s open %s success\r\n",__func__,FILE_NAME);
close(fd);
while(1);
}
int main(void)
{
pthread_t pth1,pth2 ,pth3;
int err = 0;
pthread_create(&pth1,NULL,pthread_func1,NULL);
sleep(2);
pthread_create(&pth2,NULL,pthread_func2,NULL);
sleep(2);
pthread_create(&pth3,NULL,pthread_func3,NULL);
sleep(2);
while(1);
return 0;
}
应用程序2与应用程序1的区别在于每个线程在打开完设备文件后就立刻关闭设备文件,这样做就不会导致信号量被获取而没有被释放的情况,确保了系统中的进程不会因等待信号量而进入无休止的循环。