About seq_file operations

內容簡介

本文主要講述序列文件(seq_file)接口的內核實現,如何使用它將Linux內核裏面常用的數據結構通過文件(主要關注proc文件)導出到用戶空間,最後定義了一些宏以便於編程,減少重複代碼。在分析序列文件接口實現的過程中,還連帶涉及到一些應用陷阱和避免手段。

序列文件接口

UNIX的世界裏,文件是最普通的概念,所以用文件來作爲內核和用戶空間傳遞數據的接口也是再普通不過的事情,並且這樣的接口對於shell也是相當友好的,方便管理員通過shell直接管理系統。由於僞文件系統proc文件系統在處理大數據結構(大於一頁的數據)方面有比較大的侷限性,使得在那種情況下進行編程特別彆扭,很容易導致bug,所以序列文件接口被髮明出來,它提供了更加友好的接口,以方便程序員。之所以選擇序列文件接口這個名字,應該是因爲它主要用來導出一條條的記錄數據。

爲了能給大家一個具體形象的認識,我們首先來看一段用序列文件接口通過proc文件導出內核雙向循環鏈接表的實例代碼:


#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>

static struct mutex lock;
static struct list_head head;
struct my_data {
        struct list_head list;
        int value;
};

static void add_one(void)
{
        struct my_data *data;

        mutex_lock(&lock);
        data = kmalloc(sizeof(*data), GFP_KERNEL);
        if (data != NULL)
                list_add(&data->list, &head);
        mutex_unlock(&lock);
}

static ssize_t _seq_write(struct file *file, const char __user * buffer,
                       size_t count, loff_t *ppos)
{
        add_one();
        return count;
}

static int _seq_show(struct seq_file *m, void *p)
{
        struct my_data *data = list_entry(p, struct my_data, list);


        seq_printf(m, "value: %d\n", data->value);
        return 0;
}

static void *_seq_start(struct seq_file *m, loff_t *pos)
{
        mutex_lock(&lock);
        return seq_list_start(&head, *pos);
}

static void *_seq_next(struct seq_file *m, void *p, loff_t *pos)
{
        return seq_list_next(p, &head, pos);
}

static void _seq_stop(struct seq_file *m, void *p)
{
        mutex_unlock(&lock);
}

static struct seq_operations _seq_ops = {
        .start = _seq_start,
        .next = _seq_next,
        .stop = _seq_stop,
        .show = _seq_show
};

static int _seq_open(struct inode *inode, struct file *file)
{
        return seq_open(file, &_seq_ops);
}

static struct file_operations _seq_fops = {
        .open = _seq_open,
        .read = seq_read,
        .write = _seq_write,
        .llseek = seq_lseek,
        .release = seq_release
};

static void clean_all(struct list_head *head)
{
        struct my_data *data;

        while (!list_empty(head)) {
                data = list_entry(head->next, struct my_data, list);
                list_del(&data->list);
                kfree(data);
        }
}

static int __init init(void)
{
        struct proc_dir_entry *entry;

        mutex_init(&lock);
        INIT_LIST_HEAD(&head);

        add_one();
        add_one();
        add_one();

        entry = create_proc_entry("my_data", S_IWUSR | S_IRUGO, NULL);
        if (entry == NULL) {
                clean_all(&head);
                return -ENOMEM;
        }
        entry->proc_fops = &_seq_fops;

        return 0;
}

static void __exit fini(void)
{
        remove_proc_entry("my_data", NULL);
        clean_all(&head);
}

module_init(init);
module_exit(fini);


注:以上代碼需要內核版本不小於2.6.23,小版本內核可以從2.6.23內核代碼中抽取函數seq_list_start和seq_list_next。

上面的這段程序創建了一個結構體my_data的雙向循環鏈表(head),並且通過my_data proc文件將其導出到用戶空間,用戶空間的程序除了可以通過對my_data的讀操作來獲取my_data鏈表中各個結構體中的value域的值之外,還能通過寫(write)系統調用添加一個具有隨機value值的my_data結構體到雙向循環鏈表中(從鏈表的頭部添加)。

