04-信号量的应用

 本系列文章主要讲述内核中的互斥与同步操作,主要包括内核中的锁机制,信号量和互斥体,讲述了基础概念和常用的API函数接口和代码示例,详细目录如下:
01 - 内核中的互斥与同步概述
02 - 原子变量应用示例
03 - 自旋锁应用示例
04 - 信号量的应用示例
05 - 互斥量的应用示例

 本节我们来使用信号量实现了一次只能有一个应用程序访问设备,信号量可以导致休眠,因此信号量保护的临界区没有运行时间限制,可以在驱动的 open 函数申请信号量,然后在release 函数中释放信号量。但是信号量不能用在中断中。

 信号量的特点如下:
 ①、因为信号量可以使等待资源线程进入休眠状态,因此适用于那些占用资源比较久的场合。
 ②、因此信号量不能用于中断中,因为信号量会引起休眠,中断不能休眠。
 ③、如果共享资源的持有时间比较短,那就不适合使用信号量了,因为频繁的休眠、切换线程引起的开销要远大于信号量带来的那点优势,因此在不违背自旋锁的前提下应当优先选用自旋锁。

 具体的代码部分如下所示,文章最后将测试结果附上,并对其进行了解释,详情请参考示例代码。

1 示例代码

1.1 demo.c

 驱动部分的代码,主要包含了信号量的实现和判断,init、open和release函数的实现,具体内容在代码中进行注释。

#include <linux/module.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <asm/atomic.h>
#include <linux/slab.h>				// kzalloc和kfree的头文件

typedef struct 
{
	dev_t dev_no;
	char devname[20];
	char classname[20];
	struct cdev demo_cdev;
	struct class *cls;
	struct device *dev;
	struct semaphore sem;			// 定义一个信号量,在init函数中进行初始化
}demo_struct;
demo_struct *my_demo_dev = NULL;	// 定义一个设备结构体指针,指向NULL

static int demo_open(struct inode *inode, struct file *filp)
{
	/* 首先判断设备是否可用 */
	if( down_interruptible(&my_demo_dev->sem) )		// 能够获取信号量则返回0
	{
		return -ERESTARTSYS;						// 不能获取返回错误码,进程休眠
	}
	
	printk("%s -- %d.\n", __FUNCTION__, __LINE__);
	
	return 0;

}

static int demo_release(struct inode *inode, struct file *filp)
{
	/* 释放信号量 */
	up(&my_demo_dev->sem);		// 无返回值,其他进程可以获得信号量

	printk("%s -- %d.\n", __FUNCTION__, __LINE__);
	
	return 0;
}

struct file_operations demo_ops = {
	.open = demo_open,
	.release=  demo_release,
};

static int __init demo_init(void)
{
	int ret;

	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	/* 开辟空间 */
	my_demo_dev =  kzalloc(sizeof(demo_struct), GFP_KERNEL);
	if ( IS_ERR(my_demo_dev) )
	{
		printk("kzalloc failed.\n");
		ret = PTR_ERR(my_demo_dev);
		goto kzalloc_err;
	}
	strcpy(my_demo_dev->devname, "demo_chrdev");	// 给设备名字赋值
	strcpy(my_demo_dev->classname, "demo_class");	// 给设备类的名字赋值

	sema_init(&my_demo_dev->sem, 1);				// 初始化信号量,并赋初值为2
	
	ret = alloc_chrdev_region(&my_demo_dev->dev_no, 0, 0, my_demo_dev->devname);
	if (ret)
	{
		printk("alloc_chrdev_region failed.\n");
		goto region_err;
	}

	cdev_init(&my_demo_dev->demo_cdev, &demo_ops);

	ret = cdev_add(&my_demo_dev->demo_cdev, my_demo_dev->dev_no, 1);
	if (ret < 0)
	{
		printk("cdev_add failed.\n");
		goto add_err;
	}

	my_demo_dev->cls = class_create(THIS_MODULE, my_demo_dev->classname);		/* 在目录/sys/class/.. */
	if ( IS_ERR(my_demo_dev->cls) )
	{
		ret = PTR_ERR(my_demo_dev->cls);
		printk("class_create failed.\n");
		goto cls_err;
	}

	my_demo_dev->dev = device_create(my_demo_dev->cls, NULL, my_demo_dev->dev_no, NULL, "chrdev%d", 0);	/* 在目录/dev/.. */
	if ( IS_ERR(my_demo_dev->dev) )
	{
		ret = PTR_ERR(my_demo_dev->dev);
		printk("device_create failed.\n");
		goto dev_err;
	}
	
	return 0;

dev_err:
	class_destroy(my_demo_dev->cls);	
cls_err:
	cdev_del(&my_demo_dev->demo_cdev);
add_err:
	unregister_chrdev_region(my_demo_dev->dev_no, 1);
region_err:
	kfree(my_demo_dev);		// 释放空间,避免内存泄漏
kzalloc_err:
	return ret;
}


static void __exit demo_exit(void)
{
	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	device_destroy(my_demo_dev->cls, my_demo_dev->dev_no);
	class_destroy(my_demo_dev->cls);
	cdev_del(&my_demo_dev->demo_cdev);
	unregister_chrdev_region(my_demo_dev->dev_no, 1);
	kfree(my_demo_dev);		// 释放空间,避免内存泄漏
}

module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");

1.2 test.c

 示例的应用层测试代码,包含了设备的打开和关闭函数,在打开和关闭之间延时5秒钟,方便测试第二个应用程序打开设备的结果。同时相比较上一个示例增加了传参功能,以便更好的区分是不同的应用程序所打印的内容。

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
	int fd;
	int i, count = 1;

	if(argc < 2)
	{
		printf("please input param: \n");
		return -1;
	}

	fd = open("/dev/chrdev0", O_RDWR, 0666);
	if (fd < 0)
	{
		perror("open");
		return -1;
	}

	for (i=0; i<5; i++)
	{
		printf("process %d : count = %d\n",atoi(argv[1]), count);		// 用于区别是哪个应用程序所打印的
		count++;
		sleep(1);
	}

	close(fd);

	return 0;
}

1.3 Makefile

KERNELDIR ?= /home/linux/ti-processor-sdk-linux-am335x-evm-04.00.00.04/board-support/linux-4.9.28/
PWD := $(shell pwd)

all:
	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) modules
	arm-linux-gnueabihf-gcc test.c -o app
install:
	sudo cp *.ko  app /tftpboot
clean:
	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) clean
	rm app

obj-m += demo.o

1.4 测试结果

root@am335x-evm:~# insmod demo.ko 		// 加载模块
[	47.409156] demo_init -- 53.		
root@am335x-evm:~# ./app 1 &			// 后台运行应用程序1
[1] 921
[	72.317511] demo_open -- 28.			
process 1 : count = 1					// 应用层开始打印程序1的信息
root@am335x-evm:~# ./app 2 &			// 运行应用程序2
[2] 922
process 1 : count = 2					// 信号量没有释放,一直打印程序1的信息
process 1 : count = 3
process 1 : count = 4
process 1 : count = 5
[	77.329346] demo_release -- 39.		// 程序1执行完释放信号量
[	77.332688] demo_open -- 28.			// 程序2获得信号量开始执行
process 2 : count = 1					// 应用层开始打印程序2的信息
process 2 : count = 2
process 2 : count = 3
process 2 : count = 4
process 2 : count = 5
[	82.337123] demo_release -- 39.		// 程序2执行完释放信号量
[1]-  Done					  ./app 1
[2]+  Done					  ./app 2
root@am335x-evm:~# rmmod demo.ko 
[  108.937996] demo_exit -- 117.
发布了57 篇原创文章 · 获赞 64 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_36310253/article/details/102912156