Linux設備管理(四)_從sysfs回到ktype

来源:http://www.cnblogs.com/xiaojiang1025/archive/2016/12/21/6202298.html
-Advertisement-
Play Games

sysfs 是一個基於ramfs的文件系統,在2.6內核開始引入,用來導出內核對象(kernel object)的數據、屬性以及到用戶空間。與同樣用於查看內核數據的proc不同,sysfs只關心具有層次結構的設備信息,比如系統中的匯流排,驅動以及已經載入的模塊等,而諸如PID等信息還是使用proc來管 ...


sysfs是一個基於ramfs的文件系統,在2.6內核開始引入,用來導出內核對象(kernel object)的數據、屬性以及到用戶空間。與同樣用於查看內核數據的proc不同,sysfs只關心具有層次結構的設備信息,比如系統中的匯流排,驅動以及已經載入的模塊等,而諸如PID等信息還是使用proc來管理。本質上,sysfs文件的層次結構就是基於內核中kset與kobject邏輯結構來組織的。從驅動開發的角度,sysfs為我們提供了除了虛擬字元設備的read/write/ioctlproc系統之外的另外一種通過用戶空間訪問內核數據的方式。想要使用sysfs,編譯內核的時候需要定義CONFIG_SYSFS,可以通過mount -t sysfs sysfs /sys命令來掛載sysfs到"/sys"目錄。本文以ubuntu15.04(3.19)為例分析。

sysfs目錄結構

sysfs的佈局體現內核的數據結構,頂層的目錄有

$ls /sys/
block/  bus/  class/  dev/  devices/  firmware/  fs/  hypervisor/  kernel/  module/  power/

每一個目錄都對應內核中的一個kset,每一個kset還會包含一些kobject或其他kset。下麵針對常用目錄做一個簡單的介紹

/sys/block/

塊設備的存放目錄,這是一個過時的介面,按照sysfs的設計理念,所有的設備都存放在"sys/devices/"同時在"sys/bus/"或(和)"sys/class/"存放相應的符號鏈接,所以現在這個目錄只是為了提高相容性的設計,裡面的文件已經被全部替換成了符號鏈接,只有在編譯內核的時候勾選CONFIG_SYSFS_DEPRECATED才會有這個目錄,

sys $ll block/
total 0
lrwxrwxrwx  1 root root 0 12月 20 11:29 dm-0 -> ../devices/virtual/block/dm-0/
lrwxrwxrwx  1 root root 0 12月 20 11:29 dm-1 -> ../devices/virtual/block/dm-1/
...

/sys/bus/

bus包含了系統中所有的匯流排,比如我的系統當前提供的匯流排有:

sys $ls bus/
acpi/   container/  i2c/    media/     mipi-dsi/  pci/  pnp/    sdio/   usb/    platform/     scsi/     spi/   ...

每一種匯流排通常還有兩個子目錄:device和driver,這兩個字目錄分別對應內核中的兩個kset,同時bus本身也對應一個kset,也有自己的kobject和以及(可能)有相應的ktype。我們可以查看相應的kset屬性。

sys $ls bus/platform/
devices/  drivers/  drivers_autoprobe  drivers_probe  uevent
sys $cat bus/platform/drivers_autoprobe 
1

我們可以扒一下3.19的源碼,找到這個屬性

//include/linux/platform_device.h
 22 struct platform_device {
            ...
 26         struct device   dev;
            ...
 38 };
//include/linux/device.h
 731 struct device {
             ...
 744         struct bus_type *bus;           /* type of bus device is on */
             ...
 800 };

 104 struct bus_type {
             ...
 129         struct subsys_private *p;
             ...
 131 };
//drivers/base/base.h
 28 struct subsys_private {
 29         struct kset subsys;
 30         struct kset *devices_kset;
            ...
 38         unsigned int drivers_autoprobe:1;       #Bingo!!!
            ...
 43 };  

同時,根據kset的組織形式,平臺匯流排的設備kset鏈接了掛接在平臺匯流排上的所有設備,所以"platform/devices"下應該可以查看到,要註意的事,為了使一個設備在sysfs中只有一個實例,很多目錄都是使用符號鏈接的形式,下麵顯示的結果也驗證了這種設計。

sys $ll bus/platform/devices/
lrwxrwxrwx 1 root root 0 12月 19 08:17 ACPI0003:00 -> ../../../devices/pci0000:00/0000:00:14.3/PNP0C09:00/ACPI0003:00/  ...