gentux kernel # cat /proc/my_data
value: 474615376
value: 474615632
value: 474615568
gentux kernel # echo 0 > /proc/my_data
gentux kernel # cat /proc/my_data
value: 758528120
value: 474615376
value: 474615632
value: 474615568

雖然,上面的那段代碼行數不少,但是和輸出相關的部分除了函數_seq_show外都是慣例代碼,這種簡便性是很直觀的。相信對於雙向循環鏈表數據結構的導出,讀者們都能夠照貓畫虎寫出自己的代碼而毋須我再多言。

序列文件接口相關的兩個重要數據結構爲seq_file結構體和seq_operations表(表是延續ULK3的叫法,實際上就是隻包含函數指針的結構體)。首先介紹seq_file結構體:
  • buf: 序列文件對應的數據緩衝區,要導出的數據都是首先打印到這個緩衝區,然後才被拷貝到用戶指定的用戶空間緩衝區。
  • size: 緩衝區的大小,默認爲1個內存頁面大小,隨着需求會動態以2的級數倍擴張,比如:4k,8k,16k...
  • from: 沒有拷貝到用戶空間的數據在buf中的起始偏移量。
  • count: buf中沒有被拷貝到用戶空間的數據的字節數。
  • index: 數據項的索引,和稍後提到的seq_operations表中的start, next操作中的pos項一致。
  • version: 文件的版本。
  • lock: 序列化對這個文件的並行操作
  • op:指向稍後提到的seq_operations表。
  • private: 指向文件的私有數據,是特例化一個序列文件的方法。
seq_operations表中的函數指針成員如下:
  • start: 開始讀數據項,通常需要在這個函數裏面加鎖,以防止並行訪問數據。
  • stop: 停止讀數據項,和start相對,通常需要解鎖。
  • next:找到下一個要處理的數據項。
  • show:打印數據項到臨時緩衝區。
其中start,next返回的值都會以第二個參數的形式傳遞給stop和show。start在*pos爲0的時候還可以返回SEQ_START_TOKEN,通常這個值傳遞給show的時候,show會打印表格頭。

一些有用的全局函數:
  • seq_open:通常會在打開文件的時候調用,以第二個參數爲seq_operations表創建seq_file結構體。
  • seq_read, seq_lseek和seq_release:他們通常都直接對應着文件操作表中的read, llseek和release。
  • seq_escape:將一個字符串中的需要轉義的字符(字節長)以8進制的方式打印到seq_file。
  • seq_putc, seq_puts, seq_printf:他們分別和C語言中的putc,puts和printf相對應。
  • seq_path:用於輸出文件名。
  • single_open, single_release: 打開和釋放只有一條記錄的文件。
  • seq_open_private, __seq_open_private, seq_release_private:和seq_open類似,不過打開seq_file的時候創建一小塊文件私有數據。
在2.6.23中,又爲雙向循環鏈表引入了三個幫助函數:
  • seq_list_start:返回鏈表中的特定項。
  • seq_list_start_head:在需要輸出表格頭的時候使用。
  • seq_list_next: 返回鏈表中的下一項。
這三個函數的具體用法可以參考上面的實例,不再贅述。

序列文件接口的內核實現

序列文件接口的實現比較簡單易懂,出於完整性考慮,我姑且把核心函數seq_read拿來和讀者一起解讀:
  1. 如果是第一次對序列文件調用讀操作,那麼內核數據緩衝區指針(buf)爲空,這個時候申請一整頁內存作爲緩衝區。
  2. 如果內核緩衝區中尚且有數據,那麼先用它們填補用戶緩衝區。
  3. 如果用戶緩衝區仍有空間,則按序調用start和show,填補內核緩衝區。如果當前的內核緩衝區不足以容納一條記錄,那麼將緩衝區大小加倍後再次嘗試,直到可以容納至少一條記錄。然後持續調用next和show,直到內核緩衝區無空間容納新的整條記錄後調用stop終止。
