linux設備驅動程序--sysfs用戶接口的使用


linux sysfs文件系統

本文部分內容參考自官方文檔

自2.6版本開始,linux內核開始使用sysfs文件系統,它的作用是將設備和驅動程序的信息導出到用戶空間,方便了用戶讀取設備信息,同時支持修改和調整。

與ext系列和fat等文件系統不同的是,sysfs是一個系統在啟動時構建在內存中虛擬文件系統,一般被掛載在/sys目錄下,既然是存儲在內存中,自然掉電不保存,不能存儲用戶數據。

事實上,在之前也有同樣的虛擬文件系統建立了內核與用戶系統信息的交互,它就是procfs,但是procfs並非針對設備和驅動程序,而是針對整個內核信息的抽象接口。

所以,內核開發人員覺得有必要使用一個獨立的抽象接口來描述設備和驅動信息,畢竟直到目前,驅動代碼在內核代碼中占比非常大,內容也是非常龐雜。這樣可以避免procfs的混亂,子系統之間的分層和分離總是能帶來更清晰地框架。

sysfs的默認目錄結構

上文中提到,sysfs一般被掛載在/sys目錄下,我們可以通過ls /sys來查看sysfs的內容:

block  bus  class  dev  devices  firmware  fs  kernel  module  power

首先需要注意的是,sysfs目錄下的各個子目錄中存放的設備信息並非獨立的,我們可以看成不同的目錄是從不同的角度來描述某個設備信息。

一個設備可能同時有多個屬性,所以對於同一個驅動設備,同時存在於不同的子目錄下,例如:在之前的章節中,我們使用create_dev_node.c編譯出create_dev_node.ko模塊,加載完成之后,我們可以在/sys下面看到當前驅動相關的目錄:

  • /sys/module/create_device_node/
  • /sys/class/basic_class/basic_demo (basic class為驅動程序中創建的class名稱,basic_demo為設備名)
  • /sys/devices/virtual/basic_class/basic_demo (basic class為驅動程序中創建的class名稱,basic_demo為設備名)

理解了這個概念,我們再來簡覽/sys各目錄的功能:

  • /sys/block:該子目錄包含在系統上發現的每個塊設備的一個符號鏈接。符號鏈接指向/sys/devices下的相應目錄。
  • /sys/bus:該目錄包含linux下的總線設備,每個子目錄下主要包含兩個目錄:device和driver,后面會講到linux的總線驅動模型,幾乎都是分層為device和driver來實現的。
  • /sys/class:每一個在內核中注冊了class的驅動設備都會在這里創建一個class設備。
  • /sys/dev:這個目錄下包含兩個子目錄:block和char,分別代表塊設備和字符設備,特別的是,它的組織形式是以major:minor來描述的,即每一個字符設備或者塊設備在這里對應的目錄為其相應的設備號major:minor.
  • /sys/devices:包含整個目錄內核設備樹的描述,其他目錄下的設備多為此目錄的鏈接符號。
  • /sys/firmware:包含查看和操作的接口
  • /sys/fs:包含某些文件系統的子目錄
  • /sys/kernel:包含各種正在運行的內核描述文件。
  • /sys/module:包含當前系統中被加載的模塊信息。
  • /sys/power:官方暫時沒有描述,但是根據里面文件內容和命名習慣推測,這里存放的是一些與電源管理相關的模塊信息。

如果你手頭上有設備的話,博主強烈建議動手操作一遍看看,這樣才能加深理解和記憶。

如果在、sys中添加描述文件

既然是承載用戶與內核接口的虛擬文件系統,那肯定是要能被用戶所使用的,那么我們應該怎樣在/sys中添加描述文件呢?

首先,在上文中提到了,sysfs負責向用戶展示驅動在內核中的信息,那么,肯定是要從內核出發,在內核中進行創建。

kobject kset

Linux設備模型的核心是使用Bus、Class、Device、Driver四個核心數據結構,將大量的、不同功能的硬件設備(以及驅動該硬件設備的方法),以樹狀結構的形式,進行歸納、抽象,從而方便Kernel的統一管理。

