1. 程式人生 > >read與sys_read的區別你真的理解了麼?

read與sys_read的區別你真的理解了麼?

sysfs是一個基於記憶體的檔案系統,它的作用是將核心資訊以檔案的方式提供給使用者程式使用。該檔案系統的目錄層次結構嚴格按照核心的資料結構組織。除了二進位制檔案外(只有特殊場合才使用),sysfs檔案內容均以ASCII格式儲存,且一個檔案只儲存一個數據,另外,一個檔案不可大於一個記憶體頁(通常為4096位元組)。

sysfs提供一種機制,使得可以顯式的描述核心物件、物件屬性及物件間關係。sysfs有兩組介面,一組針對核心,用於將裝置對映到檔案系統中,另一組針對使用者程式,用於讀取或操作這些裝置。表2描述了核心中的sysfs要素及其在使用者空間的表現:

sysfs在核心中的組成要素

在使用者空間的顯示

核心物件(kobject)

目錄

物件屬性(attribute)

檔案

物件關係(relationship)

連結(Symbolic Link)

表2:sysfs內部結構與外部表現

在Ubuntu或Fedora等Linux系統中,我們可以用ls –F <路徑>命令來通過檔案字尾檢視檔案型別。“/”表示資料夾,“@”表示連結,沒有後綴就是檔案了。

sysfs目錄結構

/sys 下的子目錄

所包含的內容

/sys/devices

這是核心對系統中所有裝置的分層次表達模型,也是/sys檔案系統管理裝置的最重要的目錄結構;

/sys/dev

這個目錄下維護一個按字元裝置和塊裝置的主次號碼(major:minor)

連結到真實的裝置(/sys/devices下)的符號連結檔案;

/sys/bus

這是核心裝置按匯流排型別分層放置的目錄結構, devices 中的所有裝置都是連線於某種匯流排之下,在這裡的每一種具體匯流排之下可以找到每一個具體裝置的符號連結,它也是構成 Linux 統一裝置模型的一部分;

/sys/class

這是按照裝置功能分類的裝置模型,如系統所有輸入裝置都會出現在/sys/class/input 之下,而不論它們是以何種匯流排連線到系統。它也是構成 Linux 統一裝置模型的一部分;

/sys/kernel

這裡是核心所有可調整引數的位置,目前只有 uevent_helper, kexec_loaded, mm, 

和新式的slab 分配器等幾項較新的設計在使用它,其它核心可調整引數仍然位於sysctl(/proc/sys/kernel) 介面中;

/sys/module

這裡有系統中所有模組的資訊,不論這些模組是以內聯(inlined)方式編譯到核心映像檔案(vmlinuz)中還是編譯為外部模組(ko檔案),都可能會出現在/sys/module 中:

  • 編譯為外部模組(ko檔案)在載入後會出現對應的/sys/module/<module_name>/,並且在這個目錄下會出現一些屬性檔案和屬性目錄來表示此外部模組的一些資訊,如版本號、載入狀態、所提供的驅動程式等;
  • 編譯為內聯方式的模組則只在當它有非0屬性的模組引數時會出現對應的/sys/module/<module_name>,這些模組的可用引數會出現在/sys/modules/<modname>/parameters/<param_name> 中,
    • 如/sys/module/printk/parameters/time這個可讀寫引數控制著內聯模組printk在列印核心訊息時是否加上時間字首;
    • 所有內聯模組的引數也可以由"<module_name>.<param_name>=<value>"的形式寫在核心啟動引數上,如啟動核心時加上引數"printk.time=1"與向"/sys/module/printk/parameters/time"寫入1的效果相同;
  • 沒有非0屬性引數的內聯模組不會出現於此。

/sys/power

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

表3:sysfs目錄結構

sysfs_dirent是組成sysfs單元的基本資料結構,它是sysfs資料夾或檔案在記憶體中的代表。sysfs_dirent只表示檔案型別(資料夾/普通檔案/二進位制檔案/連結檔案)及層級關係,其它資訊都儲存在對應的inode中。我們建立或刪除一個sysfs檔案或資料夾事實上只是對以sysfs_dirent為節點的樹的節點的新增或刪除。sysfs_dirent資料結構如下:

struct sysfs_dirent {

       atomic_t                s_count;

       atomic_t                s_active;

