seq相關頭文件linux/seq_file.h,seq相關函數的實現在fs/seq_file.c。seq函數最早是在2001年就引入了,但以前內核中一直用得不多,而到了2.6內核后,許多/proc的只讀文件中大量使用了seq函數處理。
由於procfs的默認操作函數只使用一頁的緩存,在處理較大的proc文件時就有點麻煩,並且在輸出一系列結構體中的數據時也比較不靈活,需要自己在read_proc函數中實現迭代,容易出現Bug。所以內核黑客們對一些/proc代碼做了研究,抽象出共性,最終形成了seq_file(Sequence file:序列文件)接口。 這個接口提供了一套簡單的函數來解決以上proc接口編程時存在的問題,使得編程更加容易,降低了Bug出現的機會。
在需要創建一個由一系列數據順序組合而成的虛擬文件或一個較大的虛擬文件時,推薦使用seq_file接口。但是我個人認為,並不是只有procfs才可以使用這個seq_file接口,因為其實seq_file是實現的是一個操作函數集,這個函數集並不是與proc綁定的,同樣可以用在其他的地方。
實現
seq_file結構體定義於linux/seq_file.h
struct seq_file { char *buf; //序列文件對應的數據緩沖區,要導出的數據是首先打印到這個緩沖區,然后才被拷貝到指定的用戶緩沖區。 size_t size; //緩沖區大小,默認為1個頁面大小,隨着需求會動態以2的級數倍擴張,4k,8k,16k... size_t from; //沒有拷貝到用戶空間的數據在buf中的起始偏移量 size_t count; //buf中沒有拷貝到用戶空間的數據的字節數,調用seq_printf()等函數向buf寫數據的同時相應增加m->count size_t pad_until; loff_t index; //正在或即將讀取的數據項索引,和seq_operations中的start、next操作中的pos項一致,一條記錄為一個索引 loff_t read_pos; //當前讀取數據(file)的偏移量,字節為單位 u64 version; //文件的版本 struct mutex lock; //序列化對這個文件的並行操作 const struct seq_operations *op; //指向seq_operations int poll_event; const struct file *file; // seq_file相關的proc或其他文件 void *private; //指向文件的私有數據 };
seq操作函數:
struct seq_operations { void * (*start) (struct seq_file *m, loff_t *pos); //開始讀數據項,通常需要在這個函數中加鎖,以防止並行訪問數據 void (*stop) (struct seq_file *m, void *v); //停止數據項,和start相對,通常需要解鎖 void * (*next) (struct seq_file *m, void *v, loff_t *pos); //下一個要處理的數據項 int (*show) (struct seq_file *m, void *v); //打印數據項到臨時緩沖區 };
start在*pos為0時可以返回SEQ_START_TOKEN,通過這個值傳遞給show的時候,show會打印表格頭。
start和next返回一條數據記錄,stop停止打印,show顯示一條記錄。
注意:要在next中對pos遞增處理,但遞增的單位與迭代器有關,可能不是1。
一些有用的全局函數:
- 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的時候創建一小塊文件私有數據。
特別提取了雙向鏈表和hash鏈表處理函數:
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; } EXPORT_SYMBOL(seq_list_start); struct list_head *seq_list_start_head(struct list_head *head, loff_t pos) { if (!pos) return head; return seq_list_start(head, pos - 1); } EXPORT_SYMBOL(seq_list_start_head); 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; } EXPORT_SYMBOL(seq_list_next);
seq_file實現中關鍵函數為seq_read(),將記錄逐條讀取到用戶空間:
/** * seq_read - ->read() method for sequential files. * @file: the file to read from * @buf: the buffer to read to * @size: the maximum number of bytes to read * @ppos: the current position in the file * * Ready-made ->f_op->read() */ ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) { struct seq_file *m = file->private_data; size_t copied = 0; loff_t pos; size_t n; void *p; int err = 0; mutex_lock(&m->lock); /* * seq_file->op->..m_start/m_stop/m_next may do special actions * or optimisations based on the file->f_version, so we want to * pass the file->f_version to those methods. * * seq_file->version is just copy of f_version, and seq_file * methods can treat it simply as file version. * It is copied in first and copied out after all operations. * It is convenient to have it as part of structure to avoid the * need of passing another argument to all the seq_file methods. */ m->version = file->f_version; /* Don't assume *ppos is where we left it */ if (unlikely(*ppos != m->read_pos)) { // 讀取位置與當前buf位置不同,traverse遍歷 while ((err = traverse(m, *ppos)) == -EAGAIN) ; if (err) { /* With prejudice... */ m->read_pos = 0; m->version = 0; m->index = 0; m->count = 0; goto Done; } else { m->read_pos = *ppos; } } /* grab buffer if we didn't have one */ if (!m->buf) { m->buf = seq_buf_alloc(m->size = PAGE_SIZE); if (!m->buf) goto Enomem; } /* if not empty - flush it first */ if (m->count) { //操作完后兩情況:buf數據讀取完,m->count=0;未讀完,size=0,下次再讀,本次讀取結束 n = min(m->count, size); err = copy_to_user(buf, m->buf + m->from, n); if (err) goto Efault; m->count -= n; m->from += n; size -= n; buf += n; copied += n; if (!m->count) { m->from = 0; m->index++; } if (!size) goto Done; } /* we need at least one record in buffer */ pos = m->index; p = m->op->start(m, &pos); while (1) { //容納一條數據后執行Fill,不能容納一條數據就分配更大空間,若讀到最后記錄就break。 err = PTR_ERR(p); if (!p || IS_ERR(p)) break; err = m->op->show(m, p); if (err < 0) break; if (unlikely(err)) m->count = 0; if (unlikely(!m->count)) { p = m->op->next(m, p, &pos); m->index = pos; continue; } if (m->count < m->size) goto Fill; m->op->stop(m, p); kvfree(m->buf); m->count = 0; m->buf = seq_buf_alloc(m->size <<= 1); if (!m->buf) goto Enomem; m->version = 0; pos = m->index; p = m->op->start(m, &pos); } m->op->stop(m, p); m->count = 0; goto Done; Fill: //退出條件為沒有記錄可讀(next返回NULL)或buf緩沖區滿 /* they want more? let's try to get some more */ while (m->count < size) { size_t offs = m->count; loff_t next = pos; p = m->op->next(m, p, &next); if (!p || IS_ERR(p)) { err = PTR_ERR(p); break; } err = m->op->show(m, p); if (seq_has_overflowed(m) || err) { m->count = offs; if (likely(err <= 0)) break; } pos = next; } m->op->stop(m, p); n = min(m->count, size); err = copy_to_user(buf, m->buf, n); if (err) goto Efault; copied += n; m->count -= n; if (m->count) m->from = n; else pos++; m->index = pos; Done: // 操作完成后,出錯返回錯誤,成功則file位置偏移和seq讀指針偏移增加拷貝字節數 if (!copied) copied = err; else { *ppos += copied; m->read_pos += copied; } file->f_version = m->version; mutex_unlock(&m->lock); return copied; Enomem: err = -ENOMEM; goto Done; Efault: err = -EFAULT; goto Done; } EXPORT_SYMBOL(seq_read);
示例
如下例用於列出進程相關信息,在ubuntu 16/18上測試通過:
//#include <linux/config.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/proc_fs.h> #include <linux/seq_file.h> #include <linux/percpu.h> #include <linux/init.h> #include <linux/sched.h>
// #include <linux/sched/signal.h> static struct proc_dir_entry *entry; static loff_t offset = 1; static void *l_start(struct seq_file *m, loff_t * pos) { loff_t index = *pos; loff_t i = 0; struct task_struct * task ; if (index == 0) { seq_printf(m, "Current all the processes in system:\n" "%-24s%-5s\n", "name", "pid"); printk(KERN_EMERG "++++++++++=========>%5d\n", 0); // offset = 1; return &init_task; }else { for(i = 0, task=&init_task; i < index; i++){ task = next_task(task); } BUG_ON(i != *pos); if(task == &init_task){ return NULL; } printk(KERN_EMERG "++++++++++>%5d\n", task->pid); return task; } } static void *l_next(struct seq_file *m, void *p, loff_t * pos) { struct task_struct * task = (struct task_struct *)p; task = next_task(task); if ((*pos != 0) && (task == &init_task)) { // if ((task == &init_task)) { // printk(KERN_EMERG "=====>%5d\n", task->pid); return NULL; } printk(KERN_EMERG "=====>%5d\n", task->pid); offset = ++(*pos); return task; } static void l_stop(struct seq_file *m, void *p) { printk(KERN_EMERG "------>\n"); } static int l_show(struct seq_file *m, void *p) { struct task_struct * task = (struct task_struct *)p; seq_printf(m, "%-24s%-5d\t%lld\n", task->comm, task->pid, offset); // seq_printf(m, "======>%-24s%-5d\n", task->comm, task->pid); return 0; } static struct seq_operations exam_seq_op = { .start = l_start, .next = l_next, .stop = l_stop, .show = l_show }; static int exam_seq_open(struct inode *inode, struct file *file) { return seq_open(file, &exam_seq_op); } static struct file_operations exam_seq_fops = { .open = exam_seq_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release, }; static int __init exam_seq_init(void) { // entry = create_proc_entry("exam_esq_file", 0, NULL); entry = proc_create("exam_esq_file", 0444, NULL, &exam_seq_fops); if (!entry) printk(KERN_EMERG "proc_create error.\n"); //entry->proc_fops = &exam_seq_fops; printk(KERN_EMERG "exam_seq_init.\n"); return 0; } static void __exit exam_seq_exit(void) { remove_proc_entry("exam_esq_file", NULL); printk(KERN_EMERG "exam_seq_exit.\n"); } module_init(exam_seq_init); module_exit(exam_seq_exit); MODULE_LICENSE("GPL");
makefile
obj-m := seq.o KDIR := /lib/modules/$(shell uname -r)/build #KDIR := ~/source_ap/build_dir/target-arm_cortex-a7_uClibc-1.0.14_eabi/linux-ipq806x/linux-3.14.77 PWD := $(shell pwd) default: $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules clean: rm -rf *.o *.ko *.mod* *.order *.sym*
參考:
1. 內核proc文件系統與seq接口(4)---seq_file接口編程淺析 tekkaman
2. 內核proc文件系統與seq接口(5)---通用proc接口與seq_file接口實驗 tekkaman