编写一个简单的proc文件

本文介绍了如何在Linux内核模块中创建一个简单的proc文件,涉及write_proc_t和read_proc_t函数的使用,以及用户空间到内核空间的数据拷贝。通过实现这些接口,可以实现在/proc文件系统下读写数据的功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

proc文件的实现也需要编写出一个内核模块并插入内核。模块的编写过程我们已经Hello,Module过了,而proc文件的实现稍微复杂一些。

在linux内核模块编写中,常常会有一些结构体,结构体中定义了很多函数指针,内核模块的编写常常是围绕着实现这些函数指针而进行的。实现了这些接口便可以调用相应的函数把相应的功能模块插入内核(拗口),以下是proc文件的结构体接口描述:

//linux/proc_fs.h

//读函数原型
typedef	int (read_proc_t)(char *page, char **start, off_t off,
			  int count, int *eof, void *data);

//写函数原型
typedef	int (write_proc_t)(struct file *file, const char __user *buffer,
			   unsigned long count, void *data);

			   
struct proc_dir_entry {
	unsigned int low_ino;
	unsigned short namelen;
	const char *name;	//proc文件的名字
	mode_t mode;
	nlink_t nlink;
	uid_t uid;
	gid_t gid;
	unsigned long size;
	struct inode_operations * proc_iops;
	struct file_operations * proc_fops;
	get_info_t *get_info;
	struct module *owner;
	struct proc_dir_entry *next, *parent, *subdir;
	void *data;
	read_proc_t *read_proc;			//读函数(需用户实现)
	write_proc_t *write_proc;		//写函数(需用户实现)
	atomic_t count;		/* use count */
	int deleted;		/* delete flag */
};

/*创建一个proc结构体
@name: proc文件的名字
@mode:
@parent: proc文件被创建的目录
*/
struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode,struct proc_dir_entry *parent);
/*
从内核中移除一个proc文件
*/
void remove_proc_entry(const char *name, struct proc_dir_entry *parent) ;

已经有一些预定义的proc目录可直接作为parent使用:

proc_dir_entry在文件系统中的位置
proc_root_fs/proc
proc_net/proc/net
proc_bus/proc/bus
proc_root_driver/proc/driver

write_proc_t 函数(写函数):用户空间到内核空间

很明显,write_proc_t函数的实现赋予proc 写语义,它的原型如下:

int (write_proc_t)(struct file *file, const char __user *buffer, unsigned long count, void *data);

参数file可忽略,buffer为用户空间需要写入的数据的头指针,count 为用户空间需要写入内核的数据长度,data为proc结构体得私有数据,对应于 struct  proc_dir_entry结构体中的data字段。编码过程中常常需要将用户空间的数据拷贝到内核,因为用户空间的指针不能在内核空间中使用(那不是很多拷贝,效率不是很低..),常见的拷贝函数有以下两枚

/* Copy buffer to user-space from kernel-space */
unsigned long copy_to_user( void __user *to, const void *from,unsigned long n );
/* Copy buffer to kernel-space from user-space */
unsigned long copy_from_user( void *to,const void __user *from, unsigned long n );


__user宏只是方便源代码查找而已,没有实际语义。这两个copy函数可能会造成阻塞,故只能在进程上下文中使用,如果在中断上下文中使用,那恭喜你,你的内核会莫名的挂掉了。

read_proc_t 函数(读函数):内核空间到用户空间
typedef int (read_proc_t)(char *page, char **start, off_t off, int count, int *eof, void *data);
当某个进程读取我们的proc文件时,内核会分配一个内存页(即PAGE_SIZE大小的内存块),内核模块将数据写入到这张页来返回数据到用户空间。
以上参数中,page参数是为进程分配的内存页,其中count定义了定义了可以写入的最大字符数。在返回多页数据(通常一页是4K),我们需要使用start和off参数。当所有数据全部写入后,就需要设置eof(文件结束参数)。与write类似,data也是私有数据。此处提供的page缓冲区在内核空间。因此不必使用copy_to_user,而可以直接写入。该函数返回写入的字节数。

start参数:start参数用户实现返回大于一页的数据。在read_proc_t方法被调用时,*start的初始值为NULL。如果保留*start为NULL,内核将家丁数据保存在内存页偏移量0的地方,并且忽略off参数。相反,如果我们将*start设为非空值,内核将认为由*start指向的数据是off参数指定的偏移量中的数据。通常返回少量数据的简单read_proc函数可忽略start参数,复杂的read_proc函数将*start设置为页面,并将请求偏移量处得数据放到内存页。
start参数还有另一种用法,将在下文继续讨论。

以下内容实现一个简单的可读写的proc文件例子:  

#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/proc_fs.h>
#include<linux/string.h>
#include<linux/vmalloc.h>
#include<asm/uaccess.h>

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Fortune Cookie Kernel Module");


#define MAX_COOKIE_LENGTH	 PAGE_SIZE

//保存proc结构体指针
static struct proc_dir_entry *proc_entry;

//保存缓冲区指针
static char *cookie_pot;

// cookie_index=已经写入的数据数目-1 。
static int cookie_index;  
//遍历标
static int next_fortune;


ssize_t fortune_write(struct file *filp,const char __user *buff, 
			unsigned long len, void *data)
{
	int space_avaliable=( MAX_COOKIE_LENGTH - cookie_index) +1;	 //计算剩余空间
	if(len>space_avaliable)
	{
		printk(KERN_INFO "fortune:cookie pot is full\n");
		return -ENOSPC;
	}
	
	if(copy_from_user(&cookie_pot[cookie_index],buff,len  ) )
		return -EFAULT;
	
	cookie_index+=len;
	cookie_pot[cookie_index-1]=0;
	return len;
}

//每读一次返回一个之前写入的字符串。
int fortune_read(char *page,char **start,off_t off,
				int count ,int *eof,void *data)
{
	int len;
	printk(KERN_INFO "fortune_read\n");
	if(off>0)
	{	
		*eof=1;		
		return 0;
	}
	
	if( next_fortune >= cookie_index ) next_fortune= 0 ;
	len=sprintf(page,"%s\n", &cookie_pot[ next_fortune ] ) ;
	next_fortune+=len;
	
	return len;
	
}

int init_fortune_module(void)
{
	int ret=0;
	//分配一个页大小的数据块,用cookie_pot保存
	cookie_pot=(char *)vmalloc( MAX_COOKIE_LENGTH );
	if(!cookie_pot)
		ret=-ENOMEM;		//错误处理。显然
	else
	{
		memset(cookie_pot,0,MAX_COOKIE_LENGTH);	//清零
		proc_entry=create_proc_entry("fortune",0644,NULL);		//创建一个新的proc文件,名为fortune.
		if(proc_entry==NULL)
		{
			ret=-ENOMEM;
			vfree(cookie_pot);
			printk(KERN_INFO "fortune :couldn't create proc entry\n");
		}
		else
		{
			cookie_index=0;
			next_fortune=0;
			proc_entry->read_proc=fortune_read;
			proc_entry->write_proc=fortune_write;
			proc_entry->owner=THIS_MODULE;
			printk(KERN_INFO "fortune: Module load.\n");		//一些一本的proc结构体初始化而已。
		}
	}
	return ret;
}

void cleanup_fortune_module(void)
{
	remove_proc_entry("fortune",proc_entry);		//移除
	vfree( cookie_pot );
	printk(KERN_INFO "fortune:Module unload\n");
}

module_init(init_fortune_module);
module_exit(cleanup_fortune_module);


上文实现了一个可以读写的proc文件。读者可以往/proc/fortune中写入字符串后再读取来查看该模块的行为。







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值