本系列文章主要講述內核中的互斥與同步操作,主要包括內核中的鎖機制,信號量和互斥體,講述了基礎概念和常用的API函數接口和代碼示例,詳細目錄如下:
01 - 內核中的互斥與同步概述
02 - 原子變量應用示例
03 - 自旋鎖應用示例
04 - 信號量的應用示例
05 - 互斥量的應用示例
信號量除了不能用於中斷的上下文,還有一個缺點就是不是很智能。在獲取信號量的代碼中只要信號量的值爲0,進程就馬上休眠了。但是更一般的情況是,在不用等待很長的時間後信號量馬上就可以獲得,那麼信號量的操作就要經歷使進程先休眠再被喚醒的一個漫長過程。可以在信號量不能獲取的時候稍微耐心等待一小段時間,如果在這段時間能夠獲取信號量,那麼獲取信號量的操作就可以立即返回,否則再將進程休眠也不遲。爲了實現這種比較智能化的信號量,內核提供了另外一種專門用於互斥的高效率信號量,也就是互斥量,也叫互斥體,類型爲 struct mutex,
互斥訪問表示一次只有一個線程可以訪問共享資源,不能遞歸申請互斥體。在我們編寫 Linux 驅動的時候遇到需要互斥訪問的地方建議使用 mutex。互斥體和二值信號量類似,只不過互斥體是專門用於互斥訪問的。
具體的代碼部分如下所示,文章最後將測試結果附上,並對其進行了解釋,詳情請參考示例代碼。
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 mutex my_mutex; // 定義一個互斥體,在init函數中進行初始化
}demo_struct;
demo_struct *my_demo_dev = NULL; // 定義一個設備結構體指針,指向NULL
static int demo_open(struct inode *inode, struct file *filp)
{
/* 首先判斷設備是否可用 */
if( mutex_lock_interruptible(&my_demo_dev->my_mutex) ) // 訪問共享資源之前獲取互斥體,成功獲取返回0
{
return -ERESTARTSYS; // 不能獲取返回錯誤碼
}
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
return 0;
}
static int demo_release(struct inode *inode, struct file *filp)
{
/* 釋放互斥量 */
mutex_unlock(&my_demo_dev->my_mutex);
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"); // 給設備類的名字賦值
mutex_init(&my_demo_dev->my_mutex); // 初始化互斥體
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.