Chinaunix首页 | 论坛 | 博客
  • 博客访问: 205941
  • 博文数量: 91
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 15
  • 用 户 组: 普通用户
  • 注册时间: 2015-12-09 10:37
文章分类
文章存档

2016年(87)

2015年(4)

我的朋友

分类: LINUX

2016-06-16 10:01:57

这篇博文将针对linux内核态与用户态通信方式中的procfs进行详细的学习。

/proc主要存放内核的一些控制信息,所以这些信息大部分的逻辑位置位于内核控制的内存,在/proc下使用ls -l你会发现大部分的文件或者文件夹的大小都是0,不过使用cd命令进到文件夹下或者使用cat命令查看某些文件的内容确实能查看到一些信息。这是因为/proc文件系统和其他常规文件系统一下把自己注册到虚拟文件系统层(VFS),但是直到VFS调用它,请求文件或者目录的i节点时,/proc才根据内核中信息建立相应的目录或者文件(可以理解为这些虚拟文件是动态创建的)。

/proc开始的设计只是为了满足内核向用户态进程报告自己的状态而设计的,随着发展/proc文件系统已经成为“内核态-用户态”交互的一种半双工的方式。用户不但可以从/proc中读取内核的相关信息,而且可以通过改变/proc下相应的文件来改变内核的某些行为状态。

在这里先简单的了解下/proc目录下的一些文件。

    --cmdline:系统启动时输入给内核的命令行参数 

    --cpuinfo:cpu的硬件信息(型号,家族,缓存大小..)

    --filesystems:当前内核支持的文件系统,当没有给mount(1)指明文件系统时,mount(1)就根据该文件便利不同的文件系统

    --nterrupts:中断的使用和触发次数,据说在调试中断时候很有用

    --ioports:当前在用的已注册的IO端口的范围

    --kmsg:对应dmesg命令。可以代替系统调用syslog(2)来记录内核日志信息。

    --filesystems:内核符号表,该文件保存了内核输出的符号定义。

    --loadavg:
        cat /proc/loadavg
        4.61 4.36 4.15 9/84 5662
        每个值的含义为:
        参数 解释
        lavg_1 (4.61) 1-分钟平均负载
        lavg_5 (4.36) 5-分钟平均负载
        lavg_15(4.15) 15-分钟平均负载
        nr_running (9) 在采样时刻,运行队列的任务的数目,与/proc/stat的procs_running表示相同意思
        nr_threads (84) 在采样时刻,系统中活跃的任务的个数(不包括运行已经结束的任务)
        last_pid(5662) 最大的pid值,包括轻量级进程,即线程。

    --locks:内核锁

    --modules:已经加载的模块列表,对应lsmod命令。

    --mounts:已加载的文件系统的列表,对应mount命令不带参数。
    --mtd:每个mtd设备对应的分区名称。
    --partitions:系统识别的分区表。


    --stat:全面统计状态表,cpu内存的利用率等都是从这里提取出来的。

    --version:对应的版本号。

    --sys:可以通过修改sys目录中的信息来修改内核的某些控制参数。

好了,切入正题。
1.通过proc文件系统来进行内核态用户态通信主要需要用到的函数:

在此处输入内容

------------创建一个目录:struct proc_dir_entry *proc_mkdir(const char *name , struct proc_dir_entry *parent)

其中name为要创建的目录名,parent为带创建的目录的上一级,如果parent为null则创建到/proc目录之下。
------------创建一个文件:struct proc_dir_entry *create_proc_entry(const char *name,mode_t mode,struct proc_die_entry *parent)

其中name和parent同上,mode为要创建文件的掩码,即文件权限。

------------上述两个函数中牵扯到一个结构体struct proc_dir_entry.此结构体定义在在include/linux/proc_fs.h文件中:

这里只列出我们这次学习中会用到的成员
            struct proc_dir_entry{
                ...
                read_proc_t *read_proc;
                write_proc_t *write_proc;
                ...    
            };
    ①关于read_proc

    typedef int (read_proc_t)(char *page, char **start, off_t off, int count, int *eof, void *data);

    page是这些数据写到的位置,位于内核空间,大小为一个页。count定义了可以写入的最大字符数。在返回多页数据时(通常一个页是4k)我们会用到start和off。当所有数据全部写入之后需要设置*eof为1。data参数是一个指向私有参数的指针。

    当我们使用cat命令,或者其他操作队proc目录下的文件进行读的操作时,这个函数会将内核中要读取的数据copy到page指向的内核空间,这里的page存在于内核空间,不需要调用copy_to_user,proc的驱动程序会自动将这块内存中的内容复制到用户空间。
    对于read_proc,我个人的一点点猜测,因为不知道从哪里深入了解这个函数,所以关于这个函数的具体原理我目前无法考证,但是这里放出个人的一点点猜测,如果有深入了解的同学,烦请告知。我的猜测:①此函数的返回值为每次读取的字节数,每次调用此函数时系统会在off的基础上再加上上次调用此函数的返回值然后赋值给off,所以此函数的返回值必须是本次调用读取的字节数,这样就会保证每次调用时候off为可以写入的内存的地址的首。②如果未置*eof为1,并且read_proc的返回值不为0会一直读取下去。③关于start我的猜测是start指向的是每个页。也就是如果是多页的数据的话,读取时候是从*start页的off处开始读取。如下图:
            
    ②关于write_proc    
    typedef int (write_proc_t)(struct file *file, const char __user *buffer, unsigned long count, void *data);
    file这个参数先可以忽略,buffer是传递给你的字符串数据,位于用户空间的一个缓冲区中,我们不能直接读取他,要先使用copy_from_user后使用。count定义了在buffer中有多少字符要被写入。data通read_proc一样是私有数据指针,即read_proc或者write_proc被调用时传给他的一个参数。

好了,一起来写一个damo程序吧。
代码:

点击(此处)折叠或打开

  1. #include <linux/module.h>
  2. #include <linux/init.h>
  3. #include <linux/proc_fs.h>
  4. #include <asm/uaccess.h>

  5. #define PROC_NAME "yzy_test"
  6. struct proc_dir_entry *my_proc_fs;
  7. static char kernel_buf[128];

  8. int read_proc(char *page, char **start, off_t off,int count, int *eof, void *data);

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

  10. static int __init module_test_init(void)
  11. {
  12.  memset(kernel_buf,0x00,128);
  13.  my_proc_fs=create_proc_entry(PROC_NAME,0666,NULL);
  14.  my_proc_fs->read_proc=read_proc;
  15.  my_proc_fs->write_proc=write_proc;
  16.  return 0;
  17. }

  18. static void __exit module_test_exit(void)
  19. {
  20.  remove_proc_entry(PROC_NAME,0);
  21. }

  22. int read_proc(char *page, char **start, off_t off,int count, int *eof, void *data)
  23. {
  24.  int len=strlen(kernel_buf);
  25.  if(count>len-off)
  26.  {
  27.   *eof=1;
  28.  }
  29. if( count>strlen(kernel_buf)-off )
  30. {
  31.   count=strlen(kernel_buf)-off;
  32. }
  33.  memcpy(page+off,kernel_buf+off,count);
  34.  printk("read:%s\n",page);
  35.  return count;
  36. }

  37. int write_proc(struct file *file, const char __user *buffer,unsigned long count, void *data)
  38. {
  39.  int len=strlen(kernel_buf);
  40.  if(count>len)
  41.  {
  42.   count=len-1;
  43.  }
  44.  if(!copy_from_user(kernel_buf,buffer,count))//成功返回0,失败返回拷贝失败的字节数.
  45.  {
  46.   return -1;
  47.  }
  48.  kernel_buf[count]=0;
  49.  printk("write:%s\n",kernel_buf);
  50.  return count;
  51. }

  52. module_init(module_test_init);
  53. module_exit(module_test_exit);

  54. MODULE_AUTHOR("yanzhiyao");
  55. MODULE_DESCRIPTION("procfs test module");
  56. MODULE_LICENSE("GPL");

点击(此处)折叠或打开

  1. makefile:
  2. ifneq ($(KERNELRELEASE),)
  3.     procfs-objs := test_procfs.o
  4.     obj-m := test_procfs.o
  5. else
  6.     K_VER ?= $(shell uname -r)
  7.     K_DIR := /lib/modules/$(K_VER)/build
  8.     M_DIR := $(shell pwd)
  9. all:
  10.     make -C $(K_DIR) M=$(M_DIR)
  11. clean:
  12.     rm -rf *.o *.ko *.symvers *.order *mod.c
  13. endif

好了,代码写好了,编一把吧。
生成的.ko文件,使用/sbin/insmod 装载,卸载时使用/sbin/rmmod 卸载 ,使用lsmod可以查看是否装载成功。


                                                                                                                                                                                                                                       
阅读(1309) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~