Linux内核模块开发:在Linux Proc文件系统中创建文件

什么是proc文件系统

Linux的proc文件系统是一个虚拟文件系统,最初设计用来进行内核调试。我们可以通过配置和获取/proc/xxx文件的信息来进行和Linux内核的交互。常用的proc文件有:
(1)/proc/cpuinfo:处理器信息
(2)/proc/meminfo:内存信息
(3)/proc/partitions:分区信息
(4)/proc/modules:加载的内核模块信息
(5)cat /proc/mounts:挂载的文件系统信息
……
可以通过cat命令查看这些文件的内容,如下所示:
在这里插入图片描述

相关接口

开发人员可以通过Linux内核提供的proc_create_data函数在/proc文件系统下添加文件,该函数定义于内核源码的fs/proc/generic.c文件中,proc_create_data函数声明如下:
struct proc_dir_entry *proc_create_data(const char *name, //文件名称
umode_t mode, //访问权限
struct proc_dir_entry *parent, //父目录
const struct proc_ops *proc_ops, //操作函数集合
void *data) //私有数据
第一个参数表示将要创建的文件名称。
第二个参数表示用户对该文件的访问权限,格式 一般为0abc,a代表本用户权限,b代表用户组权限,c代表其他用户权限。其中读、写、执行分别用4、2、1表示。如用户具有读权限,用4表示;具有读写权限用4+2=6来表示,同样,7表示用户同时具有读、写和执行权限。例如:0644表示本用户具有读写权限,用户组和其他用户具有只读权限。
umode_t的定义在内核源码目录的include/linux/types.h中,定义如下:
typedef unsigned short umode_t
umode_t就是unsigned short类型,其表示的是将要创建的目录的权限信息。类型选择定义在内核源码目录的include/uapi/linux/stat.h和include/linux/stat.h中,这里展示部分常用的权限定义:
#define S_IRWXU 00700 //该用户具有读、写、执行权限
#define S_IRUSR 00400 //该用户具有读权限
#define S_IWUSR 00200 //该用户具有写权限
#define S_IXUSR 00100 //该用户具有执行权限
#define S_IRWXG 00070 //用户组的用户具有读、写、执行权限
#define S_IRGRP 00040 //用户组的用户具有读权限
#define S_IWGRP 00020 //用户组的用户具有写权限
#define S_IXGRP 00010 //用户组的用户具有执行权限

#define S_IRWXO 00007 //其他用户具有读、写、执行权限
#define S_IROTH 00004 //其他用户具有读权限
#define S_IWOTH 00002 //其他用户具有写权限
#define S_IXOTH 00001 //其他用户具有执行权限
上述的权限和调用chmod命令的权限是一致的。实际上,调用chmod修改的文件权限在内核源码中就是用umode_t来表示。
proc_create_data的第三个参数表示将要创建的文件的父目录,是一个指针,其中NULL表示文件就在/proc目录下创建。
第四个参数表示对将要创建的文件的函数操作集合,包含对文件的读、写、打开、关闭等等操作。struct proc_ops结构体定义在Linux内核源码的include/linux/proc_fs.h头文件中,定义如下:
struct proc_ops {
……
int (*proc_open)(struct inode *, struct file *); //文件打开操作
ssize_t (*proc_read)(struct file *, char __user *, size_t, loff_t *); //文件读操作
……
ssize_t (*proc_write)(struct file *, const char __user *, size_t, loff_t *);//文件写操作
……
int (*proc_release)(struct inode *, struct file *); //文件关闭对应的释放资源操作
……
};
文件被打开、读、写、关闭时,将执行该结构体中对应的proc_open、proc_read、proc_write、proc_release函数。

示例程序1:创建一个文件

一个简单的文件创建源码如下:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/uaccess.h>

//open operation
static int proc_file_open(struct inode *inode, struct file *file)
{
	printk("Open Operation!\n");
	return 0;
}
//read operation
static ssize_t proc_file_read(struct file *file, char __user *buffer, size_t len, loff_t *offset)
{
	printk("Read Operation!\n");
	return 0;
}
//write opertion
static ssize_t proc_file_write(struct file *file, const char __user *buffer, size_t len, loff_t *offset)
{
	printk("Write Operation!\n");
	return len;
}
//release operation
int proc_file_release(struct inode *inode, struct file *file)
{
	printk("Release Operation!\n");
	return 0;
}
static const struct proc_ops proc_file_ops = {
	.proc_open 	 = proc_file_open,		//打开文件操作
	.proc_read 	 = proc_file_read,		//读文件操作
	.proc_write 	 = proc_file_write,		//写文件操作
	.proc_release = proc_file_release,		//关闭文件的释放操作
};
static int proc_file_init(void)
{
	/*
	*创建文件my_proc_file,访问权限为0644,父目录为空,即
	*该文件就在/proc目录下,函数操作集合为proc_file_ops
	*/
	proc_create_data("my_proc_file", 0644, NULL,
		&proc_file_ops, NULL);
	printk("proc_file_init success!\n");
	return 0;
}

static void proc_file_exit(void)
{
	//删除文件my_proc_file
	remove_proc_entry("my_proc_file", NULL);
	printk("proc_file_exit\n");
}