而硬件設備的數量、種類是非常多的,這就決定了Kernel中將會有大量的有關設備模型的數據結構。
這些數據結構一定有一些共同的功能,需要抽象出來統一實現,否則就會不可避免的產生冗余代碼。這就是Kobject誕生的背景。

目前為止,Kobject主要提供如下功能:

  • 通過parent指針,可以將所有Kobject以層次結構的形式組合起來。

  • 使用一個引用計數(reference count),來記錄Kobject被引用的次數,並在引用次數變為0時把它釋放(這是Kobject誕生時的唯一功能)。

  • 和sysfs虛擬文件系統配合,將每一個Kobject及其特性,以文件的形式,開放到用戶空間(有關sysfs,會在其它文章中專門描述,本文不會涉及太多內容)。

    注1:在Linux中,Kobject幾乎不會單獨存在。它的主要功能,就是內嵌在一個大型的數據結構中,為這個數據結構提供一些底層的功能實現。
    注2:Linux driver開發者,很少會直接使用Kobject以及它提供的接口,而是使用構建在Kobject之上的設備模型接口。

至於kset,其實可以看成是kobject的集合,它也可以當成kobject來使用,下面來看看這兩個結構體的內容:

struct kset {
    /*鏈表,記錄所有連入這個kset的kobject*/
	struct list_head list;
    /*kset要在文件系統中生成一個目錄,同樣需要包含一個kobj結構體,以插入內核樹中*/
	struct kobject kobj;
	...
} __randomize_layout;
struct kobject {
	const char		*name;
    /*當前kobj的父節點,在文件系統中的表現就是父目錄*/
	struct kobject		*parent;
    /*kobj屬於的kset*/
	struct kset		*kset;
    /*kobj的類型描述,最主要的是其中的屬性描述,包含其讀寫方式*/
	struct kobj_type	*ktype;
    /*當前kobj的引用,只有當引用為0時才能被刪除*/
	struct kref		kref;
    ...
};

雖然linux基於C語言開發,但是其面向對象的思想無處不在,同時我們可以將kobject結構體看成是一個基類,提供基礎的功能,而其他更為復雜的結構繼承自這個結構體,延伸出不同的屬性。

創建實例

介紹完kobject和kset的概念,當然是給出一個具體的實例來說明kobject和kset的使用:
kobject_create_test.c:

#include <linux/init.h>             
#include <linux/module.h>          
#include <linux/kernel.h>   
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/slab.h>

//指定license版本
MODULE_LICENSE("GPL");              

static struct kobject *kob;
static struct kset *kst;

//設置初始化入口函數
static int __init hello_world_init(void)
{

    int ret = 0;
	kst = kset_create_and_add("test_kset",NULL,kernel_kobj->parent);
	if(!kst)
	{
		printk(KERN_ALERT "Create kset failed\n");
		kset_put(kst);
	}
    kob = kzalloc(sizeof(*kob),GFP_KERNEL);
    if(IS_ERR(kob)){
        printk(KERN_ALERT "alloc failed!!\n");
        return -ENOMEM;
    }
	
    ret = kobject_init_and_add(kob, NULL, NULL, "%s", "test_obj");
    if(ret)
    {
        kobject_put(kob);
		kset_unregister(kst);
    }

	printk(KERN_DEBUG "kobj test project!!!\n");
	return 0;
}

//設置出口函數
static void __exit hello_world_exit(void)
{
	kobject_put(kob);
	kset_unregister(kst);
	printk(KERN_DEBUG "goodbye !!!\n");
}

//將上述定義的init()和exit()函數定義為模塊入口/出口函數
module_init(hello_world_init);
module_exit(hello_world_exit);

在上文代碼中我們創建了一個kset對象和一個kobject對象:

  • kset名為"test_kset",父節點為kernel_kobj->parent,這個kernel_kobj事實上就是/sys/kernel節點,這里相當於在/sys目錄下創建一個test_kset目錄。
  • kobject名為"test_obj",沒有指定父節點,默認父節點為/sys.

編譯加載運行

修改Makefile,然后編譯kobject_create_test.c:

make

加載模塊到內核:

sudo insmod kobject_create_test.ko

查看結果

我們可以使用下面的指令查看:

ls -l /sys/test*

輸出:

/sys/test_kset
total 0

