在上面我们介绍了使用 seq_file 需要实现的一些函数和相关结构体,现在我们把它们组合起来,介绍以下
通过 proc 来使用 seq_file 的一般步骤,而 seq_file 在其他方面的应用方法也是一样的。
(1) 实现 seq_operations ,也就是前面我们介绍的 seq_file 的底层数据操作函数集,示例如下:
static struct seq_operations proc_seq_ops = {
.start = proc_seq_start,
.next = proc_seq_next,
.stop = proc_seq_stop,
.show = proc_seq_show
};
这些回调函数都是需要根据你所要获取的序列数据结构来实现的。
(2) 实现 struct file_operations 中的 open 函数,这个 open 函数的实现也是非常简单固定格式,无私有数据
情况实例如下:
static int proc_seq_open(struct inode *inode, struct file *file)
{
return seq_open(file, &proc_seq_ops);
};
seq_open 的实现如下,此函数实现在 seq_file.c 中。
int seq_open(struct file *file, const struct seq_operations *op)
struct seq_file *p;
WARN_ON(file->private_data);
p = kzalloc(sizeof(*p), GFP_KERNEL);
if (!p)
return -ENOMEM;
file->private_data = p;
mutex_init(&p->lock);
p->op = op;
// No refcounting: the lifetime of 'p' is constrained
// to the lifetime of the file.
p->file = file;
/*
* Wrappers around seq_open(e.g. swaps_open) need to be
* aware of this. If they set f_version themselves, they
* should call seq_open first and then set f_version.
*/
file->f_version = 0;
/*
* seq_files support lseek() and pread(). They do not implement
* write() at all, but we clear FMODE_PWRITE here for historical
* reasons.
*
* If a client of seq_files a) implements file.write() and b) wishes to
* support pwrite() then that client will need to implement its own
* file.open() which calls seq_open() and then sets FMODE_PWRITE.
*/
file->f_mode &= ~FMODE_PWRITE;
return 0;
}
seq_file 结构会在 seq_open 中申请,并附在 file 的私有数据成员 private_data 中。可以看到,一般就是使用
seq_file 中的一个 API:seq_open,目的是向 seq_file 结构体中注册一个 struct seq_operations 。
另外还有 int seq_open_private(struct file *filp, const struct seq_operations *ops,int psize)函数,此函数会为
seq_file 的私有数据申请空间。
若需要私有数据可使用 seq_open_private 接口。
(3) 实现 struct file_operations proc_ops,示例如下:
static struct file_operations proc_ops = {
.owner = THIS_MODULE,
.open = proc_seq_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
大家可以看到,以上的 read、llseek、release 都是有 seq_file 提供的接口函数,直接注册即可,唯有 open 是
需要自己实现的。
在 seq 中是没有实现 write 操作的,因此,如果根据需要添加 write 操作,需要再 file_ops 中单独添加。
(4)注册 proc 文件,注册包含 seq_file 操作函数的 file_operations 结构体到 proc_fops。我的测试程序中的实
例代码如下:
……
proc_test_entry = create_proc_entry("proc_seq", 0644, NULL);
if (proc_test_entry == NULL) {
ret = -ENOMEM;
cleanup_test_data();
pr_err("proc_test: Couldn't create proc entry\n");
} else {
proc_test_entry->proc_fops = &proc_ops;
pr_info("proc_test: Module loaded.\n");
}
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/kernel_stat.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#define MAX_CPU_NUM 32
#define RPT_LINE_MAXLEN 1024
unsigned int last_user[MAX_CPU_NUM];
unsigned int last_system[MAX_CPU_NUM];
unsigned int last_nice[MAX_CPU_NUM];
unsigned int last_idle[MAX_CPU_NUM];
unsigned int last_si[MAX_CPU_NUM];
unsigned int last_hi[MAX_CPU_NUM];
static struct proc_dir_entry *monitor_root_dir;
static struct proc_dir_entry *proc_tos;
#define LEFT(x) (((unsigned)x) / 10)
#define RIGHT(x) (((unsigned)x) % 10)
#define SEQ_START_TOKEN ((void *)1)
static void *tos_get_cpuinfo_start(struct seq_file *f, loff_t *pos)
{
void *v = NULL;
v = *pos ? pos : SEQ_START_TOKEN;
return v;
}
static int tos_get_cpuinfo_show(struct seq_file *f, void *v)
{
unsigned int user=0, system=0, nice=0, idle=0, si = 0, hi = 0;
unsigned int sum;
int cpu_num = num_possible_cpus();
int i;
if (v == SEQ_START_TOKEN){
for(i=0;i<cpu_num;i++){
user = kcpustat_cpu(i).cpustat[CPUTIME_USER]-last_user[i];
nice = kcpustat_cpu(i).cpustat[CPUTIME_NICE]-last_nice[i];
system = kcpustat_cpu(i).cpustat[CPUTIME_SYSTEM]-last_system[i];
idle = kcpustat_cpu(i).cpustat[CPUTIME_IDLE]-last_idle[i];
si = kcpustat_cpu(i).cpustat[CPUTIME_SOFTIRQ]-last_si[i];
hi = kcpustat_cpu(i).cpustat[CPUTIME_IRQ]-last_hi[i];
sum = user+nice+system+idle + si + hi;
if(sum==0){
user=1;
nice = 0;
system =89;
idle = 10;
si = 0;
hi = 0;
sum = user+nice+system+idle + si + hi;
}
seq_printf(f, "CPU Load
Information:(%d %s)\n",cpu_num,cpu_num>1?"cpus":"cpu");
seq_printf(f," cpu%d:\n",i);
seq_printf(f, "%-18s%u.%u%%\n", " User",
LEFT(user*1000/sum),
RIGHT(user*1000/sum));
seq_printf(f, "%-18s%u.%u%%\n", " System",
LEFT(system*1000/sum),
RIGHT(system*1000/sum));
seq_printf(f, "%-18s%u.%u%%\n"," Nice",
LEFT(nice*1000/sum),
RIGHT(nice*1000/sum));
seq_printf(f, "%-18s%u.%u%%\n"," Si",
LEFT(si*1000/sum),
RIGHT(si*1000/sum));
seq_printf(f, "%-18s%u.%u%%\n"," Hi",
LEFT(hi*1000/sum),
RIGHT(hi*1000/sum));
seq_printf(f, "%-18s%u.%u%%\n"," Idle",
LEFT(idle*1000/sum),
RIGHT(idle*1000/sum));
}
seq_printf(f, "\n");
}
return 0;
}
static void *tos_get_cpuinfo_next(struct seq_file *f, void *v, loff_t *pos)
{
(*pos)++;
return v == SEQ_START_TOKEN ? NULL : NULL;//只获取一次
}
static void tos_get_cpuinfo_stop(struct seq_file *f, void *v)
{
/* Nothing to do */
}
static const struct seq_operations tos_get_cpuinfo_sops = {
.start = tos_get_cpuinfo_start,
.next = tos_get_cpuinfo_next,
.stop = tos_get_cpuinfo_stop,
.show = tos_get_cpuinfo_show
};
static int tos_get_cpuinfo_open(struct inode *inode, struct file *filp)
{
return seq_open(filp, &tos_get_cpuinfo_sops);
}
static const struct file_operations tos_get_cpuinfo_fopes = {
.open = tos_get_cpuinfo_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
static int __init tos_get_cpuinfo_init(void)
{
static struct proc_dir_entry *entry;
proc_tos = proc_mkdir("tos", 0);
if (!proc_tos) {
printk("tos proc mkdir tos failed!\n");
return -1;
}
monitor_root_dir = proc_mkdir("tos/monitor", NULL);
if(monitor_root_dir == NULL){
printk("tos/monitor proc mkdir tos failed!\n");
if(proc_tos)
remove_proc_entry("tos", NULL);
return -ENOMEM;
}
entry = proc_create("cpu", 0644, monitor_root_dir, &tos_get_cpuinfo_fopes);
if(!entry) {
printk("create_proc_entry error");
if(monitor_root_dir)
remove_proc_entry("tos/monitor", NULL);
if(proc_tos)
remove_proc_entry("tos", NULL);
return -1;
}
return 0;
}
static void __exit tos_get_cpuinfo_exit(void)
{
remove_proc_entry("cpu", monitor_root_dir);
if(monitor_root_dir)
remove_proc_entry("tos/monitor", NULL);
if(proc_tos)
remove_proc_entry("tos", NULL);
}
MODULE_LICENSE("GPL");
module_init(tos_get_cpuinfo_init);
module_exit(tos_get_cpuinfo_exit);
复杂的示例可阅读内核源码 fib_trie.c,查看路由的 proc 文件的实现,其中遍历路由树时使用的自己的迭代
器,迭代器在 seq->private 记录。