sys $ll bus/platform/drivers/thinkpad_acpi/
lrwxrwxrwx  1 root root    0 12月 20 20:19 thinkpad_acpi -> ../../../../devices/platform/thinkpad_acpi/
--w-------  1 root root 4096 12月 20 20:18 uevent
--w-------  1 root root 4096 12月 20 20:19 unbind
-r--r--r--  1 root root 4096 12月 20 20:19 version
...

sys $cat bus/platform/drivers/thinkpad_acpi/version 
ThinkPad ACPI Extras v0.25

/sys/class/

按照設備功能對系統設備進行分類的結果放在這個目錄,如系統所有輸入設備都會出現在 "/sys/class/input"之下。和sys/bus一樣,sys/class最終的文件都是符號鏈接,這種設備可以保證整個系統中每一個設備都只有一個實例。

sys $l class/
ata_device/   i2c-adapter/    net/     rtc/           spi_master/    gpio/      input/   ...

sys $l class/input/
event0@  event10@  event12@   mouse0@   ...

/sys/dev/

按照設備號對字元設備和塊設備進行分類的結果放在這個目錄,同樣,文件依然是使用符號鏈接的形式鏈接到"sys/devices/"中的相應文件

sys $ls dev/
block/  char/

sys $ls dev/char/
10:1@    10:236@  108:0@   1:3@    ...

/sys/devices/

如前所述,所有的設備文件實例都在"sys/devices/"目錄下,

sys $ls devices/
amd_nb/  breakpoint/  cpu/  ibs_fetch/  ibs_op/  LNXSYSTM:00/  pci0000:00/  platform/  ...

sys $ls devices/platform/serial8250/
driver@  driver_override  modalias  power/  subsystem@  tty/  uevent

sys $cat devices/platform/serial8250/driver_override 
(null)

"sys/class/","sys/bus/","sys/devices"是設備開發中最重要的幾個目錄。他們之間的關係可以用下圖表示。

/sys/fs

這裡按照設計是用於描述系統中所有文件系統,包括文件系統本身和按文件系統分類存放的已掛載點,但目前只有 fuse,gfs2 等少數文件系統支持 sysfs 介面,一些傳統的虛擬文件系統(VFS)層次控制參數仍然在 sysctl (/proc/sys/fs) 介面中中;

/sys/kernel

這裡是內核所有可調整參數的位置,目前只有 uevent_helper, kexec_loaded, mm, 和新式的 slab 分配器等幾項較新的設計在使用它,其它內核可調整參數仍然位於 sysctl (/proc/sys/kernel) 介面中 ;

/sys/module

這裡有系統中所有模塊的信息,不論這些模塊是以內聯(inlined)方式編譯到內核映像文件(vmlinuz)中還是編譯為外部模塊(ko文件),都可能會出現在 /sys/module 中:編譯為外部模塊(ko文件)在載入後會出現對應的/sys/module/

/sys/power

這裡是系統中電源選項,這個目錄下有幾個屬性文件可以用於控制整個機器的電源狀態,如可以向其中寫入控制命令讓機器關機、重啟等。

/sys/slab

(對應 2.6.23 內核,在 2.6.24 以後移至/sys/kernel/slab) 從2.6.23 開始可以選擇 SLAB 記憶體分配器的實現,並且新的 SLUB(Unqueued Slab Allocator)被設置為預設值;如果編譯了此選項,在 /sys 下就會出現 /sys/slab ,裡面有每一個 kmem_cache 結構體的可調整參數。對應於舊的 SLAB 記憶體分配器下的/proc/slabinfo 動態調整介面, 新式的 /sys/kernel/slab/

sysfs與kobject、kset

對於每一個註冊到內核的kobject,都會在sysfs中創建一個目錄!!!一個目錄!!!一個目錄!!!,目錄名就是kobject.name,這個目錄會從屬於kobject.parent對應的目錄,我們就可以實現在sysfs中用樹狀結構來呈現內核中的kobject。最初的sysfs下頂層目錄下的目錄使用subsystem的結構,在某些書中還會見到這個概念,不過現在已經被kset替代了。在 kobject 下還有一些符號鏈接文件,指向其它的 kobject,這些符號鏈接文件用於組織上面所說的 device, driver, bus_type, class, module 之間的關係。我們再來看看kobject結構:

