本系列文章主要講述內核中的互斥與同步操作,主要包括內核中的鎖機制,信號量和互斥體,講述了基礎概念和常用的API函數接口和代碼示例,詳細目錄如下:
01 - 內核中的互斥與同步概述
02 - 原子變量應用示例
03 - 自旋鎖應用示例
04 - 信號量的應用示例
05 - 互斥量的應用示例
上個示例 02-原子變量應用示例 使用原子變量實現了一次只能有一個應用程序訪問一個設備,本節我們使用自旋鎖來實現此功能。在使用自旋鎖之前,先回顧一下自旋鎖的使用注意事項:
①、自旋鎖保護的臨界區要儘可能的短,因此在 open 函數中申請自旋鎖,然後在 release 函數中釋放自旋鎖的方法就不可取。我們可以使用一個變量來表示設備的使用情況,如果設備被使用了那麼變量就一,設備被釋放以後變量就減 1,我們只需要使用自旋鎖保護這個變量即 可。
②、考慮驅動的兼容性,合理的選擇 API 函數。
綜上所述,在本次例程中採用自旋鎖和原子變量結合的方式來實現一次只能一個應用程序打開一個設備的功能,定義一個原子變量 lock_status 表示設備的使用狀態,爲1表示設備可用,0表示設備不可用。在open函數中首先上鎖判斷 lock_status 的狀態,如果爲1則表示設備可用,然後接着執行,將 lock_status 自減然後解鎖執行下面的程序,如果 lock_status 爲0,表示設備不可用直接 return 並解鎖。
自旋鎖的使用步驟如下:
1. spinlock_t my_lock; // 定義一個自旋鎖
2. spin_lock_init(&my_lock); // 初始化自旋鎖
3. spin_lock_irqsave(&my_lock, flags); // 上鎖
4. ... ... // 訪問共享資源
5. spin_unlock_irqrestore(&my_lock, flags); //解鎖
詳細的代碼實現過程如下,首先定義一個 demo_struct 結構體裏面包含了自旋鎖和設備使用狀態的變量,在 demo_init 函數中開闢空間,並且初始化自旋鎖和設備的初始使用狀態值(初始值爲1),在 demo_open 和 demo_release 函數中對自旋鎖進行了具體的操作,最後對整個代碼的執行結果進行了驗證,見本文1.4節測試結果一段。
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;
atomic_t lock_status; // 定義設備使用狀態,1表示可用,0表示不可用
spinlock_t my_lock; // 定義一個自旋鎖
}demo_struct;
demo_struct *my_demo_dev = NULL; // 定義一個設備結構體指針,指向NULL
static int demo_open(struct inode *inode, struct file *filp)
{
unsigned long flags;
/* 首先判斷設備是否可用 */
spin_lock_irqsave(&my_demo_dev->my_lock, flags); // 上鎖,保存中斷屏蔽狀態到flags中,並禁止本地中斷
if (atomic_read(&my_demo_dev->lock_status) == 0) // 設備不可用
{
printk("device busy");
spin_unlock_irqrestore(&my_demo_dev->my_lock, flags);
goto err0;
}
atomic_dec(&my_demo_dev->lock_status);
spin_unlock_irqrestore(&my_demo_dev->my_lock, flags); // 解鎖,將中斷狀態恢復到之前的狀態,並激活本地中斷
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)
{
unsigned long flags;
/* 首先判斷設備是否可用 */
spin_lock_irqsave(&my_demo_dev->my_lock, flags); // 上鎖,保存中斷屏蔽狀態到flags中,並禁止本地中斷
if (atomic_read(&my_demo_dev->lock_status) == 0) // 設備不可用
{
atomic_inc(&my_demo_dev->lock_status); // 自加1,將其恢復到初始狀態,一攻其他應用程序使用
}
spin_unlock_irqrestore(&my_demo_dev->my_lock, flags); // 解鎖,將中斷狀態恢復到之前的狀態,並激活本地中斷
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"); // 給設備類的名字賦值
atomic_set(& my_demo_dev->lock_status , 1); // 初始化鎖的狀態爲1,表示此時可以用
spin_lock_init(&my_demo_dev->my_lock); // 初始化自旋鎖
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 <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 // 加載模塊
[ 47.879080] demo_init -- 71.
root@am335x-evm:~# ./app & // 後臺運行
[1] 925
[ 50.816857] demo_open -- 36.
root@am335x-evm:~# ./app // 在上一個運行期間,再運行一次
[ 53.073234] device busy // 內核打印的,連着打開兩次,出現device busy
open: Device or resource busy // 應用層打印的
[ 55.820469] demo_release -- 56. // 5秒之後,第一次打開的設備關閉
root@am335x-evm:~# ./app // 再次打開,
[ 64.482860] demo_open -- 36. // 可以成功打開
[ 69.486465] demo_release -- 56. // 5秒之後關閉
root@am335x-evm:~# rmmod demo.ko // 卸載模塊
[ 76.329375] demo_exit -- 136.