CTDIY means Copy to DIY.
首先来copy一个例程来试试字符设备到底是如何
//globalmem.c 本例程来源于《linux设备驱动开发详解》 #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<asm/io.h> #include<asm/system.h> #include<asm/uaccess.h> #define GLOBALMEM_SIZE 0x1000 //Max memory #define MEM_CLEAR 0x1 //clear all the memory #define GLOBALMEM_MAJOR 250 //the major static int globalmem_major = GLOBALMEM_MAJOR; /*globalmem 设备结构体*/ struct globalmem_dev{ struct cdev cdev; //cdev struct unsigned char mem[GLOBALMEM_SIZE]; //global memory }; struct globalmem_dev *globalmem_devp; //cdev pointer //file open operation int globalmem_open(struct inode *inode, struct file *filp) { filp->private_data = globalmem_devp; return 0; } //file release operation int globalmem_release(struct inode *inode, struct file *filp) { return 0; } //file ioctl operation static int globalmem_ioctl(struct inode *inodep, struct file *filp, unsigned int cmd, unsigned long arg) { struct globalmem_dev *dev = filp->private_data; //get the cdev struct pointer switch(cmd){ case MEM_CLEAR: memset(dev->mem, 0, GLOBALMEM_SIZE); printk(KERN_INFO "globalmem is set to zero\n"); break; default: return - EINVAL; } return 0; } //cdev read operation static ssize_t globalmem_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 globalmem_dev *dev = filp->private_data; //get the real length if(p >= GLOBALMEM_SIZE) return 0; if(count > GLOBALMEM_SIZE - p) count = GLOBALMEM_SIZE - p; //kernel space to the usr space if(copy_to_user(buf, (void *)(dev->mem + p),count)){ ret = - EFAULT; }else{ *ppos += count; ret = count; printk(KERN_INFO "read %u byte(s) form %lu \n", count, p); } return ret; } //cdev write operation static ssize_t globalmem_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 globalmem_dev *dev = filp->private_data; //get the cdev struct pointer //get the real length if(p >= GLOBALMEM_SIZE) return 0; if(count > GLOBALMEM_SIZE - p) count = GLOBALMEM_SIZE - p; //kernel space to the usr space if(copy_to_user((dev->mem + p), buf, count)){ ret = - EFAULT; }else{ *ppos += count; ret = count; printk(KERN_INFO "written %u byte(s) form %lu \n", count, p); } return ret; } //cdev seek operation static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig) { loff_t ret = 0; switch(orig){ case 0: if(offset < 0){ ret = - EINVAL; break; } if((unsigned int)offset > GLOBALMEM_SIZE){ ret = - EINVAL; break; } filp->f_pos = (unsigned int)offset; ret = filp->f_pos; break; case 1: if((filp->f_pos + offset) > GLOBALMEM_SIZE){ ret = - EINVAL; break; } if((filp->f_pos + offset) < 0){ ret = - EINVAL; break; } filp->f_pos += offset; ret = filp->f_pos; break; default: ret = - EINVAL; break; } return ret; } //the file control struct static const struct file_operations globalmem_fops = { .owner = THIS_MODULE, .llseek = globalmem_llseek, .read = globalmem_read, .write = globalmem_write, .unlocked_ioctl = globalmem_ioctl, .open = globalmem_open, .release = globalmem_release, }; //setup the cdev static void globalmem_setup_cdev(struct globalmem_dev *dev, int index) { int err, devno = MKDEV(globalmem_major, index); cdev_init(&dev->cdev, &globalmem_fops); dev->cdev.owner = THIS_MODULE; err = cdev_add(&dev->cdev, devno, 1); if(err) printk(KERN_NOTICE "Error %d adding globalmem %d", err, index); } //module init operation int globalmem_init(void) { int result; dev_t devno = MKDEV(globalmem_major,0); if(globalmem_major) result = register_chrdev_region(devno, 1, "globalmem"); else{ result = alloc_chrdev_region(&devno, 0, 1, "globalmem"); globalmem_major = MAJOR(devno); } if (result < 0) return result; globalmem_devp = kmalloc(sizeof(struct globalmem_dev), GFP_KERNEL); if (!globalmem_devp){ result = - ENOMEM; goto fail_malloc; } memset(globalmem_devp, 0, sizeof(struct globalmem_dev)); globalmem_setup_cdev(globalmem_devp, 0); return 0; fail_malloc: unregister_chrdev_region(devno, 1); return result; } //module remove operation void globalmem_exit(void) { cdev_del(&globalmem_devp->cdev); kfree(globalmem_devp); unregister_chrdev_region(MKDEV(globalmem_major, 0), 1); } MODULE_LICENSE("Dual BSD/GPL"); module_param(globalmem_major, int, S_IRUGO); module_init(globalmem_init); module_exit(globalmem_exit);
此版本在ubuntu12.04上运行通过,适用于3.2.0版本的内核
相较于书本上的例程有两处修改
1、在file_operations中ioctl类型的修改
//2.6版本 struct file_operations { …… int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); …… }; //3.2版本 struct file_operations { …… long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); …… }; //故在make的时候将报错: 初始值设定项里有未知的字段‘ioctl’ //只需将struct file_operations中对ioctl的调用 .ioctl 改为 .locked_ioctl即可
2、版本更换导致的头文件移位
//3.2中将报错提示缺少一下两个文件 /home/lufee/mydiraver//globalmem.c:193:2: 错误:隐式声明函数‘kmalloc’ [-Werror=implicit-function-declaration] /home/lufee/mydiraver//globalmem.c:210:2: 错误:隐式声明函数‘kfree’ [-Werror=implicit-function-declaration] //在原版中中无误,而3.2中将他们放入了linux/slab.h头文件中 //加入#inclue <linux/slab.h>即可
Makefile也贴一下吧
KVERS = $(shell uname -r) # Kernel modules obj-m += globalmem.o # Specify flags for the module compilation #EXTRA_CFLAGS=-g -O0 build:kernel_modules kernel_modules: make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules clean: make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean obj-m := globalmem.o modulename-objs := globalmem.o
通过make之后,产生.ko文件,随后对该字符设备进行如下测试
# insmod globalmem.ko //加载模块 # lsmod | grep globalmem //模块已经加载好了 globalmem 12827 0 # echo "hello world" > globalmem //写入hello world # cat globalmem //读出globalmem内容 hello world
至此,一个最简单的字符设备驱动程序完成了。
接着,开始一点点地剖析其中的道理。