sysfs - 用於匯出核心物件的_The_檔案系統

Patrick Mochel <mochel@osdl.org>

Mike Murphy <mamurph@cs.clemson.edu>

修訂:

2011 年 8 月 16 日

原始:

2003 年 1 月 10 日

它是什麼

sysfs 是一個基於 RAM 的檔案系統,最初基於 ramfs。它提供了一種將核心資料結構、它們的屬性以及它們之間的連結匯出到使用者空間的方法。

sysfs 本質上與 kobject 基礎設施相關聯。請閱讀 您一直不想知道的關於 kobject、kset 和 ktype 的一切 以獲取有關 kobject 介面的更多資訊。

使用 sysfs

如果定義了 CONFIG_SYSFS,則始終編譯 sysfs。您可以透過執行以下操作來訪問它

mount -t sysfs sysfs /sys

目錄建立

對於每個向系統註冊的 kobject,都會在 sysfs 中為其建立一個目錄。該目錄是作為 kobject 父目錄的子目錄建立的,從而向用戶空間表達了內部物件層次結構。sysfs 中的頂級目錄表示物件層次結構的公共祖先;即物件所屬的子系統。

sysfs 在內部儲存一個指向 kobject 的指標,該 kobject 在與目錄關聯的 kernfs_node 物件中實現了目錄。過去,每當檔案開啟或關閉時,sysfs 都使用此 kobject 指標直接對 kobject 進行引用計數。使用當前的 sysfs 實現,kobject 引用計數僅由函式 sysfs_schedule_callback() 直接修改。

屬性

屬性可以以檔案系統中常規檔案的形式匯出為 kobject。sysfs 將檔案 I/O 操作轉發到為屬性定義的方法,從而提供了一種讀取和寫入核心屬性的方法。

屬性應該是 ASCII 文字檔案,最好每個檔案只有一個值。需要注意的是,每個檔案只包含一個值可能效率不高,因此表達相同型別的陣列是可以接受的。

混合型別、表達多行資料以及對資料進行花哨的格式化會受到嚴重譴責。做這些事情可能會讓你公開受辱,並且你的程式碼會在不通知的情況下被重寫。

屬性定義很簡單

struct attribute {
        char                    *name;
        struct module           *owner;
        umode_t                 mode;
};


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

裸屬性不包含讀取或寫入屬性值的方法。鼓勵子系統定義自己的屬性結構和包裝函式,以便為特定物件型別新增和刪除屬性。

例如,驅動程式模型定義 struct device_attribute

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);
};

int device_create_file(struct device *, const struct device_attribute *);
void device_remove_file(struct device *, const struct device_attribute *);

它還定義了這個用於定義裝置屬性的助手

#define DEVICE_ATTR(_name, _mode, _show, _store) \
struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)

例如,宣告

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

相當於做

static struct device_attribute dev_attr_foo = {
        .attr = {
                .name = "foo",
                .mode = S_IWUSR | S_IRUGO,
        },
        .show = show_foo,
        .store = store_foo,
};

請注意,正如 include/linux/kernel.h 中所述的“OTHER_WRITABLE?通常被認為是一個壞主意。”,因此嘗試將 sysfs 檔案設定為每個人都可寫將失敗,從而恢復為“其他”的 RO 模式。

對於常見情況,sysfs.h 提供了方便的宏,使定義屬性更容易,並使程式碼更簡潔易讀。以上情況可以縮短為

static struct device_attribute dev_attr_foo = __ATTR_RW(foo);

可用於定義包裝函式的助手列表為

__ATTR_RO(name)

假定預設的 name_show 和 mode 0444

__ATTR_WO(name)

僅假定 name_store 並且僅限於 mode 0200,即僅 root 寫入訪問。

__ATTR_RO_MODE(name, mode)

用於更嚴格的 RO 訪問;目前唯一的用例是 EFI 系統資源表(參見 drivers/firmware/efi/esrt.c)

__ATTR_RW(name)

假定預設的 name_show、name_store 並將模式設定為 0644。

__ATTR_NULL

這將名稱設定為 NULL,並用作列表結束指示符(參見:kernel/workqueue.c)

子系統特定回撥

當子系統定義新的屬性型別時,它必須實現一組 sysfs 操作,以便將讀取和寫入呼叫轉發到屬性所有者的 show 和 store 方法

struct sysfs_ops {
        ssize_t (*show)(struct kobject *, struct attribute *, char *);
        ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
};

[ 子系統應該已經定義了一個 struct kobj_type 作為此型別的描述符,sysfs_ops 指標儲存在該型別中。有關更多資訊,請參見 kobject 文件。]

當讀取或寫入檔案時,sysfs 會呼叫該型別的適當方法。然後,該方法將通用的 struct kobject 和 struct attribute 指標轉換為適當的指標型別,並呼叫關聯的方法。

為了說明

#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 = kobj_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) {
                printk("dev_attr_show: %pS returned bad count\n",
                                dev_attr->show);
        }
        return ret;
}

讀取/寫入屬性資料

要讀取或寫入屬性,在宣告屬性時必須指定 show() 或 store() 方法。方法型別應與為裝置屬性定義的那些方法一樣簡單

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);

也就是說,它們應該只接受一個物件、一個屬性和一個緩衝區作為引數。

