[TOC]

1. 问题来源

看到一个null pointer dereference的demo使用了这个函数。

2. 概述

Proc文件系统
Proc File System是一个虚拟的文件系统,可以理解为内核对用户开放的接口,让内核和用户进程进行数据交换 (读取内核进程的数据,修改内核参数等):

cat /proc/cpuinfo
Creating a new Proc file
To create a proc file system we need to implement a simple interface – file_operation.We can implement more than 20 functions but the common operations are read, write.

To register the interface use the function proc_create.

要创建一个Proc file需要实现file_operation结构体,主要实现read和write就可以了。然后通过proc_create来注册。将模块注册到内核后,就能在/proc/目录找到我们的文件。
对该文件进行读写就能实现用户进程与内核的通信。

3. 示例

mydev.c

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/kernel.h>   
#include <linux/proc_fs.h>
#include <asm/uaccess.h>
#define BUFSIZE  100

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Liran B.H");

static int irq=20;
module_param(irq,int,0660);

static int mode=1;
module_param(mode,int,0660);

static struct proc_dir_entry *ent;

static ssize_t mywrite(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos) 
{
    int num,c,i,m;
    char buf[BUFSIZE];
    if(*ppos > 0 || count > BUFSIZE)
        return -EFAULT;
    if(copy_from_user(buf, ubuf, count))
        return -EFAULT;
    num = sscanf(buf,"%d %d",&i,&m);
    if(num != 2)
        return -EFAULT;
    irq = i; 
    mode = m;
    c = strlen(buf);
    *ppos = c;
    return c;
}

static ssize_t myread(struct file *file, char __user *ubuf,size_t count, loff_t *ppos) 
{
    char buf[BUFSIZE];
    int len=0;
    if(*ppos > 0 || count < BUFSIZE)
        return 0;
    len += sprintf(buf,"irq = %d\n",irq);
    len += sprintf(buf + len,"mode = %d\n",mode);

    if(copy_to_user(ubuf,buf,len))
        return -EFAULT;
    *ppos = len;
    return len;
}

static struct file_operations myops = 
{
    .owner = THIS_MODULE,
    .read = myread,
    .write = mywrite,
};

static int simple_init(void)
{
    ent=proc_create("mydev",0666,NULL,&myops);
    printk(KERN_ALERT "hello...\n");
    return 0;
}

static void simple_cleanup(void)
{
    proc_remove(ent);
    printk(KERN_WARNING "bye ...\n");
}

module_init(simple_init);
module_exit(simple_cleanup);

Makefile

obj-m += mydev.o

modules:
    $(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
    $(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
output:

invincible@ubuntu:~/Desktop/my_mods/mydev$ make
make -C /lib/modules/4.4.0-112-generic/build M=/home/invincible/Desktop/my_mods/mydev modules
make[1]: Entering directory '/usr/src/linux-headers-4.4.0-112-generic'
  CC [M]  /home/invincible/Desktop/my_mods/mydev/mydev.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/invincible/Desktop/my_mods/mydev/mydev.mod.o
  LD [M]  /home/invincible/Desktop/my_mods/mydev/mydev.ko
make[1]: Leaving directory '/usr/src/linux-headers-4.4.0-112-generic'
invincible@ubuntu:~/Desktop/my_mods/mydev$ insmod mydev.ko 
insmod: ERROR: could not insert module mydev.ko: Operation not permitted
invincible@ubuntu:~/Desktop/my_mods/mydev$ sudo insmod mydev.ko 
invincible@ubuntu:~/Desktop/my_mods/mydev$ ls /proc/my*
/proc/mydev
invincible@ubuntu:~/Desktop/my_mods/mydev$ echo "32 6" > /proc/mydev
invincible@ubuntu:~/Desktop/my_mods/mydev$ cat /proc/mydev 
irq = 32
mode = 6
invincible@ubuntu:~/Desktop/my_mods/mydev$ 

user_app.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

void main(void)
{
    char buf[100];
    int fd = open("/proc/mydev", O_RDWR);
    read(fd, buf, 100);
    puts(buf);

    lseek(fd, 0 , SEEK_SET);
    write(fd, "33 4", 5);

    lseek(fd, 0 , SEEK_SET);
    read(fd, buf, 100);
    puts(buf);
}   

output


invincible@ubuntu:~/Desktop/my_mods/mydev$ gcc user_app.c -o user_app
invincible@ubuntu:~/Desktop/my_mods/mydev$ sudo insmod mydev.ko 
invincible@ubuntu:~/Desktop/my_mods/mydev$ ./user_app 
irq = 20
mode = 1

irq = 33
mode = 4

invincible@ubuntu:~/Desktop/my_mods/mydev$ 

4. 补充

proc_create是在kernel 3.10以及之后的版本中新增的,用于替换之前的create_proc_entry

kernel 3.9

include/linux/proc_fs.h

extern struct proc_dir_entry *create_proc_entry(const char *name, umode_t mode,
                        struct proc_dir_entry *parent);

static inline struct proc_dir_entry *create_proc_entry(const char *name,
    umode_t mode, struct proc_dir_entry *parent) { return NULL; }

fs/proc/generic.c

struct proc_dir_entry *create_proc_entry(const char *name, umode_t mode,
                                         struct proc_dir_entry *parent)
{
        struct proc_dir_entry *ent;
        nlink_t nlink;


        if (S_ISDIR(mode)) {
                if ((mode & S_IALLUGO) == 0)
                        mode |= S_IRUGO | S_IXUGO;
                nlink = 2;
        } else {
                if ((mode & S_IFMT) == 0)
                        mode |= S_IFREG;
                if ((mode & S_IALLUGO) == 0)
                        mode |= S_IRUGO;
                nlink = 1;
        }


        ent = __proc_create(&parent, name, mode, nlink);
        if (ent) {
                if (proc_register(parent, ent) < 0) {
                        kfree(ent);
                        ent = NULL;
                }
        }
        return ent;
}
EXPORT_SYMBOL(create_proc_entry);

kernel 3.10

include/linux/proc_fs.h

extern struct proc_dir_entry *proc_create_data(const char *, umode_t,
                           struct proc_dir_entry *,
                           const struct file_operations *,
                           void *);

static inline struct proc_dir_entry *proc_create(
    const char *name, umode_t mode, struct proc_dir_entry *parent,
    const struct file_operations *proc_fops)
{
    return proc_create_data(name, mode, parent, proc_fops, NULL);
}

fs/proc/generic.c

struct proc_dir_entry *proc_create_data(const char *name, umode_t mode,
                    struct proc_dir_entry *parent,
                    const struct file_operations *proc_fops,
                    void *data)
{
    struct proc_dir_entry *pde;
    if ((mode & S_IFMT) == 0)
        mode |= S_IFREG;

    if (!S_ISREG(mode)) {
        WARN_ON(1); /* use proc_mkdir() */
        return NULL;
    }

    if ((mode & S_IALLUGO) == 0)
        mode |= S_IRUGO;
    pde = __proc_create(&parent, name, mode, 1);
    if (!pde)
        goto out;
    pde->proc_fops = proc_fops;
    pde->data = data;
    if (proc_register(parent, pde) < 0)
        goto out_free;
    return pde;
out_free:
    kfree(pde);
out:
    return NULL;
}
EXPORT_SYMBOL(proc_create_data);

两者的区别主要就是proc_create把file_operation作为参数传递,而proc_create_data是创建了proc_dir_entry之后再设置file_operation。