proc文件系统实现用户空间与内核空间的数据通信
背景
在Linux系统中,常常会用到proc文件系统来完成用户空间与内核空间的数据交互,比如说内核通过/proc/meminfo向用户层传递系统内存使用的信息,而用户层可以通过更改/proc下的文件改变内核层代码执行的路径,最简单的比如说通过proc的文件使系统panic重启,那么就可以使用echo c > /proc/sysrq-trigger命令。
proc简介
proc 文件系统是一种虚拟文件系统,它可以实现linux内核空间和用户空间的通信。对于Linux使用者来说,与proc文件系统最常用的交互手段为cat以及echo。使用cat查看内核中的配置参数,例如内存、中断等,利用echo可以实现内核中许多参数的配置。例如与vm相关的参数配置:
[root@localhost proctest-master]# ls /proc/sys/vm/
admin_reserve_kbytes lowmem_reserve_ratio overcommit_kbytes
block_dump max_map_count overcommit_memory
compaction_proactiveness memory_failure_early_kill overcommit_ratio
compact_memory memory_failure_recovery page-cluster
compact_unevictable_allowed min_free_kbytes page_lock_unfairness
dirty_background_bytes min_slab_ratio panic_on_oom
dirty_background_ratio min_unmapped_ratio percpu_pagelist_fraction
dirty_bytes mmap_min_addr stat_interval
dirty_expire_centisecs mmap_rnd_bits stat_refresh
dirty_ratio mmap_rnd_compat_bits swappiness
dirtytime_expire_seconds nr_hugepages unprivileged_userfaultfd
dirty_writeback_centisecs nr_hugepages_mempolicy user_reserve_kbytes
drop_caches nr_overcommit_hugepages vfs_cache_pressure
extfrag_threshold numa_stat watermark_boost_factor
hugetlb_shm_group numa_zonelist_order watermark_scale_factor
laptop_mode oom_dump_tasks zone_reclaim_mode
legacy_va_layout oom_kill_allocating_task
如何实现数据交互?
1. 创建目录
使用函数:proc_mkdir
name是目录名,如“example_dir”,parent是要创建的目录的父目录名(若parent = NULL则创建在/proc目录下。
struct proc_dir_entry *proc_mkdir(const char *name, struct proc_dir_entry *parent)
例如目前想要在/proc目录下创建一个名为myworld的目录:
static struct proc_dir_entry *myworld;
myworld = proc_mkdir("myworld", NULL);
2. 创建文件
使用函数:proc_create
name为创建的文件名称,mode为文件的访问权限,parent为文件所属的文件夹,proc_ops即为该文件的操作函数(下一小节细讲)。
struct proc_dir_entry *proc_create(const char *name, umode_t mode,
struct proc_dir_entry *parent,
const struct proc_ops *proc_ops)
例如需要在/proc/myworld的目录下创建一个名为helloworld的文件:
static struct proc_dir_entry *helloworld;
helloworld = proc_create("helloworld",0644,myworld,&myops);
3. 创建可读写的文件
经过上面的两个函数我们就可以在proc中建立/proc/myworld/helloworld的文件了,但是目前为止也仅仅是一个文件,并不能利用它来做些什么;如果我们想利用该文件实现数据的交互任务,还需要为他赋予操作函数集,也就是proc_create函数中的第四个参数proc_ops,这样我们才能对其进行cat或者echo等操作。
定义文件函数集:
static struct proc_ops myops =
{
.proc_read = helloworld_read,
.proc_write = helloworld_write,
};
这里对proc文件操作函数的定义在新的内核中进行了更改,在3.10内核中直接以file_operations进行声明定义,而5.10内核从新定义了proc_ops函数集。
//For example:
//3.10内核中对irq_affinity操作的定义
static const struct file_operations irq_affinity_proc_fops = {
.open = irq_affinity_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
.write = irq_affinity_proc_write,
};
//5.10内核中对irq_affinity操作的定义
static const struct proc_ops irq_affinity_proc_ops = {
.proc_open = irq_affinity_proc_open,
.proc_read = seq_read,
.proc_lseek = seq_lseek,
.proc_release = single_release,
.proc_write = irq_affinity_proc_write,
};
我们根据proc_ops中定义的函数原型去编写我们自己的read和write函数即可。
-
如何编写自己的文件操作函数?
函数原型:
static ssize_t proc_read(struct file *file, char __user *buffer, size_t len, loff_t *offset); static ssize_t proc_write(struct file *file, const char __user *buffer, size_t len, loff_t *offset);
读取处理程序接收4个参数:
- 文件对象–具有打开的文件详细信息(权限,位置等)的每个过程结构
- 用户空间缓冲区
- 缓冲区大小
- 要求的位置(输入和输出参数)
要实现read回调,我们需要:
- 检查要求的位置
- 从请求的位置向用户缓冲区填充数据(最大大小<=缓冲区大小)
- 返回我们填充的字节数。
ssize_t helloworld_read(struct file *file, char __user *ubuf,size_t count, loff_t *ppos) { char kernel_buf[BUFSIZE]; int len=0; len += sprintf(kernel_buf,"%d\n", myID);//(1)将变量myId的值以整数的形式传递给kernel_buf if(copy_to_user(ubuf,kernel_buf,len))//(2)使用copy_to_user将内核空间内容传递至用户空间 return -EFAULT; *ppos = len; return len; }
static ssize_t helloworld_write(struct file *file, const char __user *ubuf,size_t count, loff_t *ppos) { int c; char kernel_buf[BUFSIZE]; if(*ppos > 0 || count > BUFSIZE) return -EFAULT; if(copy_from_user(kernel_buf,ubuf,count))//(1)数据从用户空间拷贝到内核空间 return -EFAULT; sscanf(kernel_buf,"%d",&myID);//(2)从kernel_buf中格式化%d形式输出到myID中 c = strlen(kernel_buf); *ppos = c; return c; }
这样如果定义的myID为一个模块内的全局变量,那么我可以检测该变量的值,当myID为不同的值时去执行不同的动作。因此通过echo来完成信息的交互动作。
vfs_read->proc_reg_read->helloworld_read
-
用户空间与内核空间的数据是如何传递的?
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);
我的理解其实就是先使用copy_to_user或者copy_from_user完成一次用户空间与内核空间的数据拷贝,随后内核空间或者用户空间再从中以特定的格式提取出相应的数据进行操作。
关于proc文件系统中seq操作后续在进行学习和分析