module_init(proc_file_init);
module_exit(proc_file_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("proc Module");

代码解析:
(1)初始化和卸载函数
上述源码的proc_file_init初始化函数通过调用proc_create_data接口实现了文件/proc/my_proc_file的创建,proc_file_exit卸载函数通过调用remove_proc_entry函数实现文件/proc/my_proc_file的销毁操作,remove_proc_entry函数定义于内核源码的fs/proc/generic.c文件中,其声明如下:
void remove_proc_entry(const char *name, struct proc_dir_entry *parent)
第一个参数name表示文件名,第二个参数代表文件的父目录,如果parent为空,表示该文件在/proc目录下。
(2)文件操作函数
static const struct proc_ops proc_file_ops变量保存了文件的打开(proc_file_open)、读(proc_file_read)、写(proc_file_write)、关闭(proc_file_release)所需执行的操作,该变量在初始化函数创建文件时作为参数被传入proc_create_data函数。初始化函数创建的/proc/my_proc_file文件在打开、读、写、关闭时将会执行对应的函数。源码中proc_file_open、proc_file_read、proc_file_write、proc_file_release的实现非常简单,仅打印了对应的操作,在对文件访问后可通过dmesg –c命令查看打印信息。
编译加载该模块之后,/proc目录下多了一个my_proc_file文件,如下所示:
在这里插入图片描述
此时可以通过echo命令向该文件写数据,写完后执行dmesg -c,可以看到echo命令首先进行文件打开操作,再执行写操作,最后执行文件关闭操作,如下所示:
在这里插入图片描述
执行rmmod卸载模块后,/proc/my_proc_file文件将会消失。

示例程序2:对文件进行读写

上一个示例创建了/proc/my_proc_file文件,然而对该文件的读、写操作仅仅做了打印,而并没有发生实际的数据交互。本节在此基础上,增加文件读写时实际的数据交互操作。要实现该功能,需要首先了解两个接口:
(1)内核向应用程序传递数据接口
unsigned long copy_to_user(
void __user *to, //用户指针
const void *from, //内核指针
unsigned long n) //传送长度
该函数用于内核模块将数据传递给应用程序,将内核模块指针from指向的内存区间的n个字节长度的数据拷贝给应用程序指针to。
(2)应用程序向内核传送数据
unsigned long copy_from_user(
void *to, //内核指针
const void __user *from, //用户指针
unsigned long n) //传送长度
与copy_to_user相反,该函数用户应用程序将数据传递给内核模块。
以上两个函数均在内核源码的include/linux/uaccess.h头文件中定义。
增加了/proc/my_proc_file实际读写操作的源码如下:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/uaccess.h>

static char kbuf[128] = {0};
//open operation
static int proc_file_open(struct inode *inode, struct file *file)
{
return 0;
}
//read operation
static ssize_t proc_file_read(struct file *file, char __user *buffer, size_t len, loff_t *offset)
{
	//将kbuf的内容拷贝到用户空间展示
	copy_to_user(buffer , kbuf , strlen(kbuf));
	return strlen(kbuf);
}
//write opertion
static ssize_t proc_file_write(struct file *file, const char __user *buffer, size_t len, loff_t *offset)
{
	memset(kbuf, 0, sizeof(kbuf));
	//将用户传入的信息拷贝到kbuf变量中
	copy_from_user(kbuf , buffer , len);
	return len;
}
//release operation
int proc_file_release(struct inode *inode, struct file *file)
{
	return 0;
}
static const struct proc_ops proc_file_ops = {
	.proc_open 	 = proc_file_open,		//打开文件操作
	.proc_read 	 = proc_file_read,		//读文件操作
	.proc_write 	 = proc_file_write,		//写文件操作
	.proc_release = proc_file_release,		//关闭文件的释放操作
};

static int proc_file_init(void)
{
	proc_create_data("my_proc_file", 0644, NULL,
		&proc_file_ops, NULL);
	return 0;
}

static void proc_file_exit(void)
{
	remove_proc_entry("my_proc_file", NULL);
}

module_init(proc_file_init);
module_exit(proc_file_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Proc Module");

该源码在proc_file_read函数中,通过copy_to_user接口将kbuf中的数据拷贝到用户空间缓存中,并返回了发送给用户空间的数据长度;在proc_file_write函数中,通过copy_from_user接口将用户空间的数据拷贝到了kbuf中,并返回从用户空间传入的数据长度。
下面通过一个简单的测试程序test.c来验证上述代码,测试程序代码如下:

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
	char buf[32] = {0};
	char *str = "hello,proc file!";
	//open file
	int fd = open("/proc/my_proc_file", O_RDWR);
	if(fd < 0)
		printf("open file error!\n");
	//write file
	write(fd, str, strlen(str));
	//read file
	read(fd , buf , 32);
	printf("read:%s\n", buf);
	//close file
	close(fd);
	return 0;
}

首先编译加载内核模块,生成/proc/my_proc_file文件,再编译运行test.c,test.c首先向/proc/my_proc_file文件写入“hello,proc file!”字符串,再从/proc/my_proc_file读取数据,写入和读取的数据一致,如下所示:
在这里插入图片描述

  • 20
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值