1、前言
在Linux驅動程序編寫中,使用DEVICE_ATTR宏,可以定義一個struct device_attribute設備屬性,並使用sysfs的API函數,便可以在設備目錄下創建出屬性文件,當我們在驅動程序中實現了show和store函數后,便可以使用cat和echo命令對創建出來的設備屬性文件進行讀寫,從而達到控制設備的功能。
2、宏DEVICE_ATTR定義
在講解DEVICE_ATTR宏之前,先了解一些基本的結構體,首先是struct attribute結構體,其定義在include/linux/device.h中,結構體定義如下所示:
struct attribute { const char *name; umode_t mode; #ifdef CONFIG_DEBUG_LOCK_ALLOC bool ignore_lockdep:1; struct lock_class_key *key; struct lock_class_key skey; #endif };
該結構體有兩個重要的成員,分別是name和mode,其中name代表屬性的名稱,一般表示為文件名,mode代表該屬性的讀寫權限,也就是屬性文件的讀寫權限。
關於文件的權限詳解,可以參考下面的鏈接:
https://blog.csdn.net/DLUTBruceZhang/article/details/8658475
接下來要了解的結構體為struct device_attribute,該結構體的定義在include /linux/device.h,其定義如下:
/* interface for exporting device attributes */ struct device_attribute { struct attribute attr; ssize_t (*show)(struct device *dev, struct device_attribute *attr, char *buf); ssize_t (*store)(struct device *dev, struct device_attribute *attr, const char *buf, size_t count); };
該結構體其實是struct attribute結構體的進一步封裝,並提供了兩個函數指針,show函數用於讀取設備的屬性文件,而store則是用於寫設備的屬性文件,當我們在Linux的驅動程序中實現了這兩個函數后,便可以使用cat和echo命令對設備屬性文件進行讀寫操作。
了解了一下基本的結構體,接下來,進一步分析宏DEVICE_ATTR的實現,在Linux內核源碼中,宏DEVICE_ATTR的定義在include/linux/device.h文件中,如下:
#define DEVICE_ATTR(_name, _mode, _show, _store) \ struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
而__ATTR宏的定義在include/linux/sysfs.h文件中,如下:
#define __ATTR(_name, _mode, _show, _store) { \ .attr = {.name = __stringify(_name), \ .mode = VERIFY_OCTAL_PERMISSIONS(_mode) }, \ .show = _show, \ .store = _store, \ }
通過上面的宏展開可以發現,其實宏DEVICE_ATTR實現的功能就是定義了一個struct device_attribute結構體變量dev_attr_name,並對里面的成員進行初始化,包括struct attribute結構體里面的name和mode成員變量,然后還有實現屬性文件讀寫的show和store函數賦值,非常簡單。
3、使用示例
接下來對宏DEVICE_ATTR使用示例進行分析,該示例在內核中將100個字節虛擬成一個設備,在驅動中實現設備屬性文件的讀寫函數,示例如下:
首先是設備屬性的定義,以及設備屬性文件讀寫函數的實現:
static ssize_t mydevice_show(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%s\n", mybuf); } static ssize_t mydevice_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { sprintf(mybuf, "%s", buf); return count; } static DEVICE_ATTR(mydevice, 0644, mydevice_show, mydevice_store);
在上面的代碼中,使用宏DEVICE_ATTR定義了一個mydevice的屬性文件,而且這個屬性文件的讀寫權限為:文件所擁有者可讀寫,組和其他人只能讀。代碼還實現了該屬性文件的讀寫函數mydevice_show()和mydevice_store(),當在用戶空間中使用cat和echo命令時,將會調用到驅動程序中實現的兩個函數。
接下來是模塊的加載函數,當模塊加載時將會被調用,該函數的實現代碼如下:
static int __init mydevice_init(void) { int ret; struct device *mydevice; major = register_chrdev(0, "mydevice", &myfops); if (major < 0) { ret = major; return ret; } myclass = class_create(THIS_MODULE, "myclass"); if (IS_ERR(myclass)) { ret = -EBUSY; goto fail; } mydevice = device_create(myclass, NULL, MKDEV(major, 0), NULL, "mydevice"); if (IS_ERR(mydevice)) { class_destroy(myclass); ret = -EBUSY; goto fail; } ret = sysfs_create_file(&mydevice->kobj, &dev_attr_mydevice.attr); if (ret < 0) return ret; return 0; fail: unregister_chrdev(major, "mydevice"); return ret; }
函數首先調用register_chrdev()完成一個主設備號的動態申請,設備的名稱為mydevice,然后調用class_create()和device_create()在sysfs中動態創建出設備所屬的類myclass和mydevice設備,需要注意的是,這兩個函數調用后,要對返回的結果進行錯誤檢測,最后,使用sysfs的API函數sysfs_create_file()在sysfs中創建出設備的屬性文件,完成驅動模塊的加載。
接下來是該模塊的卸載函數,函數的代碼如下所示:
static void __exit mydevice_exit(void) { device_destroy(myclass, MKDEV(major, 0)); class_destroy(myclass); unregister_chrdev(major, "mydevice"); }
模塊的卸載函數與模塊的加載函數是相對的操作,需要調用device_destory()和class_destory()對模塊加載創建的myclass和mydevice進行銷毀,然后調用unregister_chrdev()將動態分配的主設備號進行釋放。
驅動程序完整代碼如下:
#include <linux/module.h> #include <linux/init.h> #include <linux/sysfs.h> #include <linux/string.h> #include <linux/device.h> #include <linux/fs.h> static char mybuf[100] = "mydevice"; static int major; static struct class *myclass; static ssize_t mydevice_show(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%s\n", mybuf); } static ssize_t mydevice_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { sprintf(mybuf, "%s", buf); return count; } static DEVICE_ATTR(mydevice, 0644, mydevice_show, mydevice_store); static struct file_operations myfops = { .owner = THIS_MODULE, }; static int __init mydevice_init(void) { int ret; struct device *mydevice; major = register_chrdev(0, "mydevice", &myfops); if (major < 0) { ret = major; return ret; } myclass = class_create(THIS_MODULE, "myclass"); if (IS_ERR(myclass)) { ret = -EBUSY; goto fail; } mydevice = device_create(myclass, NULL, MKDEV(major, 0), NULL, "mydevice"); if (IS_ERR(mydevice)) { class_destroy(myclass); ret = -EBUSY; goto fail; } ret = sysfs_create_file(&mydevice->kobj, &dev_attr_mydevice.attr); if (ret < 0) return ret; return 0; fail: unregister_chrdev(major, "mydevice"); return ret; } static void __exit mydevice_exit(void) { device_destroy(myclass, MKDEV(major, 0)); class_destroy(myclass); unregister_chrdev(major, "mydevice"); } module_init(mydevice_init); module_exit(mydevice_exit); MODULE_DESCRIPTION("A simplest driver"); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("HLY");
4、測試結果
將驅動程序進行編譯后,將驅動模塊進行加載,並查看創建出來的屬性文件,使用cat和echo命令進行讀寫測試:
# make # insmod simple-device.ko # cd /sys/devices/virtual/myclass/mydevice/ # ls –al ./
結果如下所示,可以看到創建出來的屬性文件mydevice:
使用cat和echo命令進行文件讀寫測試:
# cat mydevice # echo "I am a simplest driver." > mydevice # cat mydevice
運行結果如下所示,可以看到,能使用cat和echo命令正常完成屬性文件的讀寫了:
5、小節
本文簡單介紹了Linux內核中DEVICE_ATTR宏的實現,並使用一個簡單的驅動程序示例來介紹了如何在Linux驅動程序中使用DEVICE_ATTR宏以及實現屬性文件的讀寫函數。
參考:
https://blog.csdn.net/hpu11/article/details/83113729
https://blog.csdn.net/DLUTBruceZhang/article/details/8658475
《LINUX設備驅動程序(第三版)》