sysfs 分配一個大小為 (PAGE_SIZE) 的緩衝區並將其傳遞給該方法。sysfs 將對每次讀取或寫入呼叫該方法一次。這強制對方法實現執行以下行為

  • 在 read(2) 上,show() 方法應填充整個緩衝區。回想一下,屬性應該只匯出一個值,或一個相似值的陣列,因此這不應該太昂貴。

    這允許使用者空間執行部分讀取並在整個檔案上隨意轉發查詢。如果使用者空間查找回零或使用偏移量“0”執行 pread(2),則將再次呼叫 show() 方法,重新準備,以填充緩衝區。

  • 在 write(2) 上,sysfs 希望在第一次寫入期間傳遞整個緩衝區。然後,sysfs 將整個緩衝區傳遞給 store() 方法。在儲存時,資料後會新增一個終止空字元。這使得像 sysfs_streq() 這樣的函式可以安全使用。

    在寫入 sysfs 檔案時,使用者空間程序應首先讀取整個檔案,修改它希望更改的值,然後將整個緩衝區寫回。

    屬性方法實現應在讀取和寫入值時操作相同的緩衝區。

其他說明

  • 無論當前檔案位置如何,寫入都會重新準備 show() 方法。

  • 緩衝區的長度始終為 PAGE_SIZE 位元組。在 x86 上,這是 4096。

  • show() 方法應返回列印到緩衝區中的位元組數。

  • show() 應該只使用 sysfs_emit()sysfs_emit_at() 來格式化要返回給使用者空間的值。

  • store() 應返回緩衝區中使用的位元組數。如果已使用整個緩衝區,則只需返回計數引數。

  • show() 或 store() 始終可以返回錯誤。如果出現錯誤值,請務必返回錯誤。

  • 透過 sysfs 引用計數其嵌入物件,傳遞給方法的物件將被固定在記憶體中。但是,該物件表示的物理實體(例如裝置)可能不存在。如有必要,請務必有一種方法來檢查這一點。

裝置屬性的一個非常簡單(且幼稚)的實現是

static ssize_t show_name(struct device *dev, struct device_attribute *attr,
                        char *buf)
{
        return sysfs_emit(buf, "%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 目錄排列公開了核心資料結構的關係。

頂層 sysfs 目錄如下所示

block/
bus/
class/
dev/
devices/
firmware/
fs/
hypervisor/
kernel/
module/
net/
power/

devices/ 包含裝置樹的檔案系統表示形式。它直接對映到內部核心裝置樹,該樹是 struct device 的層次結構。

bus/ 包含核心中各種匯流排型別的平面目錄佈局。每個匯流排的目錄包含兩個子目錄

devices/
drivers/

devices/ 包含系統中發現的每個裝置的符號連結,這些連結指向根目錄下的裝置目錄。

drivers/ 包含為該特定總線上的裝置載入的每個裝置驅動程式的目錄(這假定驅動程式不跨越多種匯流排型別)。

fs/ 包含一些檔案系統的目錄。目前,每個想要匯出屬性的檔案系統都必須在 fs/ 下建立自己的層次結構(有關示例,請參見 ./fuse.rst)。

module/ 包含所有已載入的系統模組(包括內建模組和可載入模組)的引數值和狀態資訊。

dev/ 包含兩個目錄:char/ 和 block/。在這兩個目錄中,都有名為 <major>:<minor> 的符號連結。這些符號連結指向給定裝置的 sysfs 目錄。/sys/dev 提供了一種從 stat(2) 操作的結果中快速查詢裝置 sysfs 介面的方法。

有關驅動程式模型特定功能的更多資訊,請參見 Documentation/driver-api/driver-model/。

TODO:完成此部分。

當前介面

sysfs 中目前存在以下介面層。

裝置 (include/linux/device.h)

結構

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);
};

宣告

DEVICE_ATTR(_name, _mode, _show, _store);

建立/刪除

int device_create_file(struct device *dev, const struct device_attribute * attr);
void device_remove_file(struct device *dev, const struct device_attribute * attr);

匯流排驅動程式 (include/linux/device.h)

結構

struct bus_attribute {
        struct attribute        attr;
        ssize_t (*show)(const struct bus_type *, char * buf);
        ssize_t (*store)(const struct bus_type *, const char * buf, size_t count);
};

宣告

static BUS_ATTR_RW(name);
static BUS_ATTR_RO(name);
static BUS_ATTR_WO(name);

建立/刪除

int bus_create_file(struct bus_type *, struct bus_attribute *);
void bus_remove_file(struct bus_type *, struct bus_attribute *);

裝置驅動程式 (include/linux/device.h)

結構

struct driver_attribute {
        struct attribute        attr;
        ssize_t (*show)(struct device_driver *, char * buf);
        ssize_t (*store)(struct device_driver *, const char * buf,
                        size_t count);
};

宣告

DRIVER_ATTR_RO(_name)
DRIVER_ATTR_RW(_name)

建立/刪除

int driver_create_file(struct device_driver *, const struct driver_attribute *);
void driver_remove_file(struct device_driver *, const struct driver_attribute *);

文件

sysfs 目錄結構和每個目錄中的屬性定義了核心與使用者空間之間的 ABI。對於任何 ABI,重要的是此 ABI 是穩定的並已正確記錄。所有新的 sysfs 屬性必須記錄在 Documentation/ABI 中。另請參見 Documentation/ABI/README 以獲取更多資訊。