开发驱动程序时,身边没有硬件设备或者使用设备太麻烦,可以在/proc下创建一个虚拟文件来当作硬件设备,方便调试。/proc 文件系统包含了一些目录(用作组织信息的方式)和虚拟文件。虚拟文件可以向用户呈现内核中的一些信息,也可以用作一种从用户空间向内核发送信息的手段。与普通文件不同的是,这些虚拟文件的内容都是动态创建的,即需要使用的时候创建即可,不用时消亡,不占用磁盘空间。
驱动模块的设计
proc_list.c
/*****************************************
*利用链表在/proc下创建缓冲文件模拟设备
*Author:LC
*Data:2014-5-14
*****************************************/
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/list.h>
#include <linux/uaccess.h>
#include <linux/sched.h>
struct proc_head{
struct list_head head;
int file_size;
};
//链表数据结构体
struct proc_item{
char *str;
struct list_head item;
};
//链表头的指针
static struct proc_head *mylist;
static struct list_head my_head;
//proc文件的读函数
static int my_proc_read(char *page,char **start,off_t off,int count,int *eof,void *data)
{
int ret = 0;
//遍历链表,显示字符串
//显示文件大小
//list_for_each/list_each_entry
//list_for_each_entry_reverse;
struct proc_item *tmp;
list_for_each_entry(tmp,&mylist->head,item){
ret += sprintf(page + ret,"timer is %s\n",tmp->str);
}
ret += sprintf(page + ret,"file size is %d\n",mylist->file_size);
return ret;
}
//写函数
static int my_proc_write(struct file *filp,const char __user *buf,unsigned long count,void *data)
{
//分配一个新的proc_item
//为proc_item->str分配空间
//copy_from_user
//INIT_LIST_HEAD();
//list_add/list_add_tail
//更新proc_head->file_size
struct proc_item *new;
new = (struct proc_item *)kzalloc(count+1,GFP_KERNEL);
if(!new)
return -ENOMEM;
new->str = (char *)kzalloc(count+1,GFP_KERNEL);
if(!new->str)
{
kfree(new);
return -ENOMEM;
}
if(copy_from_user(new->str,buf,count)){
kfree(new->str);
kfree(new);
return -EFAULT;
}
INIT_LIST_HEAD(&new->item);
list_add_tail(&new->item,&mylist->head);
mylist->file_size += count;
return count;
}
static int __init my_init(void)
{
//分配并初始化proc_head
//创建proc文件
struct proc_dir_entry *file;
mylist = (struct proc_head *)kzalloc(sizeof(*mylist), GFP_KERNEL);
mylist->file_size = 0;
INIT_LIST_HEAD(&mylist->head);
file = create_proc_entry("test_list",0644, NULL);
if (!file) {
kfree(mylist);
printk("Cannot create /proc/test_list");
return -1;
}
file->read_proc = my_proc_read;
file->write_proc = my_proc_write;
return 0;
}
static int __exit my_exit(void)
{
//remove_proc_entryl
//释放整个链表
//list_for_each_entry_safe
//list_del();
//kfree();
//释放链表头kfree(mylist)
struct proc_item *tmp1,*tmp2;
remove_proc_entry("test_list",NULL);
list_for_each_entry_safe(tmp1,tmp2,&mylist->head,item){
list_del(&tmp1->item);
kfree(tmp1->str);
kfree(tmp1);
}
kfree(mylist);
return 0;
}
module_init(my_init);
module_exit(my_exit);
MODULE_AUTHOR("LC");
MODULE_LICENSE("GPL");
利用my_proc_read函数从一个 /proc 项中读取数据(从内核空间到用户空间)。这个函数的原型如下:
static int my_proc_read(char *page,char **start,off_t off,int count,int *eof,void *data)
page
参数是这些数据写入到的位置,其中 count
定义了可以写入的最大字符数。在返回多页数据(通常一页是 4KB)时,我们需要使用start
和off
参数。当所有数据全部写入之后,就需要设置 eof
(文件结束参数),data为私有数据,这里一般为NULL。
这里使用链表存储数据,可以使用write_proc向/proc中写入多项数据
static int my_proc_write(struct file *filp,const char __user *buf,unsigned long count,void *data)
filp
参数实际上是一个打开文件结构(我们可以忽略这个参数)。buff
参数是传递给您的字符串数据。缓冲区地址实际上是一个用户空间的缓冲区,因此我们不能直接读取它。count
参数定义了在buff
中有多少数据要被写入。data为私有数据,默认为NULL就行。
创建并初始化proc文件
file = create_proc_entry("test_list",0644, NULL);
销毁链表
list_for_each_entry_safe(tmp1,tmp2,&mylist->head,item)
Makefile
obj-m := proc_list.o
KERNEL := /lib/modules/`uname -r`/build/
all:
make -C $(KERNEL) M=`pwd` modules
install:
make -C $(KERNEL) M=`pwd` modules_install
depmod -A
clean:
make -C $(KERNEL) M=`pwd` clean
编译完成后,生成proc_list.ko文件。挂载模块。
insmod proc_list.ko
挂载成功后,会在/proc下生成test_list文件,可以对这个文件进行读或者写操作。
读操作:
cat /proc/test_list
还没有写入数据之前,读不出信息。
echo > hello /proc/test_list
echo > world /proc/test_list
此时再cat
cat /proc/test_list
在终端上上得到如下信息
lc@LC--PC:$ cat /proc/test_list
hello
world
注:程序在rhel6.4系统下运行没问题,在ubuntu下运行好像需要在proc_list.c中加#include <linux/slab.h>头文件