可見,序列文件接口是面向整條記錄的,但是它只能保證這些,無法保證導出的數據結構的整體一致性,這一點請多加留意,防止因爲它導致bug。

因爲序列文件的緩衝區有自動擴張的功能,所以它更便於導出大於一頁的數據結構,這也正是single_open/release函數的用武之地。

內核常用數據結構的導出

應用序列文件接口導出常用的面向記錄的數據結構通常是非常簡單的,下面我將給出內核常用的數據結構通過序列文件接口導出的思路。

數組

數組通過其索引就能夠隨機地訪問其中的任一元素,所以我們只要將start和next參數中的pos當成索引,返回對應元素的地址即可。

雙向循環鏈表

  • start:跳過前pos個元素,返回即可。
  • next:增加pos,返回下一個元素。

哈希表

哈希表有兩種導出方法:
  1. 以每個哈希桶爲元素,這個時候哈希表退化爲數組。
  2. 以每個哈希節點爲元素,這個時候它退化爲按桶連接起來的鏈表。
第1個方法,因爲每個桶中的節點數不可控,所以可能需要大於一頁的連續內存,這在一個運行了很長時間,內存充斥着外部碎片的系統上可能是不切實際的。相比之下,第2個方法可能更加有效。

紅黑樹

因爲Linux內核提供了按照類似鏈表的方式遍歷紅黑樹的方法,所以導出它的方法和雙向鏈接表類似。

序列文件接口的幫助宏

實際上,應用序列文件接口進行編程,存在大量的重複代碼,爲了減少可惡的重複代碼,也爲了減少複製粘貼可能引入的bug,我寫了這一套宏以簡化大多數情況下的序列文件接口調用。具體宏代碼如下:


#ifndef _SEQ_FILE_HELPER_H_
#define _SEQ_FILE_HELPER_H_

#include <linux/version.h>
#include <linux/list.h>
#include <linux/rbtree.h>
#include <linux/seq_file.h>

#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 23)
static inline struct list_head *seq_list_start(struct list_head *head,
                loff_t pos)
{
        struct list_head *lh;

        list_for_each(lh, head)
                if (pos-- == 0)
                        return lh;

        return NULL;
}

static inline struct list_head *seq_list_next(void *v, struct list_head *head,
                loff_t *ppos)
{
        struct list_head *lh;

        lh = ((struct list_head *)v)->next;
        ++*ppos;
        return lh == head ? NULL : lh;
}
#endif

/* common macro */
#define __DEFINE_FOO_SEQ_FOPS(prefix, foo, imp_show, imp_write) \
static struct seq_operations _##foo##_seq_ops = { \
        .start = _##foo##_seq_start, \
        .next = _##foo##_seq_next, \
        .stop = _##foo##_seq_stop, \
        .show = imp_show \
}; \
 \
static int _##foo##_seq_open(struct inode *inode, struct file *file) \
{ \
        return seq_open(file, &_##foo##_seq_ops); \
} \
 \
prefix struct file_operations foo##_seq_fops = { \
        .open = _##foo##_seq_open, \
        .read = seq_read, \
        .write = imp_write, \
        .llseek = seq_lseek, \
        .release = seq_release, \

        .owner = THIS_MODULE

};

/* helper for array */
#define _DEFINE_ARRAY_SEQ_FOPS(prefix, arr, n, lock, unlock, imp_show, \
                imp_write) \
static void* _##arr##_seq_start(struct seq_file *m, loff_t *pos) \
{ \
        lock; \
        if (*pos >= n) \
                return NULL; \
        return &arr[*pos]; \
} \
 \
static void* _##arr##_seq_next(struct seq_file *m, void *p, loff_t *pos) \
{ \
        if (++*pos >= n) \
                return NULL; \
        return &arr[*pos]; \
} \
\
static void _##arr##_seq_stop(struct seq_file *m, void *p) \
{ \
        unlock; \
} \
 \
