memdev.c
/************************************************************************** 文件名: memdev.c 日期: 2013/06/03 头文件: memdev.h 功能: 简单字符驱动(开辟一块内存当做字符驱动进行读写) 此为驱动部分 环境: Redhat企业版5 内核版本2.6.18-53.el5 作者: Hao 流程: 1.分配设备号(a.静态申请 b.动态分配) 2.创建设备文件(a.手动创建mknod(需要设备号)注“设备名和设备文件名” b.自动创建) 3.设备注册(a.设备注册分配 b.设备注册初始化 c.设备注册添加) 4.实现file_operation中的函数 5.设备注销(cdev_del) 6.设备号注销 ***************************************************************************/ #include <linux/module.h> #include <linux/types.h> #include <linux/fs.h> #include <linux/errno.h> #include <linux/mm.h> #include <linux/sched.h> #include <linux/init.h> #include <linux/cdev.h> #include <linux/slab.h> #include <linux/poll.h> #include <linux/device.h> #include <asm/io.h> #include <asm/system.h> #include <asm/uaccess.h> #include <asm/atomic.h> #include "memdev.h" MODULE_AUTHOR("Hao"); MODULE_LICENSE("GPL"); static int mem_major=MEMDEV_MAJOR; //定义主设备号(定义一个全局变量在read write memdev_exit中都能用 ) module_param(mem_major, int, S_IRUGO);//接收模块参数主设备号 struct mem_dev *mem_devp; //定义mem设备描述结构体指针 struct cdev c_dev; //设备注册分配 替换了struct cdev *cdev_alloc() 全局变量都可以使用 struct class *mem_class; /*设备的类*/ /************************************************************************** 函数名: mem_open 函数功能: 文件打开函数 使private_data指向 字符驱动模块 函数参数: inode存储文件物理信息的结构(包含设备号) file结构 函数返回值: 返回0为正常执行 ***************************************************************************/ int mem_open(struct inode *inode,struct file *filp) { struct mem_dev *dev; int num = MINOR(inode->i_rdev); //获得次设备号 if (num >= MEMDEV_NR_DEVS) return -ENODEV; //判断是否次设备号 不正确 dev = &mem_devp[num]; filp->private_data = dev; /*不是很了解private_data 宋宝华的linux设备驱动开发详解,93页写到私有数据指针private_data在设备驱动中背广泛使用,大多数指向设备驱动自定义用于描述设备的结构体。*/ return 0; } /************************************************************************** 函数名: mem_release 函数功能: 文件关闭函数 函数参数: inode存储文件物理信息的结构(包含设备号) file结构 函数返回值: 返回0为正常执行 ***************************************************************************/ int mem_release(struct inode *inode, struct file *filp) { return 0; } /************************************************************************** 函数名: mem_read 函数功能: 文件读取函数 函数参数: struct file *filp(open的时候系统产生的结构,根据inode来的) char __user *buf 存储读取数据的空间 size_t size 读取的大小 size_t应该就是typedef unsigned int的类型 loff_t *ppos 当前文件指针的位置 函数返回值: 返回ret 正常应该是size的值 ***************************************************************************/ static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos) { unsigned long p = *ppos; //保存下文件指针的当前位置 unsigned int count=size; int ret = 0; struct mem_dev *dev = filp->private_data; //private_data是一个空指针用来接收各种数据的传递 if (p >= MEMDEV_SIZE) //判断文件指针是否有效 return 0; if (count > MEMDEV_SIZE - p)//判断读取的大小是否比剩余的内存大小还大 count = MEMDEV_SIZE - p; if (copy_to_user(buf, (void*)(dev->data + p), count))//把字符设备模块从内核空间拷贝到用户空间 { ret = - EFAULT; } else { *ppos += count; ret = count; printk(KERN_EMERG "read %d bytes(s) from %ld\n", count, p); } return ret; } /************************************************************************** 函数名: mem_write 函数功能: 文件写函数 函数参数: 参数同read函数 注意:struct file *filp,char __user *buf和size_t size是内核从应用层api-fread中传递下来的 具体是怎么传递的不详 看内核代码 Read_write.c 函数返回值: 返回size为正常执行 ***************************************************************************/ static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos) { unsigned long p = *ppos; unsigned int count = size; int ret = 0; struct mem_dev *dev = filp->private_data; if (p >= MEMDEV_SIZE)//MEMDEV_SIZE为内存最大值 return 0; if (count > MEMDEV_SIZE - p) count = MEMDEV_SIZE - p; if (copy_from_user(dev->data + p, buf, count)) //从应用程序的角度看写是从用户到内核 ret = - EFAULT; else { *ppos += count;// 不知道这里用filp->f_pos+=count行不行 因为我觉得filp->f_pos和ppos是一个地址 ret = count; printk(KERN_EMERG "written %d bytes(s) from %ld\n", count, p); } return ret; } /************************************************************************** 函数名: mem_llseek 函数功能: 文件位置指针定位函数 函数参数: struct file *filp loff_t offset偏移量 int whence 三种可能SEEK_SET SEEK_CUR SEEK_END 函数返回值: 返回0为正常执行 ***************************************************************************/ static loff_t mem_llseek(struct file *filp, loff_t offset, int whence) { loff_t newpos; //定义中间变量 switch(whence) { case 0: /* SEEK_SET */ newpos = offset; break; case 1: /* SEEK_CUR */ newpos = filp->f_pos + offset; break; case 2: /* SEEK_END */ newpos = MEMDEV_SIZE -1 + offset; break; default: /* can't happen */ return -EINVAL; } if ((newpos<0) || (newpos>MEMDEV_SIZE)) //判断newpos是否有效 return -EINVAL; filp->f_pos = newpos; //赋值 定位 return newpos; } /************************************************************************** 函数名: mem_map 函数功能: 映射函数 将物理地址和虚拟地址建立页表 函数参数: 函数返回值: 返回0为正常执行 ***************************************************************************/ static int mem_mmap(struct file *filp, struct vm_area_struct *vma) { struct mem_dev *dev = filp->private_data; vma->vm_flags|=VM_IO; vma->vm_flags|=VM_RESERVED; if(remap_pfn_range(vma, vma->vm_start, virt_to_phys(dev->data)>>PAGE_SHIFT,vma->vm_end-vma->vm_start, vma->vm_page_prot)) return -EAGAIN; printk(KERN_EMERG "NOW is in kernel mmap!!!\n"); return 0; } static const struct file_operations mem_fops = //定义此字符设备的file_operations { //这里是对结构体整体赋值的方式 .owner = THIS_MODULE, .llseek = mem_llseek, //函数名都可以自己定义 都是函数指针 .mmap = mem_mmap, .read = mem_read, .write = mem_write, .open = mem_open, .release = mem_release, }; /************************************************************************** 函数名: memdev_init 函数功能: 内核模块入口函数 函数参数: 无 函数返回值: 返回0为正常执行 返回result为设备号获取不成功 返回- ENOMEM为内存分配不成功 ***************************************************************************/ static int memdev_init() { int i; int result; dev_t dev_num=MKDEV(mem_major,0);//将主设备号和次设备号转换成32位的设备号给 静态申请设备号用的 if(mem_major) result=register_chrdev_region(dev_num,2,"newmemdev");//静态申请设备号为dev_num 2个设备 设备名为“newmemdev” else { result=alloc_chrdev_region(&dev_num,0,2,"newmemdev");//动态分配设备号 设备名可以在/proc/devices文件中找 与/dev路径找文件不同 因为一个设备文件 一个次设备号 mem_major = MAJOR(dev_num);//获得主设备号 } if (result < 0) { return result; //判断动态分配是否成功 不成功则退出函数 } /*设备注册 分配已经在前面完成了 为全局变量*/ cdev_init(&c_dev,&mem_fops);//设备注册初始化 将设备号dev_num和file_operation建立联系 c_dev.owner = THIS_MODULE;//*****************貌似可以屏蔽吧********************* cdev_add(&c_dev,dev_num,2);//设备注册 添加 设备号为dev_num 设备数为2个 mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);//开辟2个字符设备模块大小的空间 if (!mem_devp) /*判断是否分配内存成功*/ { result = - ENOMEM; goto fail_malloc; } memset(mem_devp, 0, MEMDEV_NR_DEVS * sizeof(struct mem_dev)); //将初始化2个字符设备模块大小的空间清零 mem_class = class_create(THIS_MODULE, "mem_class_name");//创建设备类 device_create(mem_class, NULL, MKDEV(mem_major, 0), NULL, "memdev0"); //创建设备文件 文件名为memdev0 for (i=0; i < MEMDEV_NR_DEVS; i++) //对字符设备模块结构赋值 { mem_devp[i].size = MEMDEV_SIZE; mem_devp[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);//因为data是指针所以继续对其开辟4k空间了 memset(mem_devp[i].data, 0, MEMDEV_SIZE); } return 0; fail_malloc: //如果内存分配不成功直接注销设备号 unregister_chrdev_region(dev_num, 1);/*但是不明白的是 1.为什么只注销一个次设备的设备号 2.为什么不先注销cdev_dev*/ return result;//虽然这里不懂 但不深究了 因为很少出现内存分配不成功的情况(在学习练习的时候) } /************************************************************************** 函数名: memdev_exit 函数功能: 内核模块退出函数 函数参数: 无 函数返回值: 无 ***************************************************************************/ static void memdev_exit(void) { cdev_del(&c_dev);//设备注销 kfree(mem_devp);//释放开辟的内存空间 /*这里貌似没有释放mem_devp[i].data的指针 是不是应该kfree(mem_devp[1].data)*/ /*这里有一个问题就是那两个内存分配 为什么要分配两次 为什么释放一次 我试过貌似第二次不分配也可以*/ device_destroy(mem_class, MKDEV(mem_major, 0));/*注销设备*/ class_destroy(mem_class); /*删除类*/ unregister_chrdev_region(MKDEV(mem_major, 0), 2);//设备号注销 } module_init(memdev_init); module_exit(memdev_exit);
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/mman.h> #include <fcntl.h> int main() { int fd; char *dev_buf; char buf[100]; if(-1==(fd=open ("/dev/memdev0", O_RDWR))) { printf("open dev0 error\n"); _exit(EXIT_FAILURE); } dev_buf = (char*)mmap(NULL,100, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); read(fd, buf, strlen(buf)); printf("now buf is %s\n",buf); strcpy(dev_buf,"hello world hao!\0"); printf("now is reading!\n"); munmap(dev_buf,100); close(fd); }
在app的函数里面可以先用mmap写一个值,再munmap断开映射,再映射mmap去读,如果读出数据 说明mmap测试成功。