一个完整的驱动程序及功能测试
目录 |
驱动程序的编写
一个驱动程序通常包括3个部分:.h头文件、.c文件和Makefile。其中,.h头文件一般会定义一些宏和这个设备相关的结构体;.c文件是驱动功能的实现;Makefile则是对模块生成规则的一些定义。
在这个例子中,我们定义了一个功能非常简单,结构却十分完整的字符型驱动程序。之所以说功能简单,是因为这个设备只能输出一些调试信息、存储一个int型的数据;而结构完整,则是因为它具备了在"/sys/"、"/dev/"、"/proc/"三个目录下生成设备文件的能力,能在"/dev/"下自动挂载,而且还提供了ioctl接口功能。
首先给这个虚拟设备取个名称——"vircdev",然后我们分别来说一下这三个文件:
一、头文件
//vircdev.h #ifndef _VIRCDEV_H_ #define _VIRCDEV_H_ #define VIRCDEV_NUM (1) #define VIRCDEV_CLASS_NAME "vircdev_class" #define VIRCDEV_DEVICE_NAME "vircdev_device" #define VIRCDEV_NODE_NAME "vircdev_node" #define VIRCDEV_PROC_NAME "vircdev_proc" #define VIRCDEV_IOC_MAGIC 'k' #define VIRCDEV_IOHI _IO(VIRCDEV_IOC_MAGIC, 0) #define VIRCDEV_IOWBYTE _IOW(VIRCDEV_IOC_MAGIC, 1, int) #define VIRCDEV_IORBYTE _IOR(VIRCDEV_IOC_MAGIC, 2, int) struct vircdev_cntx { int r1; struct semaphore sem; struct cdev cdev; }; #endif
这里首先是定义了设备的个数,因为linux中设备是由一个主设备号和一个副设备号区分的,而系统只会区分主设备号,并在一些通用文件访问(如read、write)时转向设备对应的驱动程序,而副设备号系统则是完全漠视,直接传给驱动程序,由驱动进行处理的,这里的VIRCDEV_NUM则代表了设备的个数(自然也决定了副设备号的个数)。
接着,是定义了一些名称,这些名称将会在创建设备文件时使用。
而接下来这些宏,如VIRCDEV_IOHI,则是用于ioctl中的cmd命令;VIRCDEV_IOC_MAGIC是我们通常所说的幻数,这个数的定义要比较小心,一般是取系统中未使用的,以确保ioctl中cmd命令的唯一性,这样在对错误的文件使用这些命令时便不会出现不可预知的错误。
二、vircdev.c文件
1、全局变量的定义:
/*====global====*/ static int g_vircdev_major = 0; static int g_vircdev_minor = 0; struct vircdev_cntx *g_vircdev_ptr = NULL; struct class *g_vircdev_class = 0; static struct file_operations g_vircdev_fops = { .owner = THIS_MODULE, .read = vircdev_read, .write = vircdev_write, .unlocked_ioctl = vircdev_unlocked_ioctl, .open = vircdev_open, .release = vircdev_release, };
g_vircdev_major、g_vircdev_minor为主副设备号;g_vircdev_ptr为设备结构体变量指针;g_vircdev_class是在构建/dev/设备文件时需要保留的一个struct class中间变量,在exit时要使用这个变量进行destroy;g_vircdev_fops,这个就不用过多解释了,设备的大部分操作都在这里面。
2、定义传统设备文件的访问方法,主要是定义vircdev_read、vircdev_write、vircdev_unlocked_ioctl、vircdev_open和vircdev_release:
ssize_t vircdev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { printk(KERN_ALERT "vircdev read.\n"); return 0; } ssize_t vircdev_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { printk(KERN_ALERT "vircdev write.\n"); return count; } long vircdev_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { int err = 0; long retval = 0; /* * extract the type and number bitfields, and don't decode * wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok() */ if (_IOC_TYPE(cmd) != VIRCDEV_IOC_MAGIC) return -ENOTTY; //if (_IOC_NR(cmd) > VIRCDEV_IOC_MAXNR) return -ENOTTY; /* * to verify *arg is in user space */ if (_IOC_DIR(cmd) & _IOC_READ) err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd)); else if (_IOC_DIR(cmd) & _IOC_WRITE) err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd)); if (err) return -EFAULT; switch(cmd) { case VIRCDEV_IOHI: printk(KERN_ALERT "ioctl:you said hi.\n"); break; case VIRCDEV_IOWBYTE: //retval = __get_user(g_vircdev_ptr->r1, (int __user *)arg); g_vircdev_ptr->r1 = (int __user *)arg; printk(KERN_ALERT "ioctl:write r1 to %i.\n", g_vircdev_ptr->r1); break; case VIRCDEV_IORBYTE: retval = __put_user(g_vircdev_ptr->r1, (int __user *)arg); printk(KERN_ALERT "ioctl:read r1:%i.\n", g_vircdev_ptr->r1); break; default: printk(KERN_ALERT "ioctl:you got the wrong command.\n"); break; } return retval; } int vircdev_open(struct inode *inode, struct file *filp) { return 0; } int vircdev_release(struct inode *inode, struct file *filp) { return 0; }
3、动态分配设备号函数:
//alloc device major static int vircdex_alloc_major(void) { dev_t devt = 0; int result = 0; result = alloc_chrdev_region(&devt, g_vircdev_minor, VIRCDEV_NUM, VIRCDEV_NODE_NAME); g_vircdev_major = MAJOR(devt); return result; } static int vircdev_release_major(void) { dev_t devt = MKDEV(g_vircdev_major, g_vircdev_minor); unregister_chrdev_region(devt, 1); return 0; }
4、字符设备安装函数:
static int vircdev_setup_dev(struct vircdev_cntx *dev) { int err, devno = MKDEV(g_vircdev_major, g_vircdev_minor); cdev_init(&(dev->cdev), &g_vircdev_fops); dev->cdev.owner = THIS_MODULE; err = cdev_add(&dev->cdev, devno, 1); if(err){ return err; } //init_MUTEX(&(dev->sem)); sema_init(&(dev->sem), 1); return 0; } static int vircdev_unsetup_dev(struct vircdev_cntx *dev) { cdev_del(&(dev->cdev)); return 0; }
5、创建设备文件函数:
static int vircdev_create_devfiles(dev_t devt) {//, const struct device_attribute *attr) { int err = -1; struct device *dev = NULL; g_vircdev_class = class_create(THIS_MODULE, VIRCDEV_CLASS_NAME); if(IS_ERR(g_vircdev_class)) { err = PTR_ERR(g_vircdev_class); printk(KERN_ALERT "Failed to create class.\n"); goto CLASS_CREATE_ERR; } dev = device_create(g_vircdev_class, NULL, devt, NULL, VIRCDEV_DEVICE_NAME); //dev = device_create(hello_class, NULL, dev, "%s", HELLO_DEVICE_FILE_NAME); //device_create( my_class, NULL, MKDEV(hello_major, 0), "hello" "%d", 0 ); //dev = device_create(g_vircdev_class, NULL, MKDEV(MYDRIVER_Major, 0), NULL, DEVICE_NAME); if(IS_ERR(dev)) { err = PTR_ERR(dev); printk(KERN_ALERT "Failed to create device.\n"); goto DEVICE_CREATE_ERR; } /*err = device_create_file(dev, attr); if(err < 0) { printk(KERN_ALERT"Failed to create attribute file."); goto DEVICE_CREATE_FILE_ERR; }*/ printk(KERN_ALERT "seems ok.\n"); //zmk@@debug return 0; DEVICE_CREATE_FILE_ERR: device_destroy(g_vircdev_class, devt); DEVICE_CREATE_ERR: class_destroy(g_vircdev_class); CLASS_CREATE_ERR: return err; } static int vircdev_delete_devfiles(dev_t devt) { device_destroy(g_vircdev_class, devt); class_destroy(g_vircdev_class); //device_remove_file(dev, attr); return 0; }
6、创建/proc调试文件函数:
static int vircdev_read_proc(char *buf, char **start, off_t offset, int count, int *eof, void *data) { int len =sprintf(buf, "vircdev read proc.\n"); return len; } static int vircdev_create_proc_file(void) { struct proc_dir_entry *entry = NULL; entry = create_proc_read_entry(VIRCDEV_PROC_NAME, 0, NULL, vircdev_read_proc, NULL); if(entry) { return 0; } else { return -1; } } static int vircdev_delete_proc_file(void) { remove_proc_entry(VIRCDEV_PROC_NAME, NULL); return 0; }
7、设备驱动模块的加载和释放:
MODULE_LICENSE("GPL"); static int vircdev_init(void) { int err = -1; dev_t devt = 0; //[1] alloc node number err = vircdex_alloc_major(); if(0 > err) { printk(KERN_ALERT"alloc major failed.\n"); goto ALLOC_MAJOR_ERR; } devt = MKDEV(g_vircdev_major, g_vircdev_minor); //[2] device object init g_vircdev_ptr = kmalloc(sizeof(struct vircdev_cntx), GFP_KERNEL); if(!g_vircdev_ptr) { err = -ENOMEM; printk(KERN_ALERT"kmalloc failed.\n"); goto KMALLOC_ERR; } memset(g_vircdev_ptr, 0, sizeof(struct vircdev_cntx)); //[3] setup device err = vircdev_setup_dev(g_vircdev_ptr); if(0 > err) { printk(KERN_ALERT"device setup failed.\n"); goto DEVICE_SETUP_ERR; } //[4] create files in directory "/dev/" and "/sys/" ///err = vircdev_create_devfiles(devt, attr); err = vircdev_create_devfiles(devt); if(0 > err) { printk(KERN_ALERT"devfiles create failed.\n"); goto DEVFILES_CREATE_ERR; } //[5] create proc file err = vircdev_create_proc_file(); if(0 > err) { printk(KERN_ALERT"proc file create failed.\n"); goto PROC_FILE_CREATE_ERR; } return 0; PROC_FILE_CREATE_ERR: vircdev_delete_devfiles(devt); DEVFILES_CREATE_ERR: vircdev_unsetup_dev(g_vircdev_ptr); DEVICE_SETUP_ERR: kfree(g_vircdev_ptr); ALLOC_MAJOR_ERR: vircdev_release_major(); KMALLOC_ERR: return err; } static void vircdev_exit(void) { dev_t devt = MKDEV(g_vircdev_major, g_vircdev_minor); vircdev_delete_proc_file(); vircdev_delete_devfiles(devt); vircdev_unsetup_dev(g_vircdev_ptr); kfree(g_vircdev_ptr); vircdev_release_major(); } module_init(vircdev_init); module_exit(vircdev_exit);
三、Makefile
obj-m := vircdev.o KERNELDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) modules: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules modules_install: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install clean: rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
关于Makefile,在《驱动模块中Makefile的自我理解》一文中有过说明,这里不再作过多解释。
四、验证
1.进入vircdev文件夹,编译:
$:cd vircdev $:make
查看是否生成.ko文件:
$:ls
结果:
Makefile Module.symvers vircdev.h vircdev.mod.c vircdev.o modules.order vircdev.c vircdev.ko vircdev.mod.o
2.载入模块:
$:sudo insmod ./vircdev.ko
查看模块是否载入成功:
$:lsmod |grep vir
结果:
vircdev 12980 0
3.分别使用cat、echo命令验证驱动模块的open、write、read和close功能:
$:cat /dev/vircdev_device $:echo abcd > /dev/vircdev_device $:dmesg |grep vircdev
结果:
[34058.091485] vircdev read. [34134.980198] vircdev write.
4.验证/proc调试功能:
$:cat /proc/vircdev_proc
结果:
vircdev read proc.
附:驱动程序源文件下载:文件:Vircdev.zip
ioctl测试程序的编写
鉴于read、write函数已经通过cat、echo验证ok了,现在这里编写的验证函数主要是为了验证ioctl函数。
//vircdev_ioctl_test.c #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <asm/ioctls.h> #define DEVICE_NAME "/dev/vircdev_device" #define VIRCDEV_IOC_MAGIC 'k' #define VIRCDEV_IOHI _IO(VIRCDEV_IOC_MAGIC, 0) #define VIRCDEV_IOWBYTE _IOW(VIRCDEV_IOC_MAGIC, 1, int) #define VIRCDEV_IORBYTE _IOR(VIRCDEV_IOC_MAGIC, 2, int) int main(int argc, char *argv[]) { char cmd = *argv[1]; int arg = *(argv[1]+1) - 48; int fd = 0; printf("cmd = %c, arg = %d\n", cmd, arg); fd = open(DEVICE_NAME, O_RDWR); switch(cmd) { case 'h': ioctl(fd, VIRCDEV_IOHI, arg); break; case 'w': ioctl(fd, VIRCDEV_IOWBYTE, arg); break; case 'r': ioctl(fd, VIRCDEV_IORBYTE, &arg); printf("read value:%d\n", arg); break; default: break; } close(fd); return 0; }
函数首先是定义了之前在驱动模块vircdev.h中定义过的宏,这里只是拷贝过来,不过为规范起见,建议是直接将该头文件包含过来,另外由于使用了_IOW等宏,要包含"asm/ioctls.h"头文件。
这个函数是取得用户在命令行输入的命令,然后控制对应的ioctl:
h -- VIRCDEV_IOHI,使设备驱动打印消息"ioctl:you said hi."
w -- VIRCDEV_IOWBYTE,写vircdev设备中的r1寄存器
r -- VIRCDEV_IORBYTE,读vircdev设备中的r1寄存器
编译:
$:gcc gcc vircdev_ioctl_test.c
读寄存器(自然前提是vircdev驱动模块已载入到系统):
$:./a.out r1
cmd = r, arg = 1 read value:9
写寄存器:
$:./a.out w5
cmd = w, arg = 5
再读出当前寄存器的值:
$:./a.out r1
cmd = r, arg = 1 read value:5
与写入值相等,sucess!!!
vircdev_ioctl_test.c下载:文件:Vircdev ioctl test.c.zip