__DEFINE_FOO_SEQ_FOPS(prefix, arr, imp_show, imp_write)

#define DEFINE_ARRAY_SEQ_FOPS(arr, n, lock, unlock, imp_show, imp_write) \
        _DEFINE_ARRAY_SEQ_FOPS(, arr, n, lock, unlock, imp_show, imp_write)

#define DEFINE_STATIC_ARRAY_SEQ_FOPS(arr, n, lock, unlock, imp_show, \
                imp_write) \
        _DEFINE_ARRAY_SEQ_FOPS(static, arr, n, lock, unlock, imp_show, \
                        imp_write)

/* helper for list */
#define _DEFINE_LIST_SEQ_FOPS(prefix, list, lock, unlock, imp_show, imp_write) \
static void* _##list##_seq_start(struct seq_file *m, loff_t *pos) \
{ \
        lock; \
        return seq_list_start(&list, *pos); \
} \
 \
static void* _##list##_seq_next(struct seq_file *m, void *p, loff_t *pos) \
{ \
        return seq_list_next(p, &list, pos); \
} \
\
static void _##list##_seq_stop(struct seq_file *m, void *p) \
{ \
        unlock; \
} \
 \
__DEFINE_FOO_SEQ_FOPS(prefix, list, imp_show, imp_write)

#define DEFINE_LIST_SEQ_FOPS(list, lock, unlock, imp_show, imp_write) \
        _DEFINE_LIST_SEQ_FOPS(, list, lock, unlock, imp_show, imp_write)

#define DEFINE_STATIC_LIST_SEQ_FOPS(list, lock, unlock, imp_show, imp_write) \
        _DEFINE_LIST_SEQ_FOPS(static, list, lock, unlock, imp_show, imp_write)

/* helper for hlist */
#ifndef hlist_for_each_continue
#define hlist_for_each_continue(pos) \
        for (pos = pos->next; pos && ({ prefetch(pos->next); 1; }); \
                        pos = pos->next)
#endif

#define _DEFINE_HLIST_SEQ_FOPS(prefix, head, n, lock, unlock, imp_show, \
                imp_write) \
struct _##head##_seq_iter { \
        unsigned int idx; \
        struct hlist_node *node; \
}; \
 \
static void* _##head##_seq_start(struct seq_file *m, loff_t *pos) \
{ \
        struct hlist_node *node; \
        unsigned int idx; \
        loff_t off = *pos; \
 \
        lock; \
        for (idx = 0; idx < n; idx++) { \
                hlist_for_each(node, &head[idx]) { \
                        if (off-- == 0) { \
                                struct _##head##_seq_iter *iter; \
                                iter = kmalloc(sizeof(*iter), GFP_KERNEL); \
                                if (iter == NULL) \
                                        break; \
                                iter->idx = idx; \
                                iter->node = node; \
                                return iter; \
                        } \
                } \
        } \
 \
        return NULL; \
} \
 \
static void* _##head##_seq_next(struct seq_file *m, void *p, loff_t *pos) \
{ \
        struct _##head##_seq_iter *iter = (struct _##head##_seq_iter*)p; \
 \
        ++*pos; \
        hlist_for_each_continue(iter->node) \
                return iter; \
        for (iter->idx++; iter->idx < n; iter->idx++) { \
                hlist_for_each(iter->node, &head[iter->idx]) \
                        return iter; \
        } \
        kfree(iter); \
 \
        return NULL; \
} \
\
static void _##head##_seq_stop(struct seq_file *m, void *p) \
{ \
        kfree(p); \
        unlock; \
} \
 \
static int _##head##_seq_show(struct seq_file *m, void *p) \
{ \
        struct _##head##_seq_iter *iter = (struct _##head##_seq_iter*)p; \
 \
        return imp_show(m, iter->node); \
} \
 \
