Proc文件系统


        Proc是用于与内核内部数据结构接口的伪文件系统。作为用户,您可以使用proc文件进行系统诊断-CPU,内存,中断等。您还可以配置许多参数,例如调度程序参数,内核对象,内存等

与proc的常见交互是使用cat和shell中的echo。

例如:

# cat /proc/cpuinfo
# echo "50"> /proc/sys/kernel/sched_rr_timeslice_ms

创建一个新的Proc文件(例子1)


要创建proc文件系统,我们需要实现一个简单的接口– file_operation

我们可以实现20多个功能,但常见的操作是读取,写入。要注册接口,请使用proc_create函数

基本结构是:

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/kernel.h>   
#include <linux/proc_fs.h>
#include <asm/uaccess.h>
#define BUFSIZE  100
 
 
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Liran B.H");
 
 
static struct proc_dir_entry *ent;
 
static ssize_t mywrite(struct file *file, const char __user *ubuf,size_t count, loff_t *ppos) 
{
	printk( KERN_DEBUG "write handler\n");
	return -1;
}
 
static ssize_t myread(struct file *file, char __user *ubuf,size_t count, loff_t *ppos) 
{
	printk( KERN_DEBUG "read handler\n");
	return 0;
}
 
static struct file_operations myops = 
{
	.owner = THIS_MODULE,
	.read = myread,
	.write = mywrite,
};
 
static int simple_init(void)
{
	ent=proc_create("mydev",0660,NULL,&myops);
	return 0;
}
 
static void simple_cleanup(void)
{
	proc_remove(ent);
}
 
module_init(simple_init);
module_exit(simple_cleanup);

 如果构建并插入模块,将看到一个新文件/ proc / mydev,可以使用cat和echo测试读写操作(仅查看内核日志消息)

# echo "test" > /proc/mydev
bash: echo: write error: Operation not permitted
 
# cat /proc/mydev
# dmesg | tail -2
[  694.640306] write handler
[  714.661465] read handler

实现读取处理程序


读取处理程序接收4个参数:

  • 文件对象–具有打开的文件详细信息(权限,位置等)的每个过程结构
  • 用户空间缓冲区
  • 缓冲区大小
  • 要求的位置(输入和输出参数)

要实现read回调,我们需要:

  • 检查要求的位置
  • 从请求的位置向用户缓冲区填充数据(最大大小<=缓冲区大小)
  • 返回我们填充的字节数。

例如,用户运行以下代码:

int fd = open("/proc/mydev", O_RDWR);
 
len = read(fd,buf,100);
len = read(fd,buf,50);

在第一次调用read时,我们得到用户缓冲区,大小= 100,po​​sition = 0,我们需要从位置0开始最多填充100个字节的缓冲区,更新该位置并返回我们写入的字节数。如果我们用100个字节填充缓冲区,并在下一次读取调用时返回100,则得到用户缓冲区,大小为50,位置为100

假设我们有2个模块参数,并且我们想在proc读取处理程序中返回它们的值,我们编写以下代码:

static int irq=20;
module_param(irq,int,0660);
 
static int mode=1;
module_param(mode,int,0660);
 
 
static ssize_t myread(struct file *file, char __user *ubuf,size_t count, loff_t *ppos) 
{
	char buf[BUFSIZE];
	int len=0;
	printk( KERN_DEBUG "read handler\n");
	if(*ppos > 0 || count < BUFSIZE)
		return 0;
	len += sprintf(buf,"irq = %d\n",irq);
	len += sprintf(buf + len,"mode = %d\n",mode);
	
	if(copy_to_user(ubuf,buf,len))
		return -EFAULT;
	*ppos = len;
	return len;
}

这是一个简单的实现,我们检查这是否是第一次调用read(pos = 0),并且用户缓冲区大小大于BUFSIZE,否则返回0(文件末尾)

然后,我们构建返回的缓冲区,将其复制到用户,更新位置并返回我们编写的数字

构建并插入模块,可以使用cat命令对其进行测试:

# sudo insmod ./simproc.ko irq=32 mode=4
# cat /proc/mydev 
irq = 32
mode = 4

与用户空间交换数据


在内核代码中,不能仅在用户空间提供的地址与内核空间中的缓冲区的地址之间使用memcpy

  • 对应于完全不同的地址空间(由于虚拟内存)。
  • 用户空间地址可以换出到磁盘。
  • 用户空间地址可能无效(用户空间进程试图访问未经授权的数据)。

您必须在读写文件操作代码中使用专用功能:

#include <asm/uaccess.h>
unsigned long copy_to_user(void __user *to,const void *from, unsigned long n);
unsigned long copy_from_user(void *to,const void __user *from,unsigned long n);

实现写处理程序


写入处理程序类似于读取处理程序。唯一的区别是用户缓冲区类型是const char指针。我们需要将数据从用户缓冲区复制到请求的位置,并返回复制的字节数

在此示例中,我们想使用一个简单的命令设置两个值:

# echo "32 6" > /proc/mydev

第一个值为irq编号,第二个为模式。

写入处理程序的代码:

static ssize_t mywrite(struct file *file, const char __user *ubuf,size_t count, loff_t *ppos) 
{
	int num,c,i,m;
	char buf[BUFSIZE];
	if(*ppos > 0 || count > BUFSIZE)
		return -EFAULT;
	if(copy_from_user(buf,ubuf,count))
		return -EFAULT;
	num = sscanf(buf,"%d %d",&i,&m);
	if(num != 2)
		return -EFAULT;
	irq = i; 
	mode = m;
	c = strlen(buf);
	*ppos = c;
	return c;
}

再次,我们检查这是否是首次调用write(位置0),然后使用copy_from_user将数据从用户地址空间转移到内核地址空间。我们提取值,检查错误,更新位置并返回收到的字节数

完整的模块代码:

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/kernel.h>   
#include <linux/proc_fs.h>
#include <asm/uaccess.h>
#define BUFSIZE  100
 
 
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Liran B.H");
 
static int irq=20;
module_param(irq,int,0660);
 
static int mode=1;
module_param(mode,int,0660);
 
static struct proc_dir_entry *ent;
 
static ssize_t mywrite(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos) 
{
	int num,c,i,m;
	char buf[BUFSIZE];
	if(*ppos > 0 || count > BUFSIZE)
		return -EFAULT;
	if(copy_from_user(buf, ubuf, count))
		return -EFAULT;
	num = sscanf(buf,"%d %d",&i,&m);
	if(num != 2)
		return -EFAULT;
	irq = i; 
	mode = m;
	c = strlen(buf);
	*ppos = c;
	return c;
}
 
static ssize_t myread(struct file *file, char __user *ubuf,size_t count, loff_t *ppos) 
{
	char buf[BUFSIZE];
	int len=0;
	if(*ppos > 0 || count < BUFSIZE)
		return 0;
	len += sprintf(buf,"irq = %d\n",irq);
	len += sprintf(buf + len,"mode = %d\n",mode);
	
	if(copy_to_user(ubuf,buf,len))
		return -EFAULT;
	*ppos = len;
	return len;
}
 
static struct file_operations myops = 
{
	.owner = THIS_MODULE,
	.read = myread,
	.write = mywrite,
};
 
static int simple_init(void)
{
	ent=proc_create("mydev",0660,NULL,&myops);
	printk(KERN_ALERT "hello...\n");
	return 0;
}
 
static void simple_cleanup(void)
{
	proc_remove(ent);
	printk(KERN_WARNING "bye ...\n");
}
 
module_init(simple_init);
module_exit(simple_cleanup);

注意:要实现更复杂的proc条目,请使用 seq_file包装器

用户空间应用


您可以打开文件并使用读/写功能来测试模块。每次操作后,请不要忘记将位置基准移至0:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
 
void main(void)
{
	char buf[100];
	int fd = open("/proc/mydev", O_RDWR);
	read(fd, buf, 100);
	puts(buf);
 
	lseek(fd, 0 , SEEK_SET);
	write(fd, "33 4", 5);
	
	lseek(fd, 0 , SEEK_SET);
	read(fd, buf, 100);
	puts(buf);
}	

创建一个新的Proc文件(例子2)


#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
#include <linux/module.h>
 
#if 1
    #define chao_debug(x, ...)         printk(KERN_ALERT x, ##_VARGS_)
#else
    #define chao_debug(x, ...)         do{ } while(0)
#endif
 
 
struct proc_dir_entry *chao_proc_file = NULL;
struct proc_dir_entry *chao_proc_file = NULL;
 
#define MEM_SIZE                        100
static char mem[MEM_SIZE];
 
static ssize_t chao_file_read(struct file *flip, char *buf, size_t count, loff_t *pos)
{
    int ret = 0;
    
    chao_debug("qyc, count == %d, ppos = %d", count, *pos);
    ret = simple_read_from_buffer(buf, count, pos, mem, sizoef(mem));
 
    chao_debug("qyc -- %s--%s\n", __func__, mem);
 
    return ret;
    
}
 
static ssize_t chao_file_write(struct file* flip, const char *buf, size_t len, loff_t *pos)
{
    int ret = 0;
 
    if (len > MEM_SIZE)
        len = MEM_SIZE;
 
    chao_debug("chao_file_write len == %d\n", len);
 
    if(copy_from_user(mem, buf, len))
        return -EFAULT;
    else
        return len;
 
    chao_debug("qyc, ---%s---%s\n", __func__, mem);
 
    return ret;
}
 
 
static struct file_operations chao_file_ops = {
    
    .owner = THIS_MODULE,
    .read = chao_file_read,
    .write = chao_file_write,
};
 
 
/*
 * proc/android_chao/chao_file
 *
 */
#define CHAO_PROC_DIE                "android_chao"
#define CHAO_PROC_FILE                "chao_file"
 
static int __init proc_demo_init()
{
    memset(mem, 0, MEM_SIZE);
 
    chao_proc_file = proc_mkdir(CHAO_PROC_DIR, NULL);
    if (chao_proc_file == NULL) {
        chao_debug("chao proc dir create failed, %s\n", __func__);
        goto err_dir;
    }
 
    chao_proc_file = proc_create(CHAO_PROC_FILE, S_IWUSR|S_IRUSR, chao_proc_dir, &chao_file_ops);
 
    if(chao_proc_file == NULL) {
        chao_debug("%s, proc file crate failed !\n", __func__);
        goto err_file;
    }
 
err_dir:
    remove_proc_entry(CHAO_PROC_DIR, chao_proc_dir);
err_file:
    remove_proc_entry(CHAO_PROC_FILE, chao_proc_file);
}
 
 
static void __exit proc_demo_exit()
{
    if (chao_pric_dir == NULL)
        remove_proc_entry(CHAO_PROC_DIR, chao_proc_dir);;
    if(chao_proc_file == NULL)
        remove_proc_entry(CHAO_PROC_FILE, chao_proc_file);
}
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR("qyichao");
module_init(proc_demo_init);
modulle_exit(proc_demo_exit);

创建proc文件的三种方法(例子3)


大多数/proc下的文件是只读的,但为了示例的完整性,都提供了写方法。

方法一:使用 create_proc_entry 创建 proc 文件(简单,但写操作有缓冲区溢出的危险);

方法二:使用 proc_create 和 seq_file 创建 proc 文件(较方法三简洁);

方法三:使用 proc_create_data 和 seq_file 创建 proc 文件(较麻烦,但比较完整);

示例四:在 proc 文件中使用内核链表的一个示例(用的方法三)。

方法一、

proc_test01.c 源码

#include <linux/module.h>
#include <linux/sched.h> //jiffies
#include <linux/proc_fs.h>
#include <linux/uaccess.h> //copy_to|from_user()
 
static char *str = NULL;
 
//proc文件的读函数
static int my_proc_read(char *page, char **start, off_t off, int count, int *eof, void *data)
{
	int ret = 0;
	ret = sprintf(page, "current kernel time is %ld\n", jiffies);
	ret += sprintf(page+ret, "str is %s\n", str);
 
	return ret;
}
 
//proc文件的写函数
static int my_proc_write(struct file *filp, const char __user *buf, unsigned long count, void *data)
{
	//分配临时缓冲区
	char *tmp = kzalloc((count+1), GFP_KERNEL);
	if (!tmp)
		return -ENOMEM;
 
	//将用户态write的字符串拷贝到内核空间
	//copy_to|from_user(to,from,cnt)
	if (copy_from_user(tmp, buf, count)) {
		kfree(tmp);
		return -EFAULT;
	}
 
	//将str的旧空间释放,然后将tmp赋值给str
	kfree(str);
	str = tmp;
 
	return count;
}
 
 
static int __init my_init(void)
{
	struct proc_dir_entry *file;
 
	//创建proc文件
	file = create_proc_entry("jif", 0666, NULL);
	if (!file) {
		printk("Cannot create /proc/jif\n");
		return -1;
	}
 
	//将创建好的文件和读写函数关联在一起
	file->read_proc = my_proc_read;
	file->write_proc = my_proc_write;
 
	return 0;
}
 
static void __exit my_exit(void)
{
	//删除proc文件
	remove_proc_entry("jif", NULL);
	kfree(str);
}
 
module_init(my_init);
module_exit(my_exit);
MODULE_AUTHOR("aran");
MODULE_LICENSE("GPL");

方法二、

proc_test02.c 源码

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
 
#include <linux/fs.h>		// for basic filesystem
#include <linux/proc_fs.h>	// for the proc filesystem
#include <linux/seq_file.h>	// for sequence files
#include <linux/jiffies.h>	// for jiffies
#include <linux/slab.h>		// for kzalloc, kfree
#include <linux/uaccess.h>	// for copy_from_user
 
// global var
static char *str = NULL;
 
// seq_operations -> show
static int jif_show(struct seq_file *m, void *v)
{
	seq_printf(m, "current kernel time is %llu\n", (unsigned long long) get_jiffies_64());
	seq_printf(m, "str is %s\n", str);
 
	return 0; //!! must be 0, or will show nothing T.T
}
 
// file_operations -> write
static ssize_t jif_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;
 
	//将用户态write的字符串拷贝到内核空间
	//copy_to|from_user(to,from,cnt)
	if (copy_from_user(tmp, buffer, count)) {
		kfree(tmp);
		return -EFAULT;
	}
 
	//将str的旧空间释放,然后将tmp赋值给str
	kfree(str);
	str = tmp;
 
	return count;
}
 
// seq_operations -> open
static int jif_open(struct inode *inode, struct file *file)
{
	return single_open(file, jif_show, NULL);
}
 
static const struct file_operations jif_fops = 
{
	.owner		= THIS_MODULE,
	.open		= jif_open,
	.read		= seq_read,
	.write 		= jif_write,
	.llseek		= seq_lseek,
	.release	= single_release,
};
 
// module init
static int __init jif_init(void)
{
	struct proc_dir_entry* jif_file;
 
	jif_file = proc_create("jif", 0, NULL, &jif_fops);
	if (NULL == jif_file)
	{
	    return -ENOMEM;
	}
 
	return 0;
}
 
// module exit
static void __exit jif_exit(void)
{
	remove_proc_entry("jif", NULL);
	kfree(str);
}
 
module_init(jif_init);
module_exit(jif_exit);
 
MODULE_AUTHOR("aran");
MODULE_LICENSE("GPL");

方法三、

proc_test03.c 源码

#include <linux/module.h>
#include <linux/sched.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
 
// global var
static char *str = NULL;
 
// linux/seq_file.h
// 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);
 
/**
* author:  aran
* fuction: seq_operations -> start
*/
static void *my_seq_start(struct seq_file *m, loff_t *pos)
{
	if (0 == *pos)
	{
		++*pos;
		return (void *)1; // return anything but NULL, just for test
	}
	return NULL;
}
 
/**
* author:  aran
* fuction: seq_operations -> next
*/
static void *my_seq_next(struct seq_file *m, void *v, loff_t *pos)
{
	// only once, so no next
	return NULL;
}
 
/**
* author:  aran
* fuction: seq_operations -> stop
*/
static void my_seq_stop(struct seq_file *m, void *v)
{
	// clean sth.
	// nothing to do
}
 
/**
* author:  aran
* fuction: seq_operations -> show
*/
static int my_seq_show(struct seq_file *m, void *v)
{
	seq_printf(m, "current kernel time is %llu\n", (unsigned long long) get_jiffies_64());
	seq_printf(m, "str is %s\n", str);
 
	return 0; //!! must be 0, or will show nothing T.T
}
 
// global var
static struct seq_operations my_seq_fops = 
{
	.start	= my_seq_start,
	.next	= my_seq_next,
	.stop	= my_seq_stop,
	.show	= my_seq_show,
};
 
// file_operations
// int (*open) (struct inode *, struct file *)
// ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *)
 
/**
* author:  aran
* fuction: file_operations -> open
*/
static int proc_seq_open(struct inode *inode, struct file *file)
{
	return seq_open(file, &my_seq_fops);
}
 
/**
* author:  aran
* fuction: file_operations -> write
*/
static ssize_t proc_seq_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;
 
	//将用户态write的字符串拷贝到内核空间
	//copy_to|from_user(to,from,cnt)
	if (copy_from_user(tmp, buffer, count)) {
		kfree(tmp);
		return -EFAULT;
	}
 
	//将str的旧空间释放,然后将tmp赋值给str
	kfree(str);
	str = tmp;
 
	return count;
}
 
// global var
static struct file_operations proc_seq_fops = 
{
	.owner		= THIS_MODULE,
	.open		= proc_seq_open,
	.read		= seq_read,
	.write		= proc_seq_write,
	.llseek		= seq_lseek,
	.release	= seq_release,
};
 
static int __init my_init(void)
{
	struct proc_dir_entry *file;
 
	// create "/proc/proc_seq" file
	file = proc_create_data(
		"jif",		// name
		0666,		// mode
		NULL,		// parent dir_entry
		&proc_seq_fops,	// file_operations
		NULL		// data
		);
	if (NULL == file)
	{
		printk("Count not create /proc/jif file!\n");
		return -ENOMEM;
	}
 
	return 0;
}
 
static void __exit my_exit(void)
{
	remove_proc_entry("jif", NULL);
	kfree(str);
}
 
module_init(my_init);
module_exit(my_exit);
 
MODULE_AUTHOR("aran");
MODULE_LICENSE("GPL");

示例四(方法三)、

proc_test04.c 源码

#include <linux/module.h>
#include <linux/sched.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
 
struct proc_head
{
	struct list_head lhead;
	int item_count;
	int str_count;
};
 
struct proc_item
{
	struct list_head litem;
	char *buf;
	int num;
};
 
struct proc_head *gp_head;
 
// linux/seq_file.h
// 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);
 
/**
* author:  aran
* fuction: seq_operations -> start
*/
static void *my_seq_start(struct seq_file *m, loff_t *pos)
{
	struct proc_item *entry;
 
	if (0 == *pos)
	{
		seq_printf(m, "List has %d items, total %d bytes\n", gp_head->item_count, gp_head->str_count);
	}
 
	// get first item
	++*pos;
	list_for_each_entry(entry, &gp_head->lhead, litem)
	{
		if (*pos == entry->num)
		{
			return entry;
		}
	}
	return NULL;
}
 
/**
* author:  aran
* fuction: seq_operations -> next
*/
static void *my_seq_next(struct seq_file *m, void *v, loff_t *pos)
{
	struct proc_item *entry;
 
	// get next item
	++*pos;
	list_for_each_entry(entry, &gp_head->lhead, litem)
	{
		if (*pos == entry->num)
		{
			return entry;
		}
	}
	return NULL;
}
 
/**
* author:  aran
* fuction: seq_operations -> stop
*/
static void my_seq_stop(struct seq_file *m, void *v)
{
	// clean sth.
	// nothing to do
}
 
/**
* author:  aran
* fuction: seq_operations -> show
*/
static int my_seq_show(struct seq_file *m, void *v)
{
	struct proc_item *tmp = v;
	seq_printf(m, "%s", tmp->buf);
 
	return 0;
}
 
// global var
static struct seq_operations my_seq_fops = 
{
	.start	= my_seq_start,
	.next	= my_seq_next,
	.stop	= my_seq_stop,
	.show	= my_seq_show,
};
 
// file_operations
// int (*open) (struct inode *, struct file *)
// ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *)
 
/**
* author:  aran
* fuction: file_operations -> open
*/
static int proc_seq_open(struct inode *inode, struct file *file)
{
	return seq_open(file, &my_seq_fops);
}
 
/**
* author:  aran
* fuction: file_operations -> write
*/
static ssize_t proc_seq_write(struct file *file, const char __user *buffer, size_t count, loff_t *f_pos)
{
	struct proc_item *tmp;
	int ret;
 
	// allocate proc_item
	tmp = (struct proc_item *)kzalloc(sizeof(*tmp), GFP_KERNEL);
	if (NULL == tmp)
	{
		ret = -ENOMEM;
		goto err_kzalloc1;
	}
	INIT_LIST_HEAD(&tmp->litem);
 
	// allocate str buf
	tmp->buf = (char *)kzalloc(count, GFP_KERNEL);
	if (NULL == tmp->buf)
	{
		ret = -ENOMEM;
		goto err_kzalloc2;
	}
 
	if (0 != copy_from_user(tmp->buf, buffer, count))
	{
		ret = -1;
		goto err_copy;
	}
 
	list_add(&tmp->litem, &gp_head->lhead);
	gp_head->item_count++;
	gp_head->str_count += count;
	tmp->num = gp_head->item_count; 
 
	return count;
 
err_copy:
	kfree(tmp->buf);
err_kzalloc2:
	kfree(tmp);
err_kzalloc1:
	return ret;
}
 
// global var
static struct file_operations proc_seq_fops = 
{
	.owner		= THIS_MODULE,
	.open		= proc_seq_open,
	.read		= seq_read,
	.write		= proc_seq_write,
	.llseek		= seq_lseek,
	.release	= seq_release,
};
 
static int __init my_init(void)
{
	struct proc_dir_entry *file;
	int ret;
 
	// allocate & init proc_head
	gp_head = (struct proc_head *)kzalloc(sizeof(*gp_head), GFP_KERNEL);
	if (NULL == gp_head)
	{
		ret = -ENOMEM;
		goto err_kzalloc;
	}
	gp_head->item_count = 0;
	gp_head->str_count = 0;
	INIT_LIST_HEAD(&gp_head->lhead);
 
	// create "/proc/proc_seq" file
	file = proc_create_data(
		"proc_seq",	// name
		0666,		// mode
		NULL,		// parent dir_entry
		&proc_seq_fops,	// file_operations
		NULL		// data
		);
	if (NULL == file)
	{
		printk("Count not create /proc/proc_seq file!\n");
		ret = -1;
		goto err_proc_create_data;
	}
 
	return 0;
 
err_proc_create_data:
	kfree(gp_head);
err_kzalloc:
	return ret;
}
 
static void __exit my_exit(void)
{
	struct proc_item *tmp1, *tmp2;
 
	remove_proc_entry("proc_seq", NULL);
	list_for_each_entry_safe(tmp1, tmp2, &gp_head->lhead,litem)
	{
		list_del(&tmp1->litem);
		kfree(tmp1->buf);
		kfree(tmp1);
	}
	kfree(gp_head);
}
 
module_init(my_init);
module_exit(my_exit);
 
MODULE_AUTHOR("aran");
MODULE_LICENSE("GPL");

创建proc文件(例子4)


        在定义了结构struct seq_operations之后,用户还需要把打开seq_file文件的open函数,以便该结构与对应于seq_file文件的struct file结构关联起来,例如,struct seq_operations定义为:

  struct seq_operations exam_seq_ops = {
     .start = exam_seq_start,
     .stop = exam_seq_stop,
     .next = exam_seq_next,
     .show = exam_seq_show
  };

那么,open函数应该如下定义:

 static int exam_seq_open(struct inode *inode, struct file *file)
  {
          return seq_open(file, &exam_seq_ops);
  };

注意,函数seq_open是seq_file提供的函数,它用于把struct seq_operations结构与seq_file文件关联起来。
最后,用户需要如下设置struct file_operations结构: 

 struct  file_operations exam_seq_file_ops = {
          .owner   = THIS_MODULE,
          .open    = exm_seq_open,
          .read    = seq_read,
          .llseek  = seq_lseek,
          .release = seq_release
  };

注意,用户仅需要设置open函数,其它的都是seq_file提供的函数。
然后,用户创建一个/proc文件并把它的文件操作设置为exam_seq_file_ops即可:

 struct proc_dir_entry *entry;
 entry = create_proc_entry("exam_seq_file", 0, NULL);
 if (entry)
 entry->proc_fops = &exam_seq_file_ops;

        对于简单的输出,seq_file用户并不需要定义和设置这么多函数与结构,它仅需定义一个show函数,然后使用single_open来定义open函数就可以,以下是使用这种简单形式的一般步骤:
1.定义一个show函数

int exam_show(struct seq_file *p, void *v)
{
  …
}

2. 定义open函数

int  exam_single_open(struct inode *inode, struct file *file)
{
    return(single_open(file, exam_show,  NULL));
}  

注意要使用single_open而不是seq_open。
3. 定义struct file_operations结构  

  struct file_operations exam_single_seq_file_operations = {
          .open           = exam_single_open,
          .read           = seq_read,
          .llseek         = seq_lseek,
          .release        = single_release,
  };

