一、概念
“virtualdisk 虚拟内存”的概念又指"GLOBALMEM 全局内存"
二、globalmem虚拟设备的作用
(1)、globalmem字符设备驱动中,分配一片内存大小为GLOBALMEM_SIZE(4K)的空间
(2)、提供对该片内存的读写、控制和定位函数(read,write,llseek)
(3)、用户进程能够通过linux系统调用访问这篇内存
三、实例解释,假设已经创建了一dev/globalmem
# echo 'hello world!' > /dev/globalmem //写入 hello world written 13 bytes(s) from 0 # cat /dev/ /dev/console /dev/globalmem /dev/null # cat /dev/globalmem //读取 read 4096 bytes(s) from 0 hello world! cat: read error: No such device or address//出现这样这里正确 #
由上面的实验可以看出,dev/globalmem 在内核中有了固定内存
四、使用方法
1、全局变量
#define VIRTUALDISK_SIZE 0x1000//内存为4k
#define MEM_CLEAR 0x1 //定义清除指令为0x01
#define VIRTUALDISK_MAJOR 250 //主设备号 250
int VirtualDisk_major = VIRTUALDISK_MAJOR;
struct VirtualDisk{
struct cdev cdev;//详细看cdev机制
unsigned char mem[VIRTUALDISK_SIZE ];
long count; /*记录设备目前被多少设备打开*/
};
struct VirtualDisk *VirtualDiskp;//定义全局指针变量VirtualDiskp,关键变量
2、在init()函数中:
1)、分配主设备号,
int result; dev_t devno = MKDEV(VirtualDisk_major, 0); if(VirtualDisk_major){ result = register_chrdev_region(devno, 1, "module"); }else{ result = alloc_chrdev_region(&devno, 0, 1, "module"); VirtualDisk_major = MAJOR(devno); } if(result < 0 ){ return result; }
2)、分配VirtualDiskp的内存空间(关键)
VirtualDiskp = kmalloc(sizeof(struct VirtualDisk), GFP_KERNEL); if(!VirtualDiskp){ result = -ENOMEM; goto fail_malloc; } memset(VirtualDiskp, 0, sizeof(struct VirtualDisk)); VirtualDisk_setup_cdev(VirtualDiskp, 0);//初始化VirtualDiskp的cdev成员 //..........................中间其他代码,包括return 0; fail_malloc://写在init函数中最后一行 unregister_chrdev_region(devno, 1); return result;
3、 VirtualDisk_setup_cdev()//添加cdev设备,初始化VirtualDiskp的cdev成员
static void VirtualDisk_setup_cdev(struct VirtualDisk *dev, int minorIndex){ int err; int devno = MKDEV(VirtualDisk_major, minorIndex); cdev_init(&dev->cdev, &module_drv_fops); dev->cdev.owner = THIS_MODULE; err = cdev_add(&dev->cdev, devno, 1);//将cdev内存与dev_t dev 关联 if(err){ printk("error %d cdev file added\n", err); } }
4、exit()函数中//1、删除cdev设备,释放VirtualDiskp空间,注销设备号
cdev_del(&VirtualDiskp->cdev); kfree(VirtualDiskp); unregister_chrdev_region(MKDEV(VirtualDisk_major, 0), 1);
总结,从2与4中可以看出只要设备不移除(rmmod ***.ko),VirtualDiskp就一直存在
5、设置访问,获取访问VirtualDiskp
1)、open(struct inode *inode, struct file *file)函数//设置通过设备文件(/dev/***)私有数据private_data指向设备结构体
file->private_data = VirtualDiskp;//将文件的私有数据private_data指向设备的结构体,在read、write、ioctl、llseek等函数通过private_data访问设备结构体 VirtualDiskp->count++; /*增加设备打开次数*/
2)、release(struct inode *inode, struct file *file)函数
VirtualDiskp->count--; /*减少设备打开次数*/
3)、write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
struct VirtualDisk *devp = file->private_data; /*获得设备结构体指针*/ copy_from_user(devp->mem + p, buf, countt)//将字符存与VirtualDiskp中
4)、read(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
struct VirtualDisk *devp = file->private_data; /*获得设备结构体指针*/ copy_to_user(buf, (void*)(devp->mem + p), countt)
5)、ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
struct VirtualDisk *devp = file->private_data;/*获得设备结构体指针*/ switch (cmd) { case MEM_CLEAR:/*设备内存清零*/ memset(devp->mem, 0, VIRTUALDISK_SIZE); printk(KERN_INFO "VirtualDisk is set to zero\n"); break; default: return - EINVAL; }
总结: 可以看出file->private_data是个关键的词,它是设备文件的私有空间(/dev/***只是这个文件的映射,真正的空间在内核中,详细见《[arm驱动]busybox根文件系统mdev的详解》)
五、模板实例
//“module_drv”,"module_","module" #include <linux/module.h>//模块所需的大量符号和函数定义 #include <linux/kernel.h> #include <linux/fs.h>//文件系统相关的函数和头文件 #include <linux/init.h> //指定初始化和清除函数 #include <linux/delay.h> #include <linux/cdev.h> //cdev结构的头文件包含<linux/kdev_t.h> #include <linux/device.h> #include <linux/mm.h> //#include <linux/sched.h>//包含驱动程序使用的大部分内核API的定义,包括睡眠函数以及各种变量声明 #include <asm/uaccess.h>//在内核和用户空间中移动数据的函数 #include <asm/irq.h> #include <asm/io.h> #include <asm/arch/regs-gpio.h> #include <asm/hardware.h> #define VIRTUALDISK_SIZE 0x1000//4k #define MEM_CLEAR 0x1 #define VIRTUALDISK_MAJOR 250 int VirtualDisk_major = VIRTUALDISK_MAJOR; struct VirtualDisk{ struct cdev cdev;//详细看cdev机制 unsigned char mem[VIRTUALDISK_SIZE ]; long count; /*记录设备目前被多少设备打开*/ }; static struct class *module_class; static struct class_device *module_class_dev; struct VirtualDisk *VirtualDiskp; static int module_drv_open(struct inode *inode, struct file *file) { printk("module_dev read\n"); file->private_data = VirtualDiskp; VirtualDiskp->count++; /*增加设备打开次数*/ return 0; } static int module_drv_release(struct inode *inode, struct file *file) { printk("module_dev release\n"); VirtualDiskp->count--; /*减少设备打开次数*/ return 0; } /*seek文件定位函数:seek()函数对文件定位的起始地址可以是文件开头(SEEK_SET,0)、当前位置(SEEK_CUR,1)、文件尾(SEEK_END,2)*/ static loff_t module_drv_llseek(struct file *file, loff_t offset, int origin){ loff_t ret = 0;/*返回的位置偏移*/ switch (origin) { case SEEK_SET: /*相对文件开始位置偏移*/ if (offset < 0)/*offset不合法*/ { ret = - EINVAL; /*无效的指针*/ break; } if ((unsigned int)offset > VIRTUALDISK_SIZE)/*偏移大于设备内存*/ { ret = - EINVAL; /*无效的指针*/ break; } file->f_pos = (unsigned int)offset; /*更新文件指针位置*/ ret = file->f_pos;/*返回的位置偏移*/ break; case SEEK_CUR: /*相对文件当前位置偏移*/ if ((file->f_pos + offset) > VIRTUALDISK_SIZE)/*偏移大于设备内存*/ { ret = - EINVAL;/*无效的指针*/ break; } if ((file->f_pos + offset) < 0)/*指针不合法*/ { ret = - EINVAL;/*无效的指针*/ break; } file->f_pos += offset;/*更新文件指针位置*/ ret = file->f_pos;/*返回的位置偏移*/ break; default: ret = - EINVAL;/*无效的指针*/ break; } return ret; } /*设备控制函数:ioctl()函数接受的MEM_CLEAR命令,这个命令将全局内存的有效数据长度清零,对于设备不支持的命令,ioctl()函数应该返回-EINVAL*/ static int module_drv_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg){ struct VirtualDisk *devp = file->private_data;/*获得设备结构体指针*/ switch (cmd) { case MEM_CLEAR:/*设备内存清零*/ memset(devp->mem, 0, VIRTUALDISK_SIZE); printk(KERN_INFO "VirtualDisk is set to zero\n"); break; default: return - EINVAL; } return 0; } /*读函数:读写函数主要是让设备结构体的mem[]数组与用户空间交互数据,并随着访问字节数变更返回用户的文件读写偏移位置*/ static ssize_t module_drv_read(struct file *file, const char __user *buf, size_t count, loff_t * ppos) { printk("module_dev read\n"); unsigned long p = *ppos; /*记录文件指针偏移位置*/ unsigned int countt = count;/*记录需要读取的字节数*/ int ret = 0; /*返回值*/ struct VirtualDisk *devp = file->private_data; /*获得设备结构体指针*/ /*分析和获取有效的读长度*/ if (p >= VIRTUALDISK_SIZE ) /*要读取的偏移大于设备的内存空间*/ return countt ? - ENXIO: 0;/*读取地址错误*/ if (countt > VIRTUALDISK_SIZE - p)/*要读取的字节大于设备的内存空间*/ countt = VIRTUALDISK_SIZE - p;/*将要读取的字节数设为剩余的字节数*/ /*内核空间->用户空间交换数据*/ if (copy_to_user(buf, (void*)(devp->mem + p), countt)) { ret = - EFAULT; } else { *ppos += countt; ret = countt; printk("read %d bytes(s) is %ld\n", countt, p); } printk("bytes(s) is %s\n", buf); return ret; } /* file 是文件指针,count 是请求的传输数据长度,buff 参数是指向用户空间的缓冲区,这个缓冲区或者保存要写入的数据,或者是一个存放新读入数据的空缓冲区,该地址在内核空间不能直接读写,ppos 是一个指针指向一个"long offset type"对象, 它指出用户正在存取的文件位置. 返回值是一个"signed size type。写的位置相对于文件开头的偏移。 */ static ssize_t module_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) { printk("module_dev write\n"); unsigned long p = *ppos; /*记录文件指针偏移位置*/ int ret = 0; /*返回值*/ unsigned int countt = count;/*记录需要写入的字节数*/ struct VirtualDisk *devp = file->private_data; /*获得设备结构体指针*/ /*分析和获取有效的写长度*/ if (p >= VIRTUALDISK_SIZE )/*要写入的偏移大于设备的内存空间*/ return countt ? - ENXIO: 0;/*写入地址错误*/ if (countt > VIRTUALDISK_SIZE - p)/*要写入的字节大于设备的内存空间*/ countt = VIRTUALDISK_SIZE - p;/*将要写入的字节数设为剩余的字节数*/ /*用户空间->内核空间*/ if (copy_from_user(devp->mem + p, buf, countt)) ret = - EFAULT; else { *ppos += countt;/*增加偏移位置*/ ret = countt;/*返回实际的写入字节数*/ printk("written %d bytes(s) from%ld\n", countt, p); } return ret; return 0; } static struct file_operations module_drv_fops = { .owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */ .open = module_drv_open, .read = module_drv_read, .write = module_drv_write, .release = module_drv_release, .llseek = module_drv_llseek, .ioctl = module_drv_ioctl, }; /*将 cdev 结构嵌入一个你自己的设备特定的结构,你应当初始化你已经分配的结构使用以上函数,有一个其他的 struct cdev 成员你需要初始化. 象 file_operations 结构,struct cdev 有一个拥有者成员,应当设置为 THIS_MODULE,一旦 cdev 结构建立, 最后的步骤是把它告诉内核, 调用: cdev_add(&dev->cdev, devno, 1);*/ static void VirtualDisk_setup_cdev(struct VirtualDisk *dev, int minorIndex){ int err; int devno = MKDEV(VirtualDisk_major, minorIndex); cdev_init(&dev->cdev, &module_drv_fops); dev->cdev.owner = THIS_MODULE; err = cdev_add(&dev->cdev, devno, 1); if(err){ printk("error %d cdev file added\n", err); } } static int module_drv_init(void) { int result; dev_t devno = MKDEV(VirtualDisk_major, 0); if(VirtualDisk_major){ result = register_chrdev_region(devno, 1, "module"); }else{ result = alloc_chrdev_region(&devno, 0, 1, "module"); VirtualDisk_major = MAJOR(devno); } if(result < 0 ){ return result; } VirtualDiskp = kmalloc(sizeof(struct VirtualDisk), GFP_KERNEL); if(!VirtualDiskp){ result = -ENOMEM; goto fail_malloc; } memset(VirtualDiskp, 0, sizeof(struct VirtualDisk)); VirtualDisk_setup_cdev(VirtualDiskp, 0); module_class = class_create(THIS_MODULE, "module_drv"); if (IS_ERR(module_class)) return PTR_ERR(module_class); module_class_dev = class_device_create(module_class, NULL, MKDEV(VirtualDisk_major, 0), NULL, "module"); /* /dev/xyz */ if (IS_ERR(module_class_dev)) return PTR_ERR(module_class_dev); return 0; fail_malloc: unregister_chrdev_region(devno, 1); return result; } static void module_drv_exit(void) { cdev_del(&VirtualDiskp->cdev); kfree(VirtualDiskp); unregister_chrdev_region(MKDEV(VirtualDisk_major, 0), 1); class_device_unregister(module_class_dev); class_destroy(module_class); } module_init(module_drv_init); module_exit(module_drv_exit); MODULE_LICENSE("GPL");
六、Makefile中是你要用的编译过的内核路径
#module_dev.c KERN_DIR = /workspacearm/linux-2.6.2.6 all: make -C $(KERN_DIR) M=`pwd` modules #cp module_dev.ko /opt/fsmini/ clean: make -C $(KERN_DIR) M=`pwd` modules clean rm -rf modules.order obj-m += module_dev.o