proc文件系统实现用户空间与内核空间的数据通信

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函数即可。

  1. 如何编写自己的文件操作函数?

    函数原型:

    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

  2. 用户空间与内核空间的数据是如何传递的?

    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操作后续在进行学习和分析

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值