//include/linux/kobject.h
 63 struct kobject {      
 64         const char              *name;
 65         struct list_head        entry;
 66         struct kobject          *parent;
 67         struct kset             *kset;
 68         struct kobj_type        *ktype;
 69         struct kernfs_node      *sd;
 70         struct kref             kref;
            ...
 79 };
//include/linux/kernfs.h
106 struct kernfs_node {
            ...
125         union {
126                 struct kernfs_elem_dir          dir;
127                 struct kernfs_elem_symlink      symlink;
128                 struct kernfs_elem_attr         attr;
129         };
            ...
137 };

這其中的symlink就組成了下麵的符號鏈接,許許多多這樣的符號鏈接就構成了整個sysfs的符號鏈接體系

sys $ll devices/platform/serial8250/
lrwxrwxrwx  1 root root    0 12月 20 16:17 driver -> ../../../bus/platform/drivers/serial8250/
-rw-r--r--  1 root root 4096 12月 20 16:17 driver_override
-rw-r--r--  1 root root 4096 12月 20 16:17 uevent
...

sysfs與ktype

在sysfs中,kobject的屬性(kobject.ktype.attribute)可以以普通文件的形式導出,sysfs還提供了使用文件I/O直接修改內核屬性的機制,這些屬性一般都是ASCII格式的文本文件(ktype.attribute.name)或二進位文件(通常只用在sys/firmware中),為了提高效率,可以將具有同一類型的屬性放置在一個文件中,這樣就可以使用數組進行批量修改,不要在一個文件中使用混合類型,也不要使用多行數據,這些做法會大大降低代碼的可讀性,下麵就是一個屬性的定義,可以看到,屬性中並沒有包含讀寫屬性的函數,但是從面向對象的思想看,內核提供了兩個用於讀寫attribute結構的函數。

//include/linux/sysfs.h
 29 struct attribute {
 30     const char      *name;
 31     umode_t         mode;
 32 #ifdef CONFIG_DEBUG_LOCK_ALLOC       
 33     bool            ignore_lockdep:1; 
 34     struct lock_class_key   *key;     
 35     struct lock_class_key   skey;  
 36 #endif
 37 };

int sysfs_create_file(struct kobject * kobj, const struct attribute * attr);
void sysfs_remove_file(struct kobject * kobj, const struct attribute * attr);

由於一個ktype往往包含很多屬性(default_attr是一個二級指針),當用戶通過sysfs讀寫一個kobject的屬性的時候,會自動回調ktype中的sysfs_ops->show()sysfops->remove(),所以一個典型的做法是,當我們創建了一個繼承自kobject的子類child後,同時還會創建兩個調用了sysfs_create_file()sys_remove_file()的讀寫函數,並將它們註冊到struct sysfs_ops中。比如內核使用的struct device就將相應的方法和屬性都封裝在了一起。

//include/linux/device.h
 512 /* interface for exporting device attributes */
 513 struct device_attribute {      
 514         struct attribute        attr;
 515         ssize_t (*show)(struct device *dev, struct device_attribute *attr,
 516                         char *buf);
 517         ssize_t (*store)(struct device *dev, struct device_attribute *attr,
 518                          const char *buf, size_t count);
 519 };

 560 extern int device_create_file(struct device *device,const struct device_attribute *entry);
 562 extern void device_remove_file(struct device *dev,const struct device_attribute *attr);

此外,內核甚至還提供了輔助定義這個屬性的巨集

//include/linux/device.h
 539 #define DEVICE_ATTR(_name, _mode, _show, _store) \ 
 540         struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)

//include/linux/sysfs.h
 75 #define __ATTR(_name, _mode, _show, _store) {                           \         
 76         .attr = {.name = __stringify(_name),                            \
 77                  .mode = VERIFY_OCTAL_PERMISSIONS(_mode) },             \
 78         .show   = _show,                                                \
 79         .store  = _store,                                               \
 80 }

有了這個巨集,我們就可以直接通過這個介面創建我們自己的對象

static DEVICE_ATTR(foo, S_IWUSR | S_IRUGO, show_foo, store_foo);

我們可以追一下源碼,可以發現,我們使用的自動創建設備文件device_create()就會調用device_create_file()並最終調用sysfs_create_file()

