字符设备驱动
一。字符设备程序思路
字符设备是以不固定长度与系统进行数据传输。字符设备发出请求时,IO读写就发生了。而块设备需要一块内存缓冲区。
1.1 设备号分配
初始化(init): 首先进行设备号的分配,完成对设备主设备号的分配。其中涉及到结构体有dev_t, cdev。 dev_t中存储有主设备号和次设备号,可通过MAJOR(dev_t dev)和MINOR(det_t dev)获取int类型的设备号。通过MKDEV(int major,int minor)可转换成dev_t类型。而cdev类型是
struct cdev { struct kobject kobj; // 内嵌的kobject对象 struct module *owner; // 所属模块 const struct file_operations *ops; // 文件操作结构体 struct list_head list; //linux内核所维护的链表指针 dev_t dev; //设备号 unsigned int count; //设备数目 };
可将cdev结构体嵌入自己的设备结构体中。(但是我没有这么做,我用了my_device用于存储自己的设备信息)。
设备分配可采用静态分配和动态分配两种方法。静态分配register_chrdev_region(dev_t first, unsigned int count, char *name),第一个参数提供了分配设备号的起始值,第二个参数为连续设备号的个数(我使用了两个),第三个参数为设备名。成功分配返回值为0。动态分配alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count c,char *name)。设备是否使用动态分配,通过一个宏进行初始化,再通判断char_driver_major的值判断是否进行动态分配。传入dev_t的major值为0才可以进行动态分配。
通过查看/proc/devices可以得到新分配的主设备号
动态分配结果:
1.2初始化cdev
两种方法: 第一种通过cdev_alloc(),再将cdev->ops = &myfops;
第二种:void cdev_init(struct cdev *cdev, cdev file_operation *fops)
直接将cdev中的ops赋值为文件操作结构体的地址。
而file_operation结构体的变量,在创建时就进行了初始化,内含多个函数如open,read,write等。根据书本的要求进行了定义,具体操作。
“结构体 file_operations中存储着内核模块中执行这项操作的函数的地址” “struct inode一个是代表文件,struct file一个是代表打开的文件”
其实我还没有弄懂文件操作结构体的意义,以及如何传入变量的,之后深入研究再另记。
1.3 注册设备到系统中
通过cdev_add(struct cdev *dev, dev_t num, unsigned int count);
2.设备操作实现(待完成)
3.内存分配
首先分配自己定义设备结构体的空间,并用memset将当前内存区全部设置为0。再通过指针将my_device1[i].size也就是储存设备大小信息的字段,赋值为CHAR_DRIVER_SIZE,储存单个设备的储存信息大小(4096)。再将data区初始化为0,以待之后读取输出使用。在write函数中,通过copy_from_user(void *to, const void__user *from, unsigned long count)会将buf内的内容写出给设备的data区。
4.注销设备
cdev_del
unrigister_chrdev_region用法同上。
二。驱动代码
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/fs.h> //file structure, #include <linux/cdev.h>//static allo. and register #include <linux/device.h> //unknown #include <asm/uaccess.h> #include <linux/slab.h>//kmalloc MODULE_AUTHOR("zzg"); #ifndef CHAR_DRIVER_MAJOR #define CHAR_DRIVER_MAJOR 0 #endif #ifndef CHAR_DRIVER_SIZE #define CHAR_DRIVER_SIZE 4096 #endif #ifndef DEV_NUM #define DEV_NUM 2 #endif struct my_device{ char *data; unsigned long size; }; int char_driver_major=CHAR_DRIVER_MAJOR;//this value can be given from console,by using function MODULE_PARM. struct my_device *my_device1; struct cdev cdev; /*char_driver_open*/ int char_driver_open(struct inode*inode, struct file *filp){ struct my_device *dev; unsigned int minor_number=iminor(inode); if(minor_number>=DEV_NUM) return -ENODEV; dev=my_device1; filp->private_data=dev; return 0; } /*char_driver_release*/ int char_driver_release(struct inode *inode, struct file * filp){ return 0; } /*char_driver_read read function ----fread. this function tells kernel how many datas should be read from user space, and than update the position in file. */ static ssize_t char_driver_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 my_device *dev=filp->private_data; if(p>=CHAR_DRIVER_SIZE) return 0; if(count >CHAR_DRIVER_SIZE -p) count=CHAR_DRIVER_SIZE-p; if(copy_to_user(buf,(void*)(dev->data+p),count)) { ret=-EFAULT; } else{ *ppos +=count; ret=count; printk(KERN_INFO "read %d bytes(s) from %d\n",count,p); } return ret; } /*char_driver_weite */ static ssize_t char_driver_write(struct file *filp, char __user *buf, size_t size, loff_t *ppos){ unsigned long p= *ppos; unsigned int count =size; int ret=0; struct my_device *dev=filp->private_data; if(p>=CHAR_DRIVER_SIZE) return 0; if(count > CHAR_DRIVER_SIZE-p) count=CHAR_DRIVER_SIZE-p; if(copy_from_user(dev->data+p,buf,count)) { ret=-EFAULT; } else{ *ppos +=count; ret=count; printk(KERN_INFO "written %d bytes(s) from %d\n",count,p); } return ret; } /*llseek:change the current read/write position in file*/ static loff_t char_driver_llseek(struct file *filp,loff_t offset, int whence){ loff_t newpos; switch(whence){ case 0: newpos=offset; break; case 1: newpos=filp->f_pos+offset; break; case 2: newpos=CHAR_DRIVER_SIZE+offset; //what to doSIZE?? break; defualt: return -EINVAL; } if(newpos<0) return -EINVAL; filp->f_pos = newpos; return newpos; } //file_operation initialization(function pointer).the specific realization of these functions will be directly used from the given funtions. struct file_operations char_driver_fops ={ .owner = THIS_MODULE, .llseek=char_driver_llseek, .read=char_driver_read, .write=char_driver_write, // .ioctl=char_driver_ioctl, .open=char_driver_open, .release=char_driver_release, }; //step1:firstly the Char_device must be registered. static int char_driver_init(void){ //initialization int result,i; dev_t dev_test=MKDEV(char_driver_major,0);//transfer device in dev_t structure if(char_driver_major){ result=register_chrdev(dev_test,2,"dev_test");//2 is the sequential device number.should not be too large, because the number could be overlapped with the next major number from other device. } else{ result=alloc_chrdev_region(&dev_test,0,2,"dev_test"); char_driver_major=MAJOR(dev_test); } if(char_driver_major){ printk(KERN_INFO "device_number %d alreader allocated\n", char_driver_major); } // result value should be examined if any erro existed, but we assume the dynamic number is already allocated. //step2:initiate cdev by using cdev.init(struct cdev *cdev, struct file_operations *fops). there will be a connection between cden and file operation cdev_init(&cdev,&char_driver_fops); cdev.owner=THIS_MODULE; cdev.ops=&char_driver_fops; //step3 cdev_add tell the kernel the information about the structure cdev_add(&cdev,MKDEV(char_driver_major,0), DEV_NUM ); //step4: memory allocation for device my_device1=kmalloc(DEV_NUM * sizeof(struct my_device), GFP_KERNEL); if (!my_device1){ result= -ENOMEM; goto fail_malloc; } memset(my_device1,0,sizeof(struct my_device)); //allocate for device for (i=0; i<DEV_NUM;i++){ my_device1[i].size=CHAR_DRIVER_SIZE; my_device1[i].data=kmalloc(CHAR_DRIVER_SIZE,GFP_KERNEL); memset(my_device1[i].data,0,CHAR_DRIVER_SIZE); } return 0; fail_malloc: unregister_chrdev_region(MKDEV(char_driver_major,0),2); return result; } static void char_driver_exit(void){ cdev_del(&cdev); kfree(my_device1); unregister_chrdev_region(MKDEV(char_driver_major,0),2); } MODULE_LICENSE("GPL"); module_init(char_driver_init); module_exit(char_driver_exit);
3. 驱动测试
借助网上代码http://blog.chinaunix.net/uid-11829250-id-337300.html 进行测试。
首先通过在/dev下创建设备 mknod dev_test c 250 0
gcc -o mem memdevapp.c 编译测试文件。
4.总结及问题
对于字符设备驱动有了初步认识,但对于读取过程,包括初始化函数的调用过程,还需要深入了解。file_operation的函数实现,是设计的重点,因此还需更进一步进行学习。
主要问题:1.多个设备在分配设备号时,查看多个分配的结果
2.初始化函数调用的参数,是如何一个传递路径
3.内存分配问题