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
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);
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
。
本文主要内容来自参考资料1
5. 参考资料
1. LINUX KERNEL DEVELOPMENT – CREATING A PROC FILE AND INTERFACING WITH USER SPACE
2. The Linux Kernel Module Programming Guide