05-互斥量的應用示例

 本系列文章主要講述內核中的互斥與同步操作,主要包括內核中的鎖機制,信號量和互斥體,講述了基礎概念和常用的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.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章