"drivers/base/core.c"
device_create()
   └── device_create_vargs()
            └── device_create_groups_vargs()
                        └── device_add()
                                    └── device_create_file()
                                                ├── "include/linux/sysfs.h"
                                                └── sysfs_create_file()

eg_0:

#define to_dev(obj) container_of(obj, struct device, kobj)
#define to_dev_attr(_attr) container_of(_attr, struct device_attribute, attr)

static ssize_t dev_attr_show(struct kobject *kobj, struct attribute *attr,
                             char *buf)
{
        struct device_attribute *dev_attr = to_dev_attr(attr);
        struct device *dev = to_dev(kobj);
        ssize_t ret = -EIO;

        if (dev_attr->show)
                ret = dev_attr->show(dev, dev_attr, buf);
        if (ret >= (ssize_t)PAGE_SIZE) {
                print_symbol("dev_attr_show: %s returned bad count\n",
                                (unsigned long)dev_attr->show);
        }
        return ret;
}

讀寫attribute

當一個子系統定義了一個新的屬性,它必須執行一組針對的sysfs操作以便對實現對屬性的讀寫,這些讀寫操作通過回調ktype.sysfs_ops.show()和store()

//include/linux/sysfs.h
184 struct sysfs_ops {  
185         ssize_t (*show)(struct kobject *, struct attribute *, char *);
186         ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
187 };

當進行讀寫的時候,sysfs會分配一個PAGE_SIZE大小的buf並把它作為參數傳入這兩個函數,同時,對於每一次對屬性的讀寫操作,sysfs都會調用這兩個函數,所以,調用read系統調用的時候,show()方法應該填滿整個buf,註意一個屬性應該是一個或一組相似的值,所以這種機制並不會浪費很多系統資源。這種機制允許用戶讀取一部分內容並且可以任意的移動文件位置指針,如果用戶空間將文件指針置為0或以0為偏移量調用了pread()show()會被重新調用並且再填滿一個buf。類似地,調用write()系統調用的時候,sysfs希望第一次傳入的buf是被填滿的,sysfs會在傳入的數據最後自動加NUL,這可以讓諸如sysfs_strqe()一類的函數用起來更安全。當對sysfs執行寫操作時,用戶空間應該首先讀取整個文件的內容,按自己的需求改變其中的一部分並回寫,屬性讀寫操作應該使用同一個buf

tips:

  1. 通過read()/write()傳遞數據不同,這裡的show()/store()里的buf已經是內核空間的了,不需要進行copy_to_user() etc
  2. 寫操作會導致show方法重新執行而忽視當前文件位置指針的位置
  3. buf是PAGE_SIZE大小
  4. show()方法返回列印到buf的實際byte數,這個就是scnprintf()的返回值
  5. 在進行格式化列印到用戶空間的時候,show必須用scnprintf()除非你能保證棧不會溢出
  6. stor應該返回buf中使用的數據的byte數目
  7. show或store應該設置合適的返回值確保全全

eg_1


static ssize_t show_name(struct device *dev, struct device_attribute *attr,
                         char *buf)
{
    return scnprintf(buf, PAGE_SIZE, "%s\n", dev->name);
}

static ssize_t store_name(struct device *dev, struct device_attribute *attr,
                          const char *buf, size_t count)
{
        snprintf(dev->name, sizeof(dev->name), "%.*s",
                 (int)min(count, sizeof(dev->name) - 1), buf);
    return count;
}

static DEVICE_ATTR(name, S_IRUGO, show_name, store_name);

內核已實現介面

內核中已經使用sysfs實現了很多的讀寫函數,下麵是幾個典型的

設備

/* devices */
/* structure */
//include/linux/device.h)
 512 /* interface for exporting device attributes */
 513 struct device_attribute {
 514         struct attribute        attr;
 515         ssize_t (*show)(struct device *dev, struct device_attribute *attr,
 516                         char *buf);
 517         ssize_t (*store)(struct device *dev, struct device_attribute *attr,
 518                          const char *buf, size_t count);
 519 };

/* Declaring */
 539 #define DEVICE_ATTR(_name, _mode, _show, _store) \  
 540         struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)

/* Creation/Removal */
 560 extern int device_create_file(struct device *device,const struct device_attribute *entry);
 562 extern void device_remove_file(struct device *dev,const struct device_attribute *attr);

匯流排驅動

/* bus drivers */
/* Structure */
//include/linux/device.h
  44 struct bus_attribute {  
  45         struct attribute        attr;
  46         ssize_t (*show)(struct bus_type *bus, char *buf);
  47         ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count);
  48 };

