test Blog
Happy living

proc文件系统剖析

老鼠 posted @ 2009年4月15日 22:45 in linux with tags 文件系统 proc , 2374 阅读
Proc file system
dreamice.jiang@gmail.com
1、重要的数据结构:
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;
        loff_t size;
        const struct inode_operations *proc_iops;
        /*
         * NULL ->proc_fops means "PDE is going away RSN" or
         * "PDE is just created". In either case, e.g. ->read_proc won't be
         * called because it's too late or too early, respectively.
         *
         * If you're allocating ->proc_fops dynamically, save a pointer
         * somewhere.
         */
        const 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 pde_users;        /* number of callers into module in progress */
        spinlock_t pde_unload_lock; /* proc_fops checks and pde_users bumps */
        struct completion *pde_unload_completion;
        shadow_proc_t *shadow_proc;
};

2、创建函数:
struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode,
                                                struct proc_dir_entry *parent);
name: 要创建的文件名称;
mode: 该文件的保护掩码;
parent: 确定文件所在目录,如果置NULL,则位置为/proc下。

3、读proc:
int read_proc(char *page, char **start, off_t off,
                          int count, int *eof, void *data);
page:指示用来写入数据的缓冲区;
off和count:与read函数对应的参数相同;
start和eof:用于读取大于1个page数据时实现。

4、写proc:
int write_proc(struct file *file, const char __user *buffer,
                           unsigned long count, void *data);
filp 参数实际上是一个打开文件结构(可以忽略这个参数)。Buffer 参数是用户空间要写入的数据。缓冲区地址实际上是一个用户空间的缓冲区,不能直接读取它。len 参数定义了在 buffer 中有多少数据要被写入。data 参数是一个指向私有数据的指针。

5、实现一个proc文件
(1)调用create_proc_entry创建一个struct proc_dir_entry,作为一个全局量。
(2)对创建的struct proc_dir_entry进行赋值:read_proc,mode,owner,size,write_proc等等。
示例:
proc_hello.c
#include <linux/module.h> /* Specifically, a module */
#include <linux/kernel.h> /* We're doing kernel work */
#include <linux/proc_fs.h> /* Necessary because we use the proc fs */
#define procfs_name "helloworld"

struct proc_dir_entry *Our_Proc_File;
int
procfile_read(char *buffer,
        char **buffer_location,
        off_t offset, int buffer_length, int *eof, void *data)
{
        int ret;
        printk(KERN_INFO "procfile_read (/proc/%s) called\n", procfs_name);
/*
* We give all of our information in one go, so if the
* user asks us if we have more information the
* answer should always be no.
*
* This is important because the standard read
* function from the library would continue to issue
* the read system call until the kernel replies
* that it has no more information, or until its
* buffer is filled.
*/
        if (offset > 0) {
                printk(KERN_INFO "offset > 0\n");
/* we have finished to read, return 0 */
                ret = 0;
        } else {
/* fill the buffer, return the buffer size */
                printk(KERN_INFO "offset <= 0\n");
                ret = sprintf(buffer, "HelloWorld!\n");
        }
        return ret;
}
int init_module()
{
        Our_Proc_File = create_proc_entry(procfs_name, 0644, NULL);
        if (Our_Proc_File == NULL) {
                remove_proc_entry(procfs_name, &proc_root);
                printk(KERN_ALERT "Error: Could not initialize /proc/%s\n",
                        procfs_name);
                return -ENOMEM;
        }
        Our_Proc_File->read_proc = procfile_read;
        Our_Proc_File->owner = THIS_MODULE;
        Our_Proc_File->mode = S_IFREG | S_IRUGO;
        Our_Proc_File->uid = 0;
        Our_Proc_File->gid = 0;
        Our_Proc_File->size = 37;
        printk(KERN_INFO "/proc/%s created\n", procfs_name);
        return 0; /* everything is ok */
}
void cleanup_module()
{
        remove_proc_entry(procfs_name, &proc_root);
        printk(KERN_INFO "/proc/%s removed\n", procfs_name);
}

Makefile:
obj-m := proc_hello.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

default:
        $(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
        $(RM) *.o *.mod.c *.ko *.symvers

本示例只实现了一个简单的读proc操作。
编译后,执行:
# insmod proc_hello.ko
# dmesg
  /proc/helloworld created
# cat /proc/ helloworld
  HelloWorld!
# dmesg
  procfile_read (/proc/helloworld) called
offset <= 0
procfile_read (/proc/helloworld) called
offset > 0
procfile_read (/proc/helloworld) called
offset > 0
# rmmod proc_hello
  /proc/helloworld

为什么在调用cat /proc/ helloworld后,会看到procfile_read被执行了三次,且只有第一次读到了数据?
因为通常cat,dd等,都是通过标准库函数来实现的,标准库函数往往会多次调用系统调用来读写数据,所以我们看到调用了3次。具体cat以及dd等调用一次,读取或写数据大小,这个需要具体查证一下。
我写了一个test程序,来验证这个多次读操作不是在系统调用里面实现的:
test.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define PROC_FILE       "/proc/helloworld"

int main()
{
        char buf[50];
        int fd = open(PROC_FILE, O_RDONLY);
        if (fd == -1) {
                printf("open err\n");
                return -1;
        }
        if (read(fd, buf, sizeof("HelloWorld")) > 0) {
                printf("read: %s\n", buf);
        }
        close(fd);
}

编译:
# gcc test.c –o test
加载模块后,执行
# ./test
然后,执行:
# dmesg
  procfile_read (/proc/helloworld) called
offset <= 0
确实只执行了一次,这说明多次调用procfile_read是在系统之上做的。
6、注意点
(1)切记不能访问已经卸载的一个proc文件,否则会导致kernel panic;
(2)注意不要删除正在调用的问proc文件。因为文件的入口项不存在关联的作者,文件的调用也并没有作用到模块的引用计数上;
(3)勿注册两个同名的proc文件,这样将导致入口项无法区分。

以上这些都是proc文件的缺点,也是使用时必须注意的地方。所以,现在推荐使用seq_file 和sys_fs。

 

net worth 说:
2022年12月09日 18:59

I learned a lot from the insight you shared here. It's good to learn more about this topic, and if you have some free time or you're curious about some celebrity basic information, you can visit idol net worth and search for it.


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter