嵌入式 procfs编程小结

1、重要函数以及结构体:
procfs是比较老的一种用户态与内核态的数据交换方式,内核的很多数据都是通过这种方式出口给用户的,内核的很多参数也是通过这种方式来让用户方便设置的。

struct proc_dir_entry *create_proc_entry(constchar *name, mode_t mode, 

                    struct proc_dir_entry *parent) 

该函数用于创建一个正常的proc条目,参数name给出要建立的proc条目的名称,参数mode给出了建立的该proc条目的访问权限,参数parent指定建立的proc条目所在的目录。如果要在/proc下建立proc条目,parent应当为NULL。

 

/*

* Remove a /proc entry and free it if it's not currently in use.

*/ 

void remove_proc_entry(constchar *name, struct proc_dir_entry *parent) 

该函数用于删除上面函数创建的proc条目,参数name给出要删除的proc条目的名称,参数parent指定建立的proc条目所在的目录。

cmdline:系统启动时输入给内核命令行参数
cpuinfo:CPU的硬件信息 (型号, 家族, 缓存大小等) 
devices:主设备号及设备组的列表,当前加载的各种设备(块设备/字符设备)
dma:使用的DMA通道
filesystems:当前内核支持的文件系统,当没有给 mount(1) 指明哪个文件系统的时候, mount(1) 就依靠该文件遍历不同的文件系统
interrupts :中断的使用及触发次数,调试中断时很有用
ioports I/O:当前在用的已注册 I/O 端口范围
kcore:该伪文件以 core 文件格式给出了系统的物理内存映象(比较有用),可以用 GDB 查探当前内核的任意数据结构。该文件的总长度是物理内存 (RAM) 的大小再加上 4KB
kmsg:可以用该文件取代系统调用 syslog(2) 来记录内核日志信息,对应dmesg命令
kallsym:内核符号表,该文件保存了内核输出的符号定义, modules(X)使用该文件动态地连接和捆绑可装载的模块
loadavg:负载均衡,平均负载数给出了在过去的 1、 5,、15 分钟里在运行队列里的任务数、总作业数以及正在运行的作业总数。
locks:内核锁 。
meminfo物理内存、交换空间等的信息,系统内存占用情况,对应df命令。
misc:杂项 。
modules:已经加载的模块列表,对应lsmod命令 。
mounts:已加载的文件系统的列表,对应mount命令,无参数。
partitions:系统识别的分区表 。
slabinfo:sla池信息。
stat:全面统计状态表,CPU内存的利用率等都是从这里提取数据。对应ps命令。
swaps:对换空间的利用情况。
version:指明了当前正在运行的内核版本。

/proc目录下常见的就是上述几个文件和目录。需要格外注意的就是三个黄色的目录:net、scsi和sys。sys目录是可写的,可以通过它来访问或修改内核的某些控制参数,sysctl命令接口会用到/proc/sys目录,这里我就不展开了,在介绍sysctl章节时详细讨论。而net和scsi则依赖于内核配置,协议栈和我们前面介绍过的Netfitler都在/proc/net目录下建立了各自的某些控制信息所对应的文件。如果系统不支持scsi,则 scsi目录就不存在。

接下来我们就来实践一下Linux所提供给我们的proc机制来完成用户和内核空间的通信。

如果要在/proc目录下创建一个目录,一般用:

struct proc_dir_entry *proc_mkdir(const char *name,

struct proc_dir_entry *parent)

其中,name为待创建的目录名;parent为待创建目录的上一级目录,如果为NULL则表示默认创建到/proc目录下。而如果要在/proc目录下创建一个文件,一般用:

struct proc_dir_entry *create_proc_entry(const char *name,

mode_t mode,

struct proc_dir_entry *parent)

name和parent的意义同proc_mkdir,mode指明了待创建文件的权限,即是否可读可写,或哪些用户可读,哪些用户可写。这两个函数均返回一个struct proc_dir_entry{}结构体的实例,该结构体定义在linux-2.6.21\include\linux\proc_fs.h文件中,如下:

点击(此处)折叠或打开

  1. struct proc_dir_entry {
  2.     … …
  3.     const struct inode_operations *proc_iops;
  4.     const struct file_operations *proc_fops;
  5.     … …
  6.     read_proc_t *read_proc;
  7.     write_proc_t *write_proc;
  8.     … …
  9. };


比较重要的两个成员函数是read_proc和write_proc,分别指向proc目录下待创建的文件被“读”和“写”的时候的回调处理函数(重要)。

读函数的原型:

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

当我们通过诸如“cat”之类的命令来读取/proc目录下的文件内容时,内核会分配给proc读取程序一页大小的内存空间,即PAGE_SIZE大小,proc的驱动程序会自动将这块内存中的数据复制到用户空间,最终待访问的proc文件的read_proc回调函数会被调用,其中:

page:将需要传递给用户的数据复制到这个缓冲区地址里。这个缓冲区是内核空间的,不需要用户调用copy_to_user()之类的函数,系统会自动将page中的数据复制给用户。

start:在page所指向的缓冲区中需要复制给用户数据的起始地址。一般不使用。

off:用户打算读取文件时的偏移地址,即从这个地址开始读取。类似用户空间lseek移动文件指针的作用。

count:用户要求读取的字节数。

eof:如果读到文件结尾,当驱动程序的数据发送完毕时将这个值置为1,发送给用户,我们可以通过该值判断是否读取到文件末尾。

data:私有数据指针,一般不用。


写函数的原型:

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

file:内核中一个打开的文件结构,通常忽略。

buffer:用户空间传递过来的数据指针,用户待写入文件的数据就存储在这个值所指向的地址区域中。而这家伙实际上是一个用户空间地址,内核中不能直接拿来用,需要调用诸如copy_from_user()之类的函数来讲用户空间的数据复制到内核空间来。

count:用户待写入文件的字节数。

data:一般不用。

删除proc文件比较简单,调用:

void remove_proc_entry(const char *name, struct proc_dir_entry *parent)

下面首先例出这组函数,再给出一个例子。
一、相关函数和结构体
  1,struct proc_dir_entry *proc_mkdir(const char *name,
        struct proc_dir_entry *parent)//在parent目录创建一个名为name的目录。
   比如:struct proc_dir_entry *proc_net_ipsec_dir = proc_mkdir("ipsec", proc_net);//在proc_net目录创建名为ipsec的目录
  其中proc_net相当于宏,指/proc/net目录,如果第二个参数传NULL,即指默认/proc目录。以下是几个类似的宏。
    proc_root_fs /proc
    proc_net /proc/net
    proc_bus /proc/bus
    proc_root_driver /proc/driver
  2,再继续第二个函数之前有必要先说下porc_mkdir的返回值,它是一个结构体struct pro_dir_entry
  struct proc_dir_entry {
    unsigned int low_ino;
    unsigned short namelen;
    const char *name;
    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 */
  };
  里面包含了一个文件(Linux把所有对象都当文件看代,目录当然也是一个文件)所有属性,比如:文件名、权限、uid、gid、前一级目录、包含的子目录等等,我记得在APUE2e在讲解文件系统时讲到过,所以可以结合上面内容进行理解。
  这次我们不对这些属性感兴趣,主要是关注下目录的读写函数,即read_proc和write_proc,一会例子中会提到。
  3,struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode,
                     struct proc_dir_entry *parent)//在parent目录创建一个名为name,权限为mode的文件。
    比如:struct proc_dir_entry * item = create_proc_entry("klipsdebug", 0400, "/proc/ipsec")//在/proc/ipsec目录创建一个权限为0400(只读)的文件klipsdebug
  4,create_proc_entry执行后返回的proc_dir_entry可以自己指定read、write等函数,如果只需要read函数,可以使用
    static inline struct proc_dir_entry *create_proc_read_entry(const char *name,
    mode_t mode, struct proc_dir_entry *base,
    read_proc_t *read_proc, void * data)
    它其实是对create_proc_entry进行了封装,把create_proc_read_entry的传入值read_proc赋给了create_proc_entry返回值的read_proc成员,具体可看内核实现。
  5,struct proc_dir_entry *proc_symlink( const char *name,
                                       struct proc_dir_entry *parent,
                                       const char *dest );//在parent目录创建指定dest目录的名为name的符号链接。
  比如: proc_symlink("ipsec_eroute", proc_net, "ipsec/eroute/all");//在/proc/net创建指向/proc/net/ipsec/eroute/all的符号链接ipsec_eroute。
  6,void remove_proc_entry( const char *name, struct proc_dir_entry *parent );//删除parent目录中的名为name的文件
  7,还有两上函数比较重要,就是之前提到的read、write函数中需要调用的函数,这两个函数即完成了用户空间和内核空间的数据交互。
  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 );

我的个人小示例:

root@u12d32:/qy_work/work_hi3518a/hi_test/c_hi3518/proc_programme# cat proc_pgrm.c
#include <linux/module.h>  
#include <linux/kernel.h> 
#include <linux/proc_fs.h>  
#include <linux/sched.h>  
#include <asm/uaccess.h> 

#define STRINGLEN 2048  

char global_buffer[STRINGLEN];  

struct proc_dir_entry *example_dir, *hello_file;  

int proc_read_hello(char *page, char **start, off_t off, int count, int *eof,void *data)
{  
 int len;  
 len = sprintf(page, global_buffer); //把global_buffer的内容显示给访问者  
 return len;  
}  

int proc_write_hello(struct file *file, const char *buffer, unsigned long count,void *data)
{  
 int len;  

 if (count == STRINGLEN)
 {
  len = STRINGLEN - 1;
 }
 else
 {
  len = count;
 }
 
 copy_from_user(global_buffer, buffer, len);  
 global_buffer[len] = '\0';

 return len;  
}  

static int __init proc_test_init(void) {  
 example_dir = proc_mkdir("proc_test", NULL);  
 hello_file = create_proc_entry("hello", S_IRUGO, example_dir);  
 strcpy(global_buffer, "hello\tjoseph\tkongjun\tdate\n\n");  
 hello_file->read_proc = proc_read_hello;  
 hello_file->write_proc = proc_write_hello;  
 return 0;  
}  

static void __exit proc_test_exit(void) {  
 remove_proc_entry("hello", example_dir);  
 remove_proc_entry("proc_test", NULL);
}  

module_init(proc_test_init);
module_exit(proc_test_exit);

MODULE_AUTHOR("xxxx");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("joseph_proc creat succeed ! ");

 

 Makefile:

root@u12d32:/qy_work/work_hi3518a/hi_test/c_hi3518/proc_programme# cat Makefile
ifneq ($(KERNELRELEASE),)
obj-m := proc_pgrm.o
else
KERNELDIR ?= /home/kongjun/mywork/develop_kj/Hi3518_SDK_V1.0.3.0/osdrv/kernel/linux-3.0.y
KERNELDIR_X86 ?= /usr/src/linux-headers-3.5.0-34-generic
PWD := $(shell pwd)

modules: 
 $(MAKE) -C $(KERNELDIR_X86) M=$(PWD) modules
endif

clean: 
 rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions *.symvers *.order
root@u12d32:/qy_work/work_hi3518a/hi_test/c_hi3518/proc_programme#

 

你可能感兴趣的:(嵌入式 procfs编程小结)