__DEFINE_FOO_SEQ_FOPS(prefix, head, _##head##_seq_show, imp_write)

#define DEFINE_HLIST_SEQ_FOPS(head, n, lock, unlock, imp_show, imp_write) \
        _DEFINE_HLIST_SEQ_FOPS(, head, n, lock, unlock, imp_show, imp_write)

#define DEFINE_STATIC_HLIST_SEQ_FOPS(head, n, lock, unlock, imp_show, \
                imp_write) \
        _DEFINE_HLIST_SEQ_FOPS(static, head, n, lock, unlock, imp_show, \
                        imp_write)

/* helper for rbtree */
#define _DEFINE_RBTREE_SEQ_FOPS(prefix, tree, lock, unlock, imp_show, \
                imp_write) \
static void* _##tree##_seq_start(struct seq_file *m, loff_t *pos) \
{ \
        struct rb_node *node; \
        loff_t off = *pos; \
 \
        lock; \
        node = rb_first(&tree); \
        while (off-- > 0 && node != NULL) \
                node = rb_next(node); \
 \
        return node; \
} \
 \
static void* _##tree##_seq_next(struct seq_file *m, void *p, loff_t *pos) \
{ \
        ++*pos; \
        return rb_next((struct rb_node*)p); \
} \
\
static void _##tree##_seq_stop(struct seq_file *m, void *p) \
{ \
        unlock; \
} \
 \
__DEFINE_FOO_SEQ_FOPS(prefix, tree, imp_show, imp_write)

#define DEFINE_RBTREE_SEQ_FOPS(tree, lock, unlock, imp_show, imp_write) \
        _DEFINE_RBTREE_SEQ_FOPS(, tree, lock, unlock, imp_show, imp_write)

#define DEFINE_STATIC_RBTREE_SEQ_FOPS(tree, lock, unlock, imp_show, \
                imp_write) \
        _DEFINE_RBTREE_SEQ_FOPS(static, tree, lock, unlock, imp_show, \
                        imp_write)

#endif /* _SEQ_FILE_HELPER_H_ */


應用起來比較簡單,下面是用其重寫過的上面的雙向連接表導出代碼:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>

#include "seq_file_helper.h"

static struct mutex lock;
static struct list_head head;
struct my_data {
        struct list_head list;
        int value;
};

static void add_one(void)
{
        struct my_data *data;

        mutex_lock(&lock);
        data = kmalloc(sizeof(*data), GFP_KERNEL);
        if (data != NULL)
                list_add(&data->list, &head);
        mutex_unlock(&lock);
}

static ssize_t seq_write(struct file *file, const char __user * buffer,
                       size_t count, loff_t *ppos)
{
        add_one();

        return count;
}

static int seq_show(struct seq_file *m, void *p)
{
        struct my_data *data = list_entry(p, struct my_data, list);
        seq_printf(m, "value: %d\n", data->value);

        return 0;
}

DEFINE_STATIC_LIST_SEQ_FOPS(head, mutex_lock(&lock), mutex_unlock(&lock),
                seq_show, seq_write);

static void clean_all(struct list_head *head)
{
        struct my_data *data;

        while (!list_empty(head)) {
                data = list_entry(head->next, struct my_data, list);
                list_del(&data->list);
                kfree(data);
        }
}

static int __init init(void)
{
        struct proc_dir_entry *entry;

        mutex_init(&lock);
        INIT_LIST_HEAD(&head);

        add_one();
        add_one();
        add_one();

        entry = create_proc_entry("my_data", S_IWUSR | S_IRUGO, NULL);
        if (entry == NULL) {
                clean_all(&head);
                return -ENOMEM;
        }
        entry->proc_fops = &head_seq_fops;

        return 0;
}

static void __exit fini(void)
{
        remove_proc_entry("my_data", NULL);
        clean_all(&head);
}

module_init(init);
module_exit(fini);


是不是簡單了不少了?其它數據結構的實例代碼可以參考文後的附件。

參考資料:
  1. http://www.ibm.com/developerworks/cn/linux/l-kerns-usrs2/#N100DA
  2. Linux內核源碼

文章出自:http://www.4ucode.com/Study/Topic/1554981


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章