注意,如果open函数使用了single_open,release函数必须为single_release,而不是seq_release。 下面给出了一个使用seq_file的具体例子seqfile_exam.c,它使用seq_file提供了一个查看当前系统运行的所有进程的/proc接口,在编译并插入该模块后,用户通过命令"cat /proc/exam_esq_file"可以查看系统的所有进程。

//kernel module: seqfile_exam.c
#include <linux/config.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/percpu.h>
#include <linux/sched.h>

static struct proc_dir_entry *entry;

static void *l_start(struct seq_file *m, loff_t * pos)
{
        loff_t index = *pos;

        if (index == 0) {
                seq_printf(m, "Current all the processes in system:\n"
                           "%-24s%-5s\n", "name", "pid");
                return &init_task;
        }
        else {
                return NULL;
        }
}

static void *l_next(struct seq_file *m, void *p, loff_t * pos)
{
        task_t * task = (task_t *)p;

        task = next_task(task);
        if ((*pos != 0) && (task == &init_task)) {
                return NULL;
        }
        ++*pos;
        return task;
}

static void l_stop(struct seq_file *m, void *p)
{
}

static int l_show(struct seq_file *m, void *p)
{
        task_t * task = (task_t *)p;

        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);
        if (entry)
                entry->proc_fops = &exam_seq_fops;

        return 0;
}

static void __exit exam_seq_exit(void)
{
        remove_proc_entry("exam_esq_file", NULL);
}

module_init(exam_seq_init);
module_exit(exam_seq_exit);
MODULE_LICENSE("GPL");

seq_file接口编程浅析


        由于procfs的默认操作函数只使用一页的缓存,在处理较大的proc文件时就有点麻烦,并且在输出一系列结构体中的数据时也比较不灵活,需要自己在read_proc函数中实现迭代,容易出现Bug。所以内核黑客们对一些/proc代码做了研究,抽象出共性,最终形成了seq_file(Sequence file:序列文件)接口。 这个接口提供了一套简单的函数来解决以上proc接口编程时存在的问题,使得编程更加容易,降低了Bug出现的机会。

         在需要创建一个由一系列数据顺序组合而成的虚拟文件或一个较大的虚拟文件时,推荐使用seq_file接口。但是我个人认为,并不是只有procfs才可以使用这个seq_file接口,因为其实seq_file是实现的是一个操作函数集,这个函数集并不是与proc绑定的,同样可以用在其他的地方。对于一个函数接口层的学习,首先要看一个相关的数据结构struct seq_file:

include/linux/seq_file.h

struct seq_file {
    char *buf;     //seq_file接口使用的缓存页指针
    size_t size;   //seq_file接口使用的缓存页大小
    size_t from;   //从seq_file中向用户态缓冲区拷贝时相对于buf的偏移地址
    size_t count;  //buf中可以拷贝到用户态的字符数目 
    loff_t index;  //start、next的处理的下标pos数值
    loff_t read_pos;  //当前已拷贝到用户态的数据量大小
    u64 version;
    struct mutex lock;  //针对此seq_file操作的互斥锁,所有seq_*的访问都会上锁
    const struct seq_operations *op; //操作实际底层数据的函数
    void *private;
};

        在这个结构体中,几乎所有的成员都是由seq_file内部实现来处理的,程序员不用去关心,除非你要去研究seq_file的内部原理。对于这个结构体,程序员唯一要做的就是实现其中的 const struct seq_operations *op。 为使用 seq_file接口对于不同数据结构体进行访问,你必须创建一组简单的对象迭代操作函数。

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_file文件的读开始位置,返回实际读开始位置,如果指定的位置超过文件末尾,应当返回NULL,start函数可以有一个特殊的返回SEQ_START_TOKEN,它用于让show函数输出文件头,但这只能在pos为0时使用,next函数用于把seq_file文件的当前读位置移动到下一个读位置,返回实际的下一个读位置,如果已经到达文件末尾,返回NULL,stop函数用于在读完seq_file文件后调用,它类似于文件操作close,用于做一些必要的清理,如释放内存等,show函数用于格式化输出,如果成功返回0,否则返回出错码。

        seq_file内部机制使用这些接口函数访问底层的实际数据结构体,并不断沿数据序列向前,同时逐个输出序列里的数据到seq_file自建的缓存(大小为一页)中。也就是说seq_file内部机制帮你实现了对序列数据的读取和放入缓存的机制,你只需要实现底层的迭代函数接口就好了,因为这些是和你要访问的底层数据相关的,而seq_file属于上层抽象。

