2011年3月5日 星期六

在 Linux Kernel 中取得目錄中的檔案清單

Standard
寫一支程式在 User Space 下列出目錄中的檔案清單相當容易,我們可以用 readdir() 去一項項取得檔案內容,事實上, readdir() 是由 Linux Kernel VFS(Virtual Filesystem) 所提供的 API,真正的實作在檔案系統的核心模組(Kernel Module)中。然而因為 Kernel readdir() 的相關實作牽涉到 Kernel/User space 的資料交換問題,所以如果我們是在撰寫 Kernel 的驅動程式,當然就不能使用這系列的 APIs。不過也因為所有的資訊都存在於 Kernel 的資料結構中,我們可以直接從記憶體中的 Linked list 中快速取得檔案清單。

在取得某個目錄下所有檔案資訊前,要先得到該目錄的敘述 struct dentry,VFS 用 struct dentry 的串聯組合來描述整個檔案系統的目錄結構,檔案清單則被保存在 dentry 中的 Double Linked List,因此其實只要知道如何使用 Kernel 提供的 Double Linked list,就能得到該目錄下所有的檔案資訊。而這邊要注意的是 Kernel 提供的一個通用 Linked list 結構(struct list_head),用來統一整個核心的 Linked list 操作機制,這檔案清單的 Double Linked List 就是使用這通用的結構。

這裡是 Linux Kernel 2.6.37 中 dentry 的資料結構(定義在 linux/dcache.h,其中較不重要的部份以『...』做省略,此外,某些定義與較早版本的 Kernel 有所差異,將在文章最後補上說明):
struct dentry {
...省略
    struct hlist_node d_hash;   /* lookup hash list */
    struct dentry *d_parent;    /* parent directory */
    struct qstr d_name;

    struct list_head d_lru;     /* LRU list */
    /*
     * 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;
    struct list_head d_subdirs; /* our children */
    struct list_head d_alias;   /* inode alias list */
...省略
};

由 struct dentry 可以發現,利用讀取 d_u.d_child 這個 Linked List 就可以取得底下所有的檔案資訊。實際做法可以參考範例程式碼,此程式會列出某個目錄下所有的檔案名稱:
struct list_head *next;
    struct dentry *child_dentry;
    list_for_each(next, &file->f_path.dentry->d_u.d_child) {
        child_dentry = (struct dentry *)list_entry(next, struct dentry, d_u.d_child);
        printk("%s\n", child_dentry->d_name.name);
    }

於 dentry 裡保存檔案名稱的資料結構是 struct qst,其定義如下(定義與較早版本的 Kernel 有所差異,將在文章最後補上說明):
struct qstr {
    unsigned int hash;
    unsigned int len;
    const unsigned char *name;
};

後記

更多操作 struct list_head 請參考相關的書籍或網路文件,這部份很重要,因為在 Kernel 處處都會發現 struct list_head 的蹤影。

在較早的 Linux Kernel 版本中 dentry 的 d_child 並未使用 union d_u,是這樣被定義的(網路上找到的版本也多半是這樣):
struct dentry {
...省略
  struct dentry * d_parent;
  struct list_head d_hash;
  struct list_head d_lru;
  struct list_head d_child;
  struct list_head d_subdirs;
...省略
}

而早期的 struct qstr 是這樣定義:
struct qstr {
 const unsigned char * name;
 unsigned int len;
 unsigned int hash;
 char name_str[0];
};

由於 Kernel 版本數量太多,且定義會不斷被改變,若在實際撰寫時請參考 Linux 核心程式原始碼的『 include/linux/dcache.h』。