linux seq_file 接口
如我們上面提到的, 在 /proc 下的大文件的實現有點麻煩. 一直以來, /proc 方法因為 當輸出數量變大時的錯誤實現變得聲名狼藉. 作為一種清理 /proc 代碼以及使內核開發 者活得輕松些的方法, 添加了 seq_file 接口. 這個接口提供了簡單的一套函數來實現大 內核虛擬文件.
?
set_file 接口假定你在創建一個虛擬文件, 它涉及一系列的必須返回給用戶空間的項. 為使用 seq_file, 你必須創建一個簡單的 "iterator" 對象, 它能在序列里建立一個位 置, 向前進, 并且輸出序列里的一個項. 它可能聽起來復雜, 但是, 實際上, 過程非常簡 單. 我們一步步來創建 /proc 文件在 scull 驅動里, 來展示它是如何做的.
?
第一步, 不可避免地, 是包含 <linux/seq_file.h>. 接著你必須創建 4 個 iterator 方 法, 稱為 start, next, stop, 和 show.
?
start 方法一直是首先調用. 這個函數的原型是:
?
void *start(struct seq_file *sfile, loff_t *pos);
?
sfile 參數可以幾乎是一直被忽略. pos 是一個整型位置值, 指示應當從哪里讀. 位置的 解釋完全取決于實現; 在結果文件里不需要是一個字節位置. 因為 seq_file 實現典型地 步進一系列感興趣的項, position 常常被解釋為指向序列中下一個項的指針. scull 驅 動解釋每個設備作為系列中的一項, 因此進入的 pos 簡單地是一個 scull_device 數組 的索引. 因此, scull 使用的 start 方法是:
?
static void *scull_seq_start(struct seq_file *s, loff_t *pos)
{
if (*pos >= scull_nr_devs)
return NULL;? /* No more to read */ return scull_devices + *pos;
}
?
返回值, 如果非 NULL, 是一個可以被 iterator 實現使用的私有值.
?
next 函數應當移動 iterator 到下一個位置, 如果序列里什么都沒有剩下就返回 NULL. 這個方法的原型是:
?
void *next(struct seq_file *sfile, void *v, loff_t *pos);
?
這里, v 是從前一個對 start 或者 next 的調用返回的 iterator, pos 是文件的當前位 置. next 應當遞增有 pos 指向的值; 根據你的 iterator 是如何工作的, 你可能(盡管 可能不會)需要遞增 pos 不止是 1. 這是 scull 所做的:
?
static void *scull_seq_next(struct seq_file *s, void *v, loff_t *pos)
{
(*pos)++;
if (*pos >= scull_nr_devs) return NULL;
return scull_devices + *pos;
}
?
當內核處理完 iterator, 它調用 stop 來清理: void stop(struct seq_file *sfile, void *v);
scull 實現沒有清理工作要做, 所以它的 stop 方法是空的.
?
設計上, 值得注意 seq_file 代碼在調用 start 和 stop 之間不睡眠或者進行其他非原 子性任務. 你也肯定會看到在調用 start 后馬上有一個 stop 調用. 因此, 對你的 start 方法來說請求信號量或自旋鎖是安全的. 只要你的其他 seq_file 方法是原子的, 調用的整個序列是原子的. (如果這一段對你沒有意義, 在你讀了下一章后再回到這.)
?
在這些調用中, 內核調用 show 方法來真正輸出有用的東西給用戶空間. 這個方法的原型 是:
?
int show(struct seq_file *sfile, void *v);
?
這個方法應當創建序列中由 iterator v 指示的項的輸出. 不應當使用 printk, 但是; 有一套特殊的用作 seq_file 輸出的函數:
?
int seq_printf(struct seq_file *sfile, const char *fmt, ...);
?
這是給 seq_file 實現的 printf 對等體; 它采用常用的格式串和附加值參數. 你 必須也將給 show 函數的 set_file 結構傳遞給它, 然而. 如果 seq_printf 返回 非零值, 意思是緩存區已填充, 輸出被丟棄. 大部分實現忽略了返回值, 但是.
?
int seq_putc(struct seq_file *sfile, char c);
int seq_puts(struct seq_file *sfile, const char *s); 它們是用戶空間 putc 和 puts 函數的對等體.
int seq_escape(struct seq_file *m, const char *s, const char *esc);
?
這個函數是 seq_puts 的對等體, 除了 s 中的任何也在 esc 中出現的字符以八進 制格式打印. esc 的一個通用值是"\t\n\\", 它使內嵌的空格不會搞亂輸出和可能 搞亂 shell 腳本.
?
int seq_path(struct seq_file *sfile, struct vfsmount *m, struct dentry *dentry, char *esc);
?
這個函數能夠用來輸出和給定命令項關聯的文件名子. 它在設備驅動中不可能有用; 我們是為了完整在此包含它.
?
回到我們的例子; 在 scull 使用的 show 方法是:
?
static int scull_seq_show(struct seq_file *s, void *v)
{
struct scull_dev *dev = (struct scull_dev *) v; struct scull_qset *d;
int i;
?
if (down_interruptible (&dev->sem)) return -ERESTARTSYS;
?
seq_printf(s, "\nDevice %i: qset %i, q %i, sz %li\n", (int) (dev - scull_devices), dev->qset, dev->quantum, dev->size);
?
for (d = dev->data; d; d = d->next) { /* scan the list */ seq_printf(s, " item at %p, qset at %p\n", d, d->data);
?
if (d->data && !d->next) /* dump only the last item */
?
for (i = 0; i < dev->qset; i++) { if (d->data[i])
seq_printf(s, " % 4i: %8p\n",
i, d->data[i]);
}
}
up(&dev->sem); return 0;
}
?
這里, 我們最終解釋我們的" iterator" 值, 簡單地是一個 scull_dev 結構指針.
?
現在已有了一個完整的 iterator 操作的集合, scull 必須包裝起它們, 并且連接它們到
/proc 中的一個文件. 第一步是填充一個 seq_operations 結構:
?
static struct seq_operations scull_seq_ops = {
.start = scull_seq_start,
.next = scull_seq_next,
.stop = scull_seq_stop,
.show = scull_seq_show
};
?
有那個結構在, 我們必須創建一個內核理解的文件實現. 我們不使用前面描述過的 read_proc 方法; 在使用 seq_file 時, 最好在一個稍低的級別上連接到 /proc. 那意味 著創建一個 file_operations 結構(是的, 和字符驅動使用的同樣結構) 來實現所有內核 需要的操作, 來處理文件上的讀和移動. 幸運的是, 這個任務是簡單的. 第一步是創建一 個 open 方法連接文件到 seq_file 操作:
?
static int scull_proc_open(struct inode *inode, struct file *file)
{
return seq_open(file, &scull_seq_ops);
}
?
調用 seq_open 連接文件結構和我們上面定義的序列操作. 事實證明, open 是我們必須 自己實現的唯一文件操作, 因此我們現在可以建立我們的 file_operations 結構:
?
static struct file_operations scull_proc_ops = {
.owner = THIS_MODULE,
.open = scull_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release
};
?
這里我們指定我們自己的 open 方法, 但是使用預裝好的方法 seq_read, seq_lseek, 和 seq_release 給其他.
?
最后的步驟是創建 /proc 中的實際文件:
?
entry = create_proc_entry("scullseq", 0, NULL); if (entry)
entry->proc_fops = &scull_proc_ops;
?
不是使用 create_proc_read_entry, 我們調用低層的 create_proc_entry, 我們有這個 原型:
?
struct proc_dir_entry *create_proc_entry(const char *name,mode_t mode,struct proc_dir_entry *parent);
?
參數和它們的在 create_proc_read_entry 中的對等體相同: 文件名子, 它的位置, 以及 父目錄.
?
有了上面代碼, scull 有一個新的 /proc 入口, 看來很象前面的一個. 但是, 它是高級 的, 因為它不管它的輸出有多么大, 它正確處理移動, 并且通常它是易讀和易維護的. 我 們建議使用 seq_file , 來實現包含多個非常小數目的輸出行數的文件.
?
轉載于:https://www.cnblogs.com/fanweisheng/p/11141537.html
總結
以上是生活随笔為你收集整理的linux seq_file 接口的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 访问CrmService遇到HTTP s
- 下一篇: Linux学习日记:第二天