介绍下struct seq_operations中的各个函数的作用:

void * (*start) (struct seq_file *m, loff_t *pos);
m:指向的是本seq_file的结构体,在正常情况下无需处理。
pos:是一个整型位置值,指示开始读取的位置。对于这个位置的意义完全取决于底层实现,不一定是字节为单位的位置,可能是一个元素的序列号。
返回值如果非NULL,则是一个指向迭代器实现的私有数据结构体指针。如果访问出错则返回NULL。

        start 方法会首先被调用,它的作用是在设置访问的起始点。

        设置好了访问起始点,seq_file内部机制可能会使用show方法获取start返回值指向的结构体中的数据到内部缓存,并适时送往用户空间。

int (*show) (struct seq_file *m, void *v);

        所以show方法就是负责将v指向元素中的数据输出到seq_file的内部缓存,但是其中必须借助seq_file提供的一些类似printf的接口函数:

int seq_printf(struct seq_file *sfile, const char *fmt, ...);   
//专为 seq_file 实现的类似 printf 的函数;用于将数据常用的格式串和附加值参数。你必须将给 show 函数的 set_file 结构指针传递给它。如果seq_printf 返回-1,意味着缓存区已满,部分输出被丢弃。但是大部分时候都忽略了其返回值。

int seq_putc(struct seq_file *sfile, char c);
//函数seq_putc用于把一个字符输出到seq_file文件
int seq_puts(struct seq_file *sfile, const char *s);
//函数seq_puts则用于把一个字符串输出到seq_file文件

int seq_escape(struct seq_file *m, const char *s, const char *esc);
//函数seq_escape类似于seq_puts,只是,它将把第一个字符串参数中出现的包含在第二个字符串参数
中的字符按照八进制形式输出,也即对这些字符进行转义处理(它会将 s 中所有在 esc 中出现的字符以八进制格式输出到缓存。esc 的常用值是"\t\n\\", 它使内嵌的空格不会搞乱输出或迷惑 shell 脚本.)

int seq_write(struct seq_file *seq, const void *data, size_t len) 
//直接将data指向的数据写入seq_file缓存,数据长度为len。用于非字符串数据。

int seq_path(struct seq_file *sfile, struct vfsmount *m, struct dentry *dentry, char *esc);
//这个函数能够用来输出给定目录项关联的文件名,驱动极少使用,它主要供文件系统使用

        在show函数返回之后,seq_file机制可能需要移动到下一个数据元素,那就必须使用next方法。

void * (*next) (struct seq_file *m, void *v, loff_t *pos);
v:是之前调用start或next返回的元素指针,可能是上一个show已经完成输出所指向的元素。
pos:需要移动到的元素索引值。

         在next实现中应当递增pos指向的值,但是具体递增的数量和迭代器的实现有关,不一定是1。而next的返回值如果非NULL,则是下一个需要输出到缓存的元素指针,否则表明已经输出结束,将会调用stop方法做清理。

void (*stop) (struct seq_file *m, void *v);

        在stop实现中,参数m指向本seq_file的结构体,在正常情况下无需处理。而v是指向上一个next或start返回的元素指针。在需要做退出处理的时候才需要实现具体的功能。但是许多情况下可以直接返回。

编程步骤:

        在上面我们介绍了使用 seq_file 需要实现的一些函数和相关结构体,现在我们把她们组合起来,介绍以下通过 proc 来使用 seq_file 的一般步骤,而 seq_file 在其他方面的应用方法也是一样的。

(1)在注册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");
}

 (2)实现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是需要自己实现的。

(3)这个open函数的实现也是非常简单固定格式,实例如下:

static int proc_seq_open(struct inode *inode, struct file *file)
{
     return seq_open(file, &proc_seq_ops);
};

        可以看到,一般就是使用seq_file中的一个API:seq_open,目的是向seq_file结构体中注册一个struct seq_operations 。

(4)实现 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
};

 这些回调函数都是需要根据你所要获取的序列数据结构来实现的。

       其实在实际编程过程应该是相反的:(4)-->(3)-->(2)-->(1),但是为了更好的理解,我就从最后的注册开始,再介绍需要实现的结构体。

      

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