本系列文章主要讲述内核中的互斥与同步操作,主要包括内核中的锁机制,信号量和互斥体,讲述了基础概念和常用的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.