/sys/test_obj:
total 0

果然,在/sys目錄下生成了相應目錄。

添加屬性

事實上嚴格來說,上面的示例是有問題的:

  • 首先,這兩個文件僅僅是存在在那里,任何作用也起不了
  • 如果你有同時查看log信息,會發現,上面的示例在加載時內核會報錯:
    Dec 23 08:44:28 beaglebone kernel: [21705.791009] kobject (daa8d880): must have a ktype to be initialized properly!
    

報錯信息可以看到,對於kobject而言,必須對kobject添加相應的操作屬性。

ktype

既然需要添加相應操作屬性,那我們就再來詳細看看kobject結構體的源碼(為避免陷入一些不必要的細節,博主只列出主干部分,有興趣的朋友可以自行查看源碼):

struct kobject {
    ...
	struct kobj_type	*ktype;
    ...
};

先從kobject中找到kobj_type,這是描述kobject屬性的結構體

struct kobj_type {
	void (*release)(struct kobject *kobj);
	const struct sysfs_ops *sysfs_ops;
	struct attribute **default_attrs;
	...
};

在kobj_type結構體中:

  • release函數在當前kobject的引用計數為0時,釋放當前kobject的資源。
  • sysfs_ops:對文件的操作函數
  • default_attrs:表示當前object的屬性

我們再來看看sysfs_ops,這是對應文件的操作函數:

struct sysfs_ops {
	ssize_t	(*show)(struct kobject *, struct attribute *, char *);                 //當我們對/sys下目標文件進行讀操作時,調用show函數
	ssize_t	(*store)(struct kobject *, struct attribute *, const char *, size_t);  //當我們對/sys下目標文件進行寫操作時,調用store函數
};

default_attrs描述了當前kobject的屬性:

struct attribute {
	const char		*name;           //作為當前kobject目錄下的文件名
	umode_t			mode;            //文件操作權限
}

必須來個小結

不知道上面的結構體關系有沒有把你繞暈,我們按照主干線再來總結一下:

  • kobject和kset將會在相應的/sys目錄下創建一個目錄,父目錄由參數parent指定,本目錄名由參數name指定。
  • 每個kobject需要填充kobj_type結構體,這個結構體指定本目錄的相關操作信息,也可以使用默認值。
  • kobj_type結構體主要包含三個部分:
    • release主要負責當前kobject的釋放
    • sysfs_ops的內容為兩個函數指針,store對應用戶對文件寫操作的回調函數,show對應用戶讀文件的回調函數,這兩個函數一般有開發者來決定執行什么操作,這個接口實現了用戶與內核數據的交互。
    • attribute描述kobject的屬性,它有兩個元素,name和mode,分別表示kobject目錄下的文件名和文件操作權限,定義為二級指針,在使用時傳入的是指針數組。

示例

光說不練假把式,我們來看看下面的示例kobject_create_with_attrs:

#include <linux/init.h>             
#include <linux/module.h>           
#include <linux/kernel.h>
#include <linux/kthread.h>      
#include <linux/delay.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/gpio.h>

MODULE_LICENSE("GPL");              
MODULE_AUTHOR("Downey");      
MODULE_DESCRIPTION("Kobject test!");  
MODULE_VERSION("0.1");              

static int led_status = 0;
#define LED_PIN   26
/*************************kobject***************************/
static struct kobject *kob;

static ssize_t led_show(struct kobject* kobjs,struct kobj_attribute *attr,char *buf)
{
    printk(KERN_INFO "Read led\n");
    return sprintf(buf,"The led_status status = %d\n",led_status);
}

static ssize_t led_status_show(struct kobject* kobjs,struct kobj_attribute *attr,char *buf)
{
    printk(KERN_INFO "led status show\n");
    return sprintf(buf,"led status : \n%d\n",led_status);
}


static ssize_t led_status_store(struct kobject *kobj, struct kobj_attribute *attr,const char *buf, size_t count)
{
    printk(KERN_INFO "led status store\n");
    if(0 == memcmp(buf,"on",2))
    {
        gpio_set_value(LED_PIN,1);
        led_status = 1;
    }
    else if(0 == memcmp(buf,"off",3))
    {
        gpio_set_value(LED_PIN,0);
        led_status = 0;
    }
    else
    {
        printk(KERN_INFO "Not support cmd\n");
    }
    
    return count;
}


static struct kobj_attribute status_attr = __ATTR_RO(led);
static struct kobj_attribute led_attr = __ATTR(led_status,0660,led_status_show,led_status_store);  //Doesn't support 0666 in new version.


static struct attribute *led_attrs[] = {
    &status_attr.attr,
    &led_attr.attr,
    NULL,
};

static struct attribute_group attr_g = {
    .name = "kobject_test",
    .attrs = led_attrs,
};


int create_kobject(void)
{
    kob = kobject_create_and_add("obj_test",kernel_kobj->parent);
    return 0;
}

static void gpio_config(void)
{
    if(!gpio_is_valid(LED_PIN)){
        printk(KERN_ALERT "Error wrong gpio number\n");
        return ;
    }
    gpio_request(LED_PIN,"led_ctr");
    gpio_direction_output(LED_PIN,1);
    gpio_set_value(LED_PIN,1);
    led_status = 1;
}

static void gpio_deconfig(void)
{
    gpio_free(LED_PIN);
}

static int __init sysfs_ctrl_init(void){
    printk(KERN_INFO "Kobject test!\n");
    gpio_config();
    create_kobject();
    sysfs_create_group(kob, &attr_g);
    return 0;
}
 

static void __exit sysfs_ctrl_exit(void){

    gpio_deconfig();
    kobject_put(kob);
    printk(KERN_INFO "Goodbye!\n");
}
 

module_init(sysfs_ctrl_init);
module_exit(sysfs_ctrl_exit);

在上述的示例中,我們依舊引入了一個指示燈,值得注意的是,在示例中,博主並沒有將led_attrs傳入給kobject本身,而是使用sysfs_create_group()接口創建了一個目錄,目錄下的文件有led和led_status.

編譯加載運行

修改Makefile,然后使用make進行編譯。

加載相應內核模塊:

sudo insmod kobject_create_with_attrs.ko

加載完成之后如果你有在gpio26連上指示燈,可以看到指示燈現在處於亮的狀態,同時我們可以用指令查看是否在/sys目錄下生成了相應的目錄:

ls -l /sys/obj_test/kobject_test/

輸出結果:

-r--r--r-- 1 root root 4096 Dec 25 14:45 led
-rw-rw---- 1 root root 4096 Dec 25 14:52 led_status

根據程序中的實現,led顯示的內容是led的狀態,同時我們可以通過向led_status文件來控制led燈的狀態。

我們先查看led文件:

cat /sys/obj_test/kobject_test/led

輸出:

The led_status status = 1

如我們所料,對led的讀調用了led_show()函數,我們再來試試led_status文件,在這之前,我們先要賦予文件操作權限:

chmod 666 /sys/obj_test/kobject_test/led_status

然后往led_status文件中寫off來關閉led:

echo "off" > /sys/obj_test/kobject_test/led_status

果然,led被關閉,此時我們再查看led文件發現led狀態為0。

相信到這里,大家對kobject、kset和sysfs有了一個基本的理解,博主在這里再貼上一些kobject的注意事項:

  • 上文說到kobject常常不會單獨存在,而是作為一部分嵌入到其他對象中,一個對象struct只能包含一個kobject,不然會導致混亂
  • kobject不能在棧上分配,也不推薦將其作為靜態存儲,最好的是在堆上申請資源,原因可以自己想想。
  • 釋放kobject時不要使用kfree,要使用kobject_put()函數釋放
  • 不要在release函數中對kobject改名,會造成內存泄漏。

關於kobject和kset更詳細的部分歡迎大家訪問官方文檔,這里有更詳細的資料。
同時建議大家多多嘗試,這樣才能有更深地理解。

kobject描述部分參考大牛的博客 (博主目前看過最好的講解linux內核的系列博客,強烈推薦!)

好了,關於linux驅動程序-sys_fs用戶接口使用就到此為止啦,如果朋友們對於這個有什么疑問或者發現有文章中有什么錯誤,歡迎留言

原創博客,轉載請注明出處!

祝各位早日實現項目叢中過,bug不沾身.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM