Linux Kernel 序列文件(seq_file)接口实战

  • Post author:
  • Post category:linux




1.PROC文件系统简介

使用 /proc 文件系统来访问 Linux 内核的内容,这个虚拟文件系统在内核空间和用户空间之间打开了一个通信窗口:

/proc 文件系统是一个虚拟文件系统,通过它可以使用一种新的方法在 Linux内核空间和用户间之间进行通信。在/proc 文件系统中,我们可以将对虚拟文件的读写作为与内核中实体进行通信的一种手段,但是与普通文件不同的是,这些虚拟文件的内容都是动态创建的。本文对/proc 虚拟文件系统进行了介绍,并展示了它的用法。

最初开发

/proc 文件系统是为了提供有关系统中进程的信息。但是由于这个文件系统非常有用,因此内核中的很多元素也开始使用它来报告信息,或启用动态运行时配置。图一是对 /proc 中部分元素进行一次交互查询的结果。它显示的是 /proc 文件系统的根目录中的内容。注意,在左边是一系列数字编号的文件。每个实际上都是一个目录,表示系统中的一个进程。由于在 GNU/Linux 中创建的第一个进程是 init 进程,因此它的 process-id 为 1。然后对这个目录执行一个 ls 命令,这会显示很多文件。每个文件都提供了有关这个特殊进程的详细信息。/proc

中另外一些有趣的文件有:cpuinfo,它标识了处理器的类型和速度;pci,显示在

PCI 总线上找到的设备;modules,标识了当前加载到内核中的模块。

另外,我们还可以使用 sysctl 来配置这些内核条目。/proc 文件系统并不是 GNU/Linux 系统中的惟一一个虚拟文件系统。在这种系统上,sysfs 是一个与 /proc 类似的文件系统,但是它的组织更好(从 /proc 中学习了很多教训)。不过 /proc 已经确立了自己的地位,因此即使 sysfs 与 /proc 相比有一些优点,/proc 也依然会存在。还有一个 debugfs 文件系统,不过(顾名思义)它提供的更多是调试接口。debugfs 的一个优点是它将一个值导出给用户空间非常简单(实际上这不过是一个调用而已)。

process

图一

这些文件的解释和意义如下:

cmdline:系统启动时输入给内核命令行参数

cpuinfo:CPU的硬件信息 (型号, 家族, 缓存大小等)

devices:主设备号及设备组的列表,当前加载的各种设备(块设备/字符设备)

dma:使用的DMA通道

filesystems:当前内核支持的文件系统,当没有给 mount(1) 指明哪个文件系统的时候, mount(1) 就依靠该文件遍历不同的文件系统

interrupts :中断的使用及触发次数,调试中断时很有用

ioports I/O:当前在用的已注册 I/O 端口范围

kcore:该伪文件以 core 文件格式给出了系统的物理内存映象(比较有用),可以用 GDB 查探当前内核的任意数据结构。该文件的总长度是物理内存 (RAM) 的大小再加上 4KB

kmsg:可以用该文件取代系统调用 syslog(2) 来记录内核日志信息,对应dmesg命令

kallsym:内核符号表,该文件保存了内核输出的符号定义, modules(X)使用该文件动态地连接和捆绑可装载的模块

loadavg:负载均衡,平均负载数给出了在过去的 1、 5,、15 分钟里在运行队列里的任务数、总作业数以及正在运行的作业总数。

locks:内核锁 。

meminfo物理内存、交换空间等的信息,系统内存占用情况,对应df命令。

misc:杂项 。

modules:已经加载的模块列表,对应lsmod命令 。

mounts:已加载的文件系统的列表,对应mount命令,无参数。

partitions:系统识别的分区表 。

slabinfo:sla池信息。

stat:全面统计状态表,CPU内存的利用率等都是从这里提取数据。对应ps命令。

swaps:对换空间的利用情况。

version:指明了当前正在运行的内核版本。

可加载内核模块(LKM)是用来展示 /proc 文件系统的一种简单方法,这是因为这是一种用来动态地向 Linux 内核添加或删除代码的新方法。LKM 也是 Linux 内核中为设备驱动程序和文件系统使用的一种流行机制。如果你曾经重新编译过 Linux 内核,就可能会发现在内核的配置过程中,有很多设备驱动程序和其他内核元素都被编译成了模块。如果一个驱动程序被直接编译到了内核中,那么即使这个驱动程序没有运行,它的代码和静态数据也会占据一部分空间。但是如果这个驱动程序被编译成一个模块,就只有在需要内存并将其加载到内核时才会真正占用内存空间



2.seq_file



2.1简介

UNIX的世界里,文件是最普通的概念,所以用文件来作为内核和用户空间传递数据的接口也是再普通不过的事情,并且这样的接口对于shell也是相当友好的,方便管理员通过shell直接管理系统。由于伪文件系统proc文件系统在处理大数据结构(大于一页的数据)方面有比较大的局限性,使得在那种情况下进行编程特别别扭,很容易导致bug,所以序列文件接口被发明出来,它提供了更加友好的接口,以方便程序员。之所以选择序列文件接口这个名字,应该是因为它主要用来导出一条条的记录数据。

Seq_file File System

针对proc文件的不足而诞生了Seq_file。

Seq_file的实现基于proc文件。使用Seq_file,用户必须抽象出一个链接对象,然后可以依次遍历这个链接对象。这个链接对象可以是链表,数组,哈希表等等。

编程接口

Seq_file必须实现四个操作函数:start(), next(), show(), stop()。

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);
};

start():
主要实现初始化工作,在遍历一个链接对象开始时,调用。返回一个链接对象的偏移或者SEQ_START_TOKEN(表征这是所有循环的开始)。出错返回ERR_PTR。
stop():
当所有链接对象遍历结束时调用。主要完成一些清理工作。
next():
用来在遍历中寻找下一个链接对象。返回下一个链接对象或者NULL(遍历结束)。
show():
对遍历对象进行操作的函数。主要是调用seq_printf(), seq_puts()之类的函数,打印出这个对象节点的信息。
下图描述了seq_file函数对一个链表的遍历。



2.2重要的数据结构

除了struct seq_operations以外,另一个最重要的数据结构是struct seq_file:

struct seq_file {


char *buf;

size_t size;

size_t from;

size_t count;

loff_t index;

u64 version;

struct mutex lock;

const struct seq_operations *op;

void *private;

};

该结构会在seq_open函数调用中分配,然后作为参数传递给每个seq_file的操作函数。Privat变量可以用来在各个操作函数之间传递参数。



3.代码实例



3.1实例一

/************************************************* 
 使用seq_file接口实现可读写proc文件的例子 
 适用于3.10以后的内核 
 Author: Chaos 
 Date: 2015-5-17  
 *************************************************/  
#include <linux/module.h>  
#include <linux/sched.h>  
#include <linux/uaccess.h>  
#include <linux/proc_fs.h>  
#include <linux/fs.h>  
#include <linux/seq_file.h>  
#include <linux/slab.h>  
  
static char *str = NULL;  
  
/*5,实现show函数 
  作用是将内核数据输出到用户空间 
  将在proc file输出时被调用*/  
static int my_proc_show(struct seq_file *m, void *v)  
{  
    /*这里不能使用printfk之类的函数 
      要使用seq_file输出的一组特殊函数 
      详见ldd3的91页*/  
    seq_printf(m, "current kernel time is %ld\n", jiffies);  
    seq_printf(m, "str is %s\n", str);  
    return 0;  
}  
  
  
  
/*3,实现open和write函数*/  
static ssize_t my_proc_write(struct file *file, const char __user *buffer,  
                             size_t count, loff_t *f_pos)  
{  
    char *tmp = kzalloc((count+1), GFP_KERNEL);  
    if(!tmp)  
        return -ENOMEM;  
    if(copy_from_user(tmp, buffer, count))  
    {  
        kfree(tmp);  
        return EFAULT;  
    }  
    kfree(str);  
    str = tmp;  
    return count;  
}  
  
static int my_proc_open(struct inode *inode, struct file *file)  
{  
    /*4,在open函数中调用 single_open 绑定 seq_show 函数指针 
      需要说明的是,ldd3中介绍的seq接口用该调用 seq_open 函数 
      其调用形式如下: 
      return sep_open(file, &scull_seq_ops); 
      scull_seq_ops为struct seq_operations结构体 
      在该结构体中绑定show函数指针 
      需要准备seq_operations结构体 
      而调用 single_open 函数只需直接指定show的函数指针即可 
      个人猜测可能是在single_open函数中实现了seq_operations结构体 
      至于是不是就不知道了,没有查看具体实现 
      有兴趣的同学可以参考文档:Documentation\filesystems\seq_file.txt 
      关于第三个参数,其类型应为viod*, 
      内核中有些地方传入的NULL,有些地方传入的inode->i_private,也有传入其他值的 
      来看看data在single_open函数中如何被使用的: 
        if (!res) 
         ((struct seq_file *)file->private_data)->private = data; 
      data是seq_file结构体的private成员。 
      那么data如何真正被使用的呢? 
      发现show函数的第一个参数为seq_file类型,在show函数中, 
      可以将seq_file的private成员转换成对应的类型进行使用。 
      也就是说,可以通过seq_file的private成员将data参数传递到show函数中*/  
    return single_open(file, my_proc_show, NULL);  
}  
  
/*2,填充proc_create函数中调用的flie_operations结构体 
  其中my开头的函数为自己实现的函数, 
  seq和single开头为内核实现好的函数,直接填充上就行 
  open为必须填充函数 */  
static struct file_operations my_fops = {  
    .owner   = THIS_MODULE,  
    .open    = my_proc_open,  
    .release = single_release,  
    .read    = seq_read,  
    .llseek  = seq_lseek,  
    .write   = my_proc_write,  
};  
  
static int __init my_init(void)  
{  
    struct proc_dri_entry *file;  
    /*3.10以后内核的proc文件的新接口需要关联file_operations*/ 
        
    /*1,首先要调用创建proc文件的函数,需要绑定flie_operations*/  
    file = proc_create("abc_proc", 0644, NULL, &my_fops);  
    if(!file)  
        return -ENOMEM;  
    return 0;  
}  
  
/*6,删除proc文件*/  
static void __exit my_exit(void)  
{  
    remove_proc_entry("abc_proc", NULL);  
    kfree(str);  
}  
  
module_init(my_init);  
module_exit(my_exit);  
MODULE_LICENSE("GPL");  
MODULE_AUTHOR("Chaos");  

测试方法:cat /proc/abc_proc

          echo 任意字符串 >/proc/abc_pro(需root权限)



3.2实例二

备注:实际项目中底层提供一个接口给上层使用,通过ADB指令可以查询camera中由于环境亮暗差异底层上报的数据

static int chaos_show_iso(struct seq_file *seq, void *v)
{
	kal_uint8 sub_cam_data=fake_cam_sensor_data();
	kal_uint8 main_cam_data=main_cam_sensor_data();
	char str[50];
	sprintf(str, "sub_cam=%d,main_cam=%d,",sub_cam_data,main_cam_data);
	printk("chaos_show_iso in,sub_cam_data=%d,main_cam_data=%d,upload_string=%s\n",sub_cam_data,main_cam_data,str);
	seq_puts(seq, str);
	return 0; 	   
}
static ssize_t chaos_write_iso(struct file *file, const char __user *buffer,
        size_t count, loff_t *pos)
{
	printk("chaos_write_iso in,count=%d\n",count);
	return count;
}

static int chaos_proc_open(struct inode *inode, struct file *file)
{
	printk("chaos_proc_open in,\n");
	return single_open(file, chaos_show_iso, inode->i_private);
}

static const struct file_operations subcamera_proc_fops = {
 .open	= chaos_proc_open,
 .read	= seq_read,
 .write  = chaos_write_iso,
 .llseek  = seq_lseek,
 .release = single_release,
};

static int __init CAMERA_chaos_init(void)
{

	printk("[CAMERA_chaos_init] start\n");
	proc_create("driver/sub_camsensor", 0, NULL, &subcamera_proc_fops);

	return 0;
}

static void __exit CAMERA_chaos_exit(void)
{
}

module_init(CAMERA_chaos_init);
module_exit(CAMERA_chaos_exit);

MODULE_DESCRIPTION("Chaos,get camera iso");
MODULE_AUTHOR("Chaos");
MODULE_LICENSE("GPL");



版权声明:本文为weixin_39280491原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。