在需要創建一個由一系列數據順序組合而成的/proc虛擬文件或一個較大的/proc虛擬文件時,推薦使用seq_file接口。
數據結構struct seq_fille定義在include/linux/seq_file.h
struct seq_file {
char *buf; //seq_file接口使用的緩存頁指針
size_t size; //seq_file接口使用的緩存頁大小
size_t from; //從seq_file中向用戶態緩沖區拷貝時相對於buf的偏移地址
size_t count; //buf中可以拷貝到用戶態的字符數目
loff_t index; //start、next的處理的下標pos數值
loff_t read_pos; //當前已拷貝到用戶態的數據量大小
u64 version;
struct mutex lock; //針對此seq_file操作的互斥鎖,所有seq_*的訪問都會上鎖
const struct seq_operations *op; //操作實際底層數據的函數
void *private;
};
這里我們大致介紹下struct seq_operations中的各個函數的作用:
void * (*start) (struct seq_file *m, loff_t *pos);
start 方法會首先被調用,它的作用是在設置訪問的起始點。
m:指向的是本seq_file的結構體,在正常情況下無需處理。
pos:是一個整型位置值,指示開始讀取的位置。對於這個位置的意義完全取決於底層實現,不一定是字節為單位的位置,可能是一個元素的序列號。返回值如果非NULL,則是一個指向迭代器實現的私有數據結構體指針。如果訪問出錯則返回NULL。
設置好了訪問起始點,seq_file內部機制可能會使用show方法獲取start返回值指向的結構體中的數據到內部緩存,並適時送往用戶空間。
int (*show) (struct seq_file *m, void *v);
所以show方法就是負責將v指向元素中的數據輸出到seq_file的內部緩存,但是其中必須借助seq_file提供的一些類似printf的接口函數:
int seq_printf(struct seq_file *sfile, const char *fmt, ...);//專為 seq_file 實現的類似 printf 的函數;用於將數據常用的格式串和附加值參數.
//你必須將給 show 函數的 set_file 結構指針傳遞給它。如果seq_printf 返回-1,意味着緩存區已滿,部分輸出被丟棄。但是大部分時候都忽略了其返回值。
int seq_putc(struct seq_file *sfile, char c);
int seq_puts(struct seq_file *sfile, const char *s);//類似 putc 和 puts 函數的功能,sfile參數和返回值與 seq_printf相同。
int seq_escape(struct seq_file *m, const char *s, const char *esc);//這個函數類似 seq_puts ,但是它會將 s 中所有在 esc 中出現的字符以八進制格式輸出到緩存。
//esc 的常用值是"\t\n\\", 它使內嵌的空格不會搞亂輸出或迷惑 shell 腳本.
在show函數返回之后,seq_file機制可能需要移動到下一個數據元素,那就必須使用next方法。
void * (*next) (struct seq_file *m, void *v, loff_t *pos);
v:是之前調用start或next返回的元素指針,可能是上一個show已經完成輸出所指向的元素。
pos:需要移動到的元素索引值。
在next實現中應當遞增pos指向的值,但是具體遞增的數量和迭代器的實現有關,不一定是1。而next的返回值如果非NULL,則是下一個需要輸出到緩存的元素指針,否則表明已經輸出結束,將會調用stop方法做清理。
void (*stop) (struct seq_file *m, void *v);
在stop實現中,參數m指向本seq_file的結構體,在正常情況下無需處理。而v是指向上一個next或start返回的元素指針。在需要做退出處理的時候才需要實現具體的功能。但是許多情況下可以直接返回。
在next和start的實現中可能需要對一個序列的函數進行遍歷,而在內核中,對於一個序列數據結構體的實現一般是使用雙向鏈表或者哈希鏈表,所有seq_file同時提供了一些對於內核雙向鏈表和哈希鏈表的封裝接口函數,方便程序員實現對於通過鏈表鏈接的結構體序列的操作。這些函數名一般是seq_list_*或者seq_hlist_*,這些函數的實現都在fs/seq_file.c中。
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
static struct proc_dir_entry *proc_root;
static struct proc_dir_entry *proc_entry;
#define USER_ROOT_DIR "fellow_root"
#define USER_ENTRY "fellow_entry"
#define INFO_LEN 16
char *info;
static int proc_fellow_show(struct seq_file *m, void *v)
{
seq_printf(m, "%s\n",info);
return 0;
}
static int proc_fellow_open(struct inode *inode, struct file *file)
{
return single_open(file, proc_fellow_show, NULL);//而調用single_open函數只需直接指定show的函數指針即可,個人猜測可能是在single_open函數中實現了seq_operations結構體 。如果使用seq_open則需要實現seq_operations。
}
static ssize_t proc_fellow_write(struct file *file, const char __user *buffer, size_t count, loff_t *f_pos)
{
if ( count > INFO_LEN)
return -EFAULT;
if(copy_from_user(info, buffer, count))
{
return -EFAULT;
}
return count;
}
static const struct file_operations fellow_proc_fops = {
.owner = THIS_MODULE,
.open = proc_fellow_open,
.read = seq_read,
.write = proc_fellow_write,
.llseek = seq_lseek,
.release = single_release,
};
static int fellow_create_proc_entry(void)
{
int error = 0;
proc_root = proc_mkdir(USER_ROOT_DIR, NULL); //創建目錄
if (NULL==proc_root)
{
printk(KERN_ALERT"Create dir /proc/%s error!\n", USER_ROOT_DIR);
return -ENOMEM;
}
printk(KERN_INFO"Create dir /proc/%s\n", USER_ROOT_DIR);
// proc_entry =create_proc_entry(USER_ENTRY, 0666, proc_root);
proc_entry = proc_create(USER_ENTRY, 0666, proc_root, &fellow_proc_fops); //在proc_root下創建proc_entry.
if (NULL ==proc_entry)
{
printk(KERN_ALERT"Create entry %s under /proc/%s error!\n", USER_ENTRY,USER_ROOT_DIR);
error = -ENOMEM;
goto err_out;
}
//proc_entry->write_proc= fellow_writeproc;
//proc_entry->read_proc = fellow_readproc;
printk(KERN_INFO"Create /proc/%s/%s\n", USER_ROOT_DIR,USER_ENTRY);
return 0;
err_out:
//proc_entry->read_proc = NULL;
//proc_entry->write_proc= NULL;
remove_proc_entry(USER_ENTRY, proc_root);
remove_proc_entry(USER_ROOT_DIR, NULL);
return error;
}
static int fellowproc_init(void)
{
int ret = 0;
printk("fellowproc_init\n");
info = kmalloc(INFO_LEN * sizeof(char), GFP_KERNEL);
if (!info)
{
ret = -ENOMEM;
goto fail;
}
memset(info, 0, INFO_LEN);
fellow_create_proc_entry();
return 0;
fail:
return ret;
}
static void fellowproc_exit(void)
{
kfree(info);
remove_proc_entry(USER_ENTRY, proc_root);
remove_proc_entry(USER_ROOT_DIR, NULL);
}
MODULE_AUTHOR("fellow");
MODULE_LICENSE("GPL");
module_init(fellowproc_init);
module_exit(fellowproc_exit);
運行結果如下: