本系列文章主要讲述内核中的互斥与同步操作,主要包括内核中的锁机制,信号量和互斥体,讲述了基础概念和常用的API函数接口和代码示例,详细目录如下:
01 - 内核中的互斥与同步概述
02 - 原子变量应用示例
03 - 自旋锁应用示例
04 - 信号量的应用示例
05 - 互斥量的应用示例
本实例使用原子变量实现了一个应用程序只能打开一个设备的目的,在init函数中初始化原子变量的值为1,每次打开设备将原子变量的值减1。
在open函数中对原子变量的值进行判断,如果原子变量的值等于0表示只有一个应用程序打开了设备,可以进行其他操作,如果原理变量的值小于0,那么表示此时有应用程序正在使用设备,返回一个错误码EBUSY,并将原子变量的值+1(目的是当正在打开设备的应用程序关闭时,原子变量的值还能恢复到初始值1)。
具体的代码部分如下所示,文章最后将测试结果附上,并对其进行了解释,详情请参考示例代码。
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>
dev_t dev_no;
char devname[] = "demo_chrdev";
char classname[] = "demo_class";
struct cdev demo_cdev;
struct class *cls;
struct device *dev;
atomic_t atomic_v; // 定义一个原子变量,在init函数中初始化
static int demo_open(struct inode *inode, struct file *filp)
{
/*
每次打开设备原子变量自减1,atomic_dec_and_test结果为0返回true
*/
if ( !atomic_dec_and_test(&atomic_v) )
{
printk("device busy.\n");
atomic_inc(&atomic_v);
goto err0;
}
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
return 0;
err0:
return -EBUSY; /* Device or resource busy */
}
static int demo_release(struct inode *inode, struct file *filp)
{
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
atomic_inc(&atomic_v); // 关闭设备后,原子变量值+1,其他程序可以访问
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__);
atomic_set(&atomic_v, 1) ; // 原子变量初始化为1
ret = alloc_chrdev_region(&dev_no, 0, 0, devname);
if (ret)
{
printk("alloc_chrdev_region failed.\n");
goto region_err;
}
cdev_init(&demo_cdev, &demo_ops);
ret = cdev_add(&demo_cdev, dev_no, 1);
if (ret < 0)
{
printk("cdev_add failed.\n");
goto add_err;
}
cls = class_create(THIS_MODULE, classname); /* /sys/class/.. */
if ( IS_ERR(cls) )
{
ret = PTR_ERR(cls);
printk("class_create failed.\n");
goto cls_err;
}
dev = device_create(cls, NULL, dev_no, NULL, "chrdev%d", 0); /* /dev/.. */
if ( IS_ERR(dev) )
{
ret = PTR_ERR(dev);
printk("device_create failed.\n");
goto dev_err;
}
return 0;
dev_err:
class_destroy(cls);
cls_err:
cdev_del(&demo_cdev);
add_err:
unregister_chrdev_region(dev_no, 1);
region_err:
return ret;
}
static void __exit demo_exit(void)
{
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
device_destroy(cls, dev_no);
class_destroy(cls);
cdev_del(&demo_cdev);
unregister_chrdev_region(dev_no, 1);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
1.2 test.c
示例的应用层测试代码,包含了设备的打开和关闭函数,在打开和关闭之间延时5秒钟,方便测试第二个应用程序打开设备的结果。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
int fd;
fd = open("/dev/chrdev0", O_RDWR, 0666);
if (fd < 0)
{
perror("open");
return -1;
}
sleep(5);
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 // 加载模块
[ 53.871028] demo_init -- 53.
root@am335x-evm:~# ./app & // 后台运行
[1] 903
[ 61.966737] demo_open -- 27. // 第一次成功打开
root@am335x-evm:~# ./app // 在上一个运行期间,再运行一次
[ 64.006403] device busy. // 内核打印的信息,连着打开两次,出现device busy
[ 66.970339] open: Device or resource busy // 应用层打印的信息
root@am335x-evm:~# demo_release -- 37. // 5秒之后,第一次打开的设备关闭
root@am335x-evm:~# ./app // 再次打开,可以成功打开
[ 71.853160] demo_open -- 27.
[ 76.856765] demo_release -- 37. // 5秒之后关闭
root@am335x-evm:~# rmmod demo.ko // 卸载模块
[ 86.067955] demo_exit -- 104.