/* Declaring */
  50 #define BUS_ATTR(_name, _mode, _show, _store)   \    
  51         struct bus_attribute bus_attr_##_name = __ATTR(_name, _mode, _show, _store)

/* Creation/Removal */
  57 extern int __must_check bus_create_file(struct bus_type *,struct bus_attribute *);
  59 extern void bus_remove_file(struct bus_type *, struct bus_attribute *);

設備驅動

/* device drivers */
/* Structure */
//include/linux/device.h

 265 struct driver_attribute {    
 266         struct attribute attr;
 267         ssize_t (*show)(struct device_driver *driver, char *buf);
 268         ssize_t (*store)(struct device_driver *driver, const char *buf,
 269                          size_t count);
 270 };

/* Declaring */
 272 #define DRIVER_ATTR(_name, _mode, _show, _store) \  
 273         struct driver_attribute driver_attr_##_name = __ATTR(_name, _mode, _show, _store)

/* Creation/Removal */
 281 extern int __must_check driver_create_file(struct device_driver *driver,   
 282                                         const struct driver_attribute *attr);
 283 extern void driver_remove_file(struct device_driver *driver,
 284                                const struct driver_attribute *attr);

參考文檔


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 概覽屏幕 概覽屏幕 概覽屏幕(也稱為最新動態屏幕、最近任務列表或最近使用的應用)是一個系統級別 UI,其中列出了最近訪問過的 Activity 和任務。 用戶可以瀏覽該列表並選擇要恢復的任務,也可以通過滑動清除任務將其從列表中移除。 對於 Android 5.0 版本(API 級別 21),包含不同 ...
  • 廣度優先搜索 在給定圖G=(V,E)和一個特定的源頂點s的情況下,廣度優先搜索系統地探索G中的邊,以期“發現”可從s 到達的所有頂點,並計算s 到所有這些可達頂點之間的距離(即最少的邊數)。該搜索演算法同時還能生成一棵根為s、且包括所有s 的可達頂點的廣度優先樹。對從s 可達的任意頂點v,廣度優先樹中 ...
  • 一、SQLite保存數據介紹 將資料庫保存在資料庫對於重覆或者結構化數據(比如契約信息)而言是理想之選。SQL資料庫的主要原則之一是架構:資料庫如何組織正式聲明。架構體現於用於創建資料庫的SQL語句。它有助於創建伴隨類,即契約類,其以一種系統性、自記錄的方式明確指定架構佈局。 契約類是用於定義URL ...
  • res/layout中的佈局文件太雜,沒有層次感,受不了的我治好想辦法解決這個問題。 前幾天看博客說可以使用插件分組,可惜我沒找到。知道看到另一篇博客時,才知道這個方法不能用了。 不能用插件,那就手動來吧。(http://blog.csdn.net/u011156012/article/detail ...
  • 使用Android Studio 一、在build.gradle(Module:app)添加代碼 下載,調用插件 1 apply plugin: 'com.android.application' 2 3 android { 4 compileSdkVersion 24 5 buildToolsVe ...
  • 自從Android6.0發佈以來,在許可權上做出了很大的變動,不再是之前的只要在manifest設置就可以任意獲取許可權,而是更加的註重用戶的隱私和體驗,不會再強迫用戶因拒絕不該擁有的許可權而導致的無法安裝的事情,也不會再不征求用戶授權的情況下,就可以任意的訪問用戶隱私,而且即使在授權之後也可以及時的更改 ...
  • 1. 操作系統中的棧和堆 我們先來看看一個由C/C++/OBJC編譯的程式占用記憶體分佈的結構: 棧區(stack):由系統自動分配,一般存放函數參數值、局部變數的值等。由編譯器自動創建與釋放。其操作方式類似於數據結構中的棧,即後進先出、先進後出的原則。 例如:在函數中申明一個局部變數int b;系統 ...
  • 在平攤分析中,執行一系列數據結構操作所需要的時間是通過對執行的所有操作求平均而得出的。 平攤分析可以用來證明在一系列操作中,通過對所有操作求平均之後,即使其中單一的操作具有較大的代價,平均代價還是很小的。平攤分析與平均情況分析的不同之處在於它不牽涉到概率;平攤分析保證在最壞情況下,每個操作具有平均性 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...