作者
彭東林
pengdonglin137@163.com
平台
Linux-4.14.13
Qemu + vexpress
概述
從內核中導出信息到用戶空間有很多方法,可以自己去實現file_operations的read函數或者mmap函數,但是這種方法不夠簡單,而且也會有一些限制,比如一次read讀取大於1頁時,驅動里就不得不去進行復雜的緩沖區管理。為此,就需要學習一下seq_file的用法,為了更簡單和方便,內核提供了single_xxx系列接口,它是對seq_file的進一步封裝。
正文
示例程序
1 #include <linux/init.h> 2 #include <linux/module.h> 3 #include <linux/seq_file.h> 4 #include <linux/debugfs.h> 5 #include <linux/fs.h> 6 7 static struct dentry *seq_file_demo_dir; 8 9 static int seq_file_demo_show(struct seq_file *seq, void *v) 10 { 11 seq_printf(seq, "Hello World\n"); 12 return 0; 13 } 14 15 static int seq_file_demo_open(struct inode *inode, struct file *file) 16 { 17 return single_open(file, &seq_file_demo_show, NULL); 18 } 19 20 static const struct file_operations seq_file_demo_fops = { 21 .owner = THIS_MODULE, 22 .open = seq_file_demo_open, 23 .read = seq_read, 24 .llseek = seq_lseek, 25 .release = single_release, 26 }; 27 28 static int __init seq_file_demo_init(void) 29 { 30 seq_file_demo_dir = debugfs_create_file("seq_file_demo", 0444, NULL, 31 NULL, &seq_file_demo_fops); 32 return 0; 33 } 34 35 static void __exit seq_file_demo_exit(void) 36 { 37 if (seq_file_demo_dir) 38 debugfs_remove(seq_file_demo_dir); 39 } 40 41 module_init(seq_file_demo_init); 42 module_exit(seq_file_demo_exit); 43 MODULE_LICENSE("GPL");
上面的demo在/sys/kernel/debug/下創建了一個"seq_file_demo",讀取這個文件會得到"Hello World"字符串。上面的函數中需要我們實現的只有一個:seq_file_demo_show,直接調用seq_file提供的輸出函數即可,不用我們去考慮緩沖區的分配、釋放以及越界等問題,可以盡情暢快的輸出。
上面的代碼里有些標准化的函數,看着有些礙眼,所以在Linux-4.15的
include/linux/seq_file.h中提供了下面的宏定義:
1 #define DEFINE_SHOW_ATTRIBUTE(__name) \ 2 static int __name ## _open(struct inode *inode, struct file *file) \ 3 { \ 4 return single_open(file, __name ## _show, inode->i_private); \ 5 } \ 6 \ 7 static const struct file_operations __name ## _fops = { \ 8 .owner = THIS_MODULE, \ 9 .open = __name ## _open, \ 10 .read = seq_read, \ 11 .llseek = seq_lseek, \ 12 .release = single_release, \ 13 }
利用上面的宏可以對我們的驅動做進一步簡化:
1 #include <linux/init.h> 2 #include <linux/module.h> 3 #include <linux/seq_file.h> 4 #include <linux/debugfs.h> 5 #include <linux/fs.h> 6 7 static struct dentry *seq_file_demo_dir; 8 9 static int seq_file_demo_show(struct seq_file *seq, void *v) 10 { 11 seq_printf(seq, "Hello World\n"); 12 return 0; 13 } 14 DEFINE_SHOW_ATTRIBUTE(seq_file_demo); 15 16 static int __init seq_file_demo_init(void) 17 { 18 seq_file_demo_dir = debugfs_create_file("seq_file_demo", 0444, NULL, 19 NULL, &seq_file_demo_fops); 20 return 0; 21 } 22 23 static void __exit seq_file_demo_exit(void) 24 { 25 if (seq_file_demo_dir) 26 debugfs_remove(seq_file_demo_dir); 27 } 28 29 module_init(seq_file_demo_init); 30 module_exit(seq_file_demo_exit); 31 MODULE_LICENSE("GPL");
這樣我們只需要專心實現show函數即可。
如果看一下single_open,會發現它是對seq_file的進一步封裝:
1 int single_open(struct file *file, int (*show)(struct seq_file *, void *), 2 void *data) 3 { 4 struct seq_operations *op = kmalloc(sizeof(*op), GFP_KERNEL); 5 int res = -ENOMEM; 6 7 if (op) { 8 op->start = single_start; 9 op->next = single_next; 10 op->stop = single_stop; 11 op->show = show; 12 res = seq_open(file, op); 13 if (!res) 14 ((struct seq_file *)file->private_data)->private = data; 15 else 16 kfree(op); 17 } 18 return res; 19 }
上面設置了single_xx的start、next以及stop回調函數,實現很簡單:
1 static void *single_start(struct seq_file *p, loff_t *pos) 2 { 3 return NULL + (*pos == 0); 4 } 5 6 static void *single_next(struct seq_file *p, void *v, loff_t *pos) 7 { 8 ++*pos; 9 return NULL; 10 } 11 12 static void single_stop(struct seq_file *p, void *v) 13 { 14 }
其中,*ops表示的是要輸出的元素的索引編號,從0開始,依次遞增;
single_start的返回值表示要輸出的元素的首地址,這個函數的作用是找到索引號為*pos的元素,並返回該元素的首地址,此外也可以做一些加鎖的操作
single_next的入參中v表示剛剛show過的元素的首地址,*pos是該元素的索引,這個函數的目的是計算並返回下一個要show的元素的首地址以及索引號
single_stop里可以做一些釋放鎖的操作
show需要自己實現,向用戶show出當前元素的相關信息
未完待續