       struct sysfs_dirent  *s_parent;  /* 指向父節點 */

struct sysfs_dirent  *s_sibling;  /* 指向兄弟節點,兄弟節點是按照inode索引s_ino的大小順序連結在一起的。*/

       const char             *s_name;    /* 節點名稱 */

       union {

              struct sysfs_elem_dir            s_dir;      /* 資料夾,s_dir->kobj指向sysfs物件 */

              struct sysfs_elem_symlink    s_symlink;  /* 連結 */

              struct sysfs_elem_attr           s_attr;     /* 普通檔案 */

              struct sysfs_elem_bin_attr     s_bin_attr;  /* 二進位制檔案 */

       };

       unsigned int           s_flags;

ino_t          s_ino;     /* inode索引,建立節點時被動態申請,通過此值和sysfs_dirent地址可以到inode散列表中獲取inode結構 */

       umode_t                s_mode;

       struct iattr             *s_iattr;

};

inode(index node)中儲存了裝置的主從裝置號、一組檔案操作函式和一組inode操作函式。檔案操作比較常見:open、read、write等。inode操作在sysfs檔案系統中只針對資料夾實現了兩個函式一個是目錄下查詢inode函式(.lookup=sysfs_lookup),該函式在找不到inode時會建立一個,並用sysfs_init_inode為其賦值;另一個是設定inode屬性函式(.setattr=sysfs_setattr),該函式用於修改使用者的許可權等。inode結構如下:

struct inode {

       struct hlist_node     i_hash;    /* 散列表鏈節 */

       struct list_head       i_list;

       struct list_head       i_sb_list;

       struct list_head       i_dentry;  /* dentry鏈節 */

       unsigned long         i_ino;   /* inode索引 */

       atomic_t             i_count;

       unsigned int           i_nlink;

       uid_t                     i_uid;

       gid_t                     i_gid;

       dev_t                    i_rdev; /* 主從裝置號 */

const struct inode_operations *i_op; /* 一組inode操作函式,可用其中lookup查詢目錄下的inode,對應sysfs為sysfs_lookup函式 */

const struct file_operations  *i_fop;    /* 一組檔案操作函式,對於sysfs為sysfs的open/read/write等函式 */

       struct super_block  *i_sb;

       struct list_head       i_devices;

       union {

              struct pipe_inode_info    *i_pipe;

              struct block_device       *i_bdev;

              struct cdev            *i_cdev;

       };

};

dentry(directory entry)的中文名稱是目錄項,是Linux檔案系統中某個索引節點(inode)的連結。這個索引節點可以是檔案,也可以是目錄。引入dentry的目的是加快檔案的訪問。dentry資料結構如下:

struct dentry {

       atomic_t d_count;         /* 目錄項物件使用的計數器 */

       unsigned int d_flags;             /* 目錄項標誌 */

       spinlock_t d_lock;              /* 目錄項自旋鎖 */

       int d_mounted;                     /* 對於安裝點而言,表示被安裝檔案系統根項 */

       struct inode *d_inode;           /* 檔案索引節點(inode) */

       /*

        * The next three fields are touched by __d_lookup.  Place them here

        * so they all fit in a cache line.

        */

       struct hlist_node d_hash;       /* lookup hash list */

       struct dentry *d_parent; /* parent directory */

       struct qstr d_name;              /* 檔名 */

       /*

        * d_child and d_rcu can share memory

        */

       union {

              struct list_head d_child; /* child of parent list */

             struct rcu_head d_rcu;

       } d_u;

       void *d_fsdata;                    /* 與檔案系統相關的資料,在sysfs中指向sysfs_dirent */

       unsigned char d_iname[DNAME_INLINE_LEN_MIN];      /* 存放短檔名 */

};

sysfs_dirent、inode、dentry三者關係:

圖3-1:sysfs在記憶體中的形態

如上圖sysfs超級塊sysfs_sb、dentry根目錄root、sysfs_direct根目錄sysfs_root都是在sysfs初始化時建立。

sysfs_root下的子節點是新增裝置物件或物件屬性時呼叫sysfs_create_dir/ sysfs_create_file建立的,同時會申請對應的inode的索引號s_ino。注意此時並未建立inode。

inode是在用到的時候呼叫sysfs_get_inode函式建立並依據sysfs_sb地址和申請到的s_ino索引計算散列表位置放入其中。

