Linux驱动之信号量

信号量是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 <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <unistd.h>

#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 <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <unistd.h>

#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的区别在于每个线程在打开完设备文件后就立刻关闭设备文件,这样做就不会导致信号量被获取而没有被释放的情况,确保了系统中的进程不会因等待信号量而进入无休止的循环。
在这里插入图片描述

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值