seq_file 是內核提供的標準接口,用於在 /proc 或 /sys 下以流式方式輸出大量數據(如鏈表、數組)。本文通過一個受讀寫自旋鎖保護的鏈表示例,完整演示 seq_operations 四大回調的實現與註冊流程。
struct node {
struct node *next;
int num;
};
struct node *list_head;
DEFINE_RWLOCK(list_lock);
struct seq_operations {
void * (*start) (struct seq_file *m, loff_t *pos);
void (*stop) (struct seq_file *m, void *v);
void * (*next) (struct seq_file *m, void *v, loff_t *pos);
int (*show) (struct seq_file *m, void *v);
};
*pos==0 返回 SEQ_START_TOKEN 打印表頭;否則遍歷鏈表定位到第 pos 個節點。pos++,返回節點指針或 NULL 結束。v==SEQ_START_TOKEN 打印表頭;否則輸出 num,每 10 個換行。static void *my_start(struct seq_file *m, loff_t *pos) {
int i = 1;
struct node *tmp;
read_lock_bh(&list_lock);
if (*pos == 0) return SEQ_START_TOKEN;
for (tmp = list_head; tmp; tmp = tmp->next) {
if (i == *pos) return tmp;
++i;
}
return NULL;
}
static void *my_next(struct seq_file *m, void *v, loff_t *pos) {
++*pos;
return (v == SEQ_START_TOKEN) ? list_head : ((struct node *)v)->next;
}
static int my_show(struct seq_file *m, void *v) {
static int line = 0;
if (v == SEQ_START_TOKEN) {
seq_printf(m, "\n----- data start -----\n");
} else {
seq_printf(m, " %d ", ((struct node *)v)->num);
if (++line == 10) { seq_printf(m, "\n"); line = 0; }
}
return 0;
}
static void my_stop(struct seq_file *m, void *v) {
read_unlock_bh(&list_lock);
seq_printf(m, "\n----- data end -----\n");
}
static const struct seq_operations my_seq_ops = {
.start = my_start, .next = my_next,
.stop = my_stop, .show = my_show,
};
static int proc_my_open(struct inode *inode, struct file *file) {
return seq_open(file, &my_seq_ops);
}
static const struct file_operations my_fops = {
.owner = THIS_MODULE,
.open = proc_my_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
proc_create("my_test", 0, NULL, &my_fops);
start/next/stop 可能因緩衝區分頁多次調用,依賴 pos 維持迭代狀態。start/stop,保證遍歷原子性。single_open + 單一 show 替代,代碼更精簡。掌握 seq_file,即可高效實現內核數據到用戶態的大規模、分頁友好導出。