dentry的子節點也是需要用的時候才會建立。比如open檔案時,會呼叫path_walk根據路徑一層層的查詢指定dentry,如果找不到,則建立一個,並呼叫父dentry的inode的lookup函式(sysfs檔案系統的為sysfs_lookup)查詢對應的子inode填充指定的dentry。

這裡有必要介紹一下sysfs_lookup的實現,以保證我們更加清晰地瞭解這個過程,函式主體如下:

static struct dentry * sysfs_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd)

{

       struct dentry *ret = NULL;

       struct sysfs_dirent *parent_sd = dentry->d_parent->d_fsdata; //獲取父sysfs_direct

       struct sysfs_dirent *sd;

       struct inode *inode;

       mutex_lock(&sysfs_mutex);

       /* 在父sysfs_direct查詢名為dentry->d_name.name的節點 */

       sd = sysfs_find_dirent(parent_sd, dentry->d_name.name);

       /* no such entry */

       if (!sd) {

              ret = ERR_PTR(-ENOENT);

              goto out_unlock;

       }

       /* 這兒就是通過sysfs_direct獲取對應的inode,sysfs_get_inode實現原理上面已經介紹過了 */

       /* attach dentry and inode */

       inode = sysfs_get_inode(sd);

       if (!inode) {

              ret = ERR_PTR(-ENOMEM);

              goto out_unlock;

       }

       /* 填充目錄項,至此一個目錄項建立完畢 */

       /* instantiate and hash dentry */

dentry->d_op = &sysfs_dentry_ops;   /* 填充目錄項的操作方法,該方法只提供一釋放inode函式sysfs_d_iput */

       dentry->d_fsdata = sysfs_get(sd);        //填充sysfs_direct

       d_instantiate(dentry, inode);                 //填充inode

       d_rehash(dentry);                               //將dentry加入hash表

 out_unlock:

       mutex_unlock(&sysfs_mutex);

       return ret;

}

sysfs檔案open流程

open的主要過程是通過指定的路徑找到對應的dentry,並從中獲取inode,然後獲取一個空的file結構,將inode中相關內容賦值給file,這其中包括將inode的fop賦給file的fop。因此接下來呼叫的filp->fop->open其實就是inode裡的fop->open。新的file結構對應一個檔案控制代碼fd,這會作為整個open函式的返回值。之後的read/write操作就靠這個fd找到對應的file結構了。

圖3-2是從網上找到的,清晰地描述了file和dentry以及inode之間的關係:

圖3-2:file、dentry、inode關係

程序每開啟一個檔案,就會有一個file結構與之對應。同一個程序可以多次開啟同一個檔案而得到多個不同的file結構,file結構描述了被開啟檔案的屬性,讀寫的偏移指標等等當前資訊。

兩個不同的file結構可以對應同一個dentry結構。程序多次開啟同一個檔案時,對應的只有一個dentry結構。dentry結構儲存目錄項和對應檔案(inode)的資訊。

在儲存介質中,每個檔案對應唯一的inode結點,但是,每個檔案又可以有多個檔名。即可以通過不同的檔名訪問同一個檔案。這裡多個檔名對應一個檔案的關係在資料結構中表示就是dentry和inode的關係。

inode中不儲存檔案的名字,它只儲存節點號;而dentry則儲存有名字和與其對應的節點號,所以就可以通過不同的dentry訪問同一個inode。

sysfs檔案read/write流程

sysfs與普通檔案系統的最大差異是,sysfs不會申請任何記憶體空間來儲存檔案的內容。事實上再不對檔案操作時,檔案是不存在的。只有使用者讀或寫檔案時,sysfs才會申請一頁記憶體(只有一頁),用於儲存將要讀取的檔案資訊。如果作讀操作,sysfs就會呼叫檔案的父物件(資料夾kobject)的屬性處理函式kobject->ktype->sysfs_ops->show,然後通過show函式來呼叫包含該物件的外層裝置(或驅動、匯流排等)的屬性的show函式來獲取硬體裝置的對應屬性值,然後將該值拷貝到使用者空間的buff,這樣就完成了讀操作。寫操作也類似,都要進行核心空間ßà使用者空間記憶體的拷貝,以保護核心程式碼的安全執行。

圖3-1為使用者空間程式讀sysfs檔案的處理流程,其他操作類似:

圖3-3:使用者空間程式讀sysfs檔案的處理流程