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

linux sysfs文件系統

本文部份內容參考自官方文檔html

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

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

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

因此,內核開發人員以爲有必要使用一個獨立的抽象接口來描述設備和驅動信息,畢竟直到目前,驅動代碼在內核代碼中佔比很是大,內容也是很是龐雜。這樣能夠避免procfs的混亂,子系統之間的分層和分離老是能帶來更清晰地框架。數據結構

sysfs的默認目錄結構

上文中提到,sysfs通常被掛載在/sys目錄下,咱們能夠經過ls /sys來查看sysfs的內容:框架

block  bus  class  dev  devices  firmware  fs  kernel  module  power

首先須要注意的是,sysfs目錄下的各個子目錄中存放的設備信息並不是獨立的,咱們能夠當作不一樣的目錄是從不一樣的角度來描述某個設備信息。dom

一個設備可能同時有多個屬性,因此對於同一個驅動設備,同時存在於不一樣的子目錄下,例如:在以前的章節中,咱們使用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各目錄的功能:.net

  • /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不沾身.

相關文章
相關標籤/搜索