- 1 -字符驱动开发学习笔记Linux下的设备驱动程序被组织为一组完成不同任务的函数的集合,通过这些函数使得Windows的设备操作犹如文件一般。在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作,如open ()、close ()、read ()、write () 等。Linux主要将设备分为二类:字符设备和块设备。字符设备是指设备发送和接收数据以字符的形式进行;而块设备则以整个数据缓冲区的形式进行。字符设备的驱动相对比较简单。 下面假设一个非常简单的虚拟字符设备:这个设备中只有一个4个字节的全局变量int global_var,设备的名为\"gobalvar\"。对\"gobalvar\"设备的读写等操作即是对其中全局变量global_var的操作。1、编写globalvar.c文件,代码如下: 打开终端,在test文件夹下创建globalvar.c文件PC$cd ~/testPC$gedit globalvar.c源文件globalvar.c代码如下,我所使用的内核是2.6版本。#include#include#include#include#includeMODULE_LICENSE(\"GPL\"); int globalvar_open(struct inode *,struct file *);int globalvar_release(struct inode *,struct file *);ssize_t globalvar_read(struct file *,char *,size_t,loff_t *);ssize_t globalvar_write(struct file *,const char *,size_t,loff_t *);int dev_major=0;1- 2 -int dev_minor=0;struct file_operations globalvar_fops= //将文件操作预分配的设备号相连{ .owner=THIS_MODULE, .open=globalvar_open, .release=globalvar_release, .read=globalvar_read, .write=globalvar_write,};struct globalvar_dev //用来表示我们定义设备的结构{ int global_var; //代表要操作的设备 struct cdev cdev; //内核中表示字符设备的结构};struct globalvar_dev *my_dev; //设备结构指针static void __exit globalvar_exit(void) //退出模块时的操作{ dev_t devno=MKDEV(dev_major,dev_minor); //dev_t是用来表示设备编号的结构 cdev_del(&my_dev->cdev); //从系统中移除一个字符设备 kfree(my_dev); //释放自定义的设备结构 unregister_chrdev_region(devno,1); //注销已注册的驱动程序 printk(\"globalvar unregister success \\n\");}static int __init globalvar_init(void) //初始化模块的操作 { int ret, err; if (dev_major) { dev_t devno=MKDEV(dev_major, dev_minor); ret=register_chrdev_region(devno, 1, \"globalvar\"); } else { ret=alloc_chrdev_region(&devno, dev_minor, 1, \"globalvar\"); //动态分配设备号 dev_major=MAJOR(devno); //保存动态分配的主设备号 } if(ret<0) { 2- 3 - printk(\"globalvar register failure\\n\"); globalvar_exit(); //如果注册设备号失败就退出 return ret; } else { printk(\"globalvar register success\\n\"); } my_dev=kmalloc(sizeof(struct globalvar_dev), GFP_KERNEL); //为设备分配内核空间 if(!my_dev) //如果分配失败返回错误信息 { ret=-ENOMEM; printk(\"create device failed\\n\"); } else //如果分配成功就可以完成设 { my_dev->global_var=0; //设备变量初始化为0 cdev_init(&my_dev->cdev, &globalvar_fops); //初始化设备中的cdev结构 my_dev->cdev.owner=THIS_MODULE; //初始化cdev中的所有者字段 err=cdev_add(&my_dev->cdev, devno, 1); //向内核添加cdev结构的信息 if(err<0) //如果添加失败打印错误消息 printk(\"add device failure\\n\"); else printk(\"add device success\\n\"); } return ret; }//打开操作int globalvar_open(struct inode *inode,struct file *filp){ struct globalvar_dev *dev; //根据inode结构的cdev字段,获得整个设备结构的指针 dev=container_of(inode->i_cdev,struct globalvar_dev,cdev); filp->private_data=dev; //保存数据指针 return 0; }//释放操作int globalvar_release(struct inode *inode,struct file *filp){ return 0;}3- 4 -//读操作ssize_t globalvar_read(struct file *filp,char *buf,size_t len,loff_t *off){ struct globalvar_dev *dev=filp->private_data; //获取已指向分配数据的指针 if(copy_to_user(buf,&dev->global_var,sizeof(int))) //将设备变量值复制到用户空间 { return -EFAULT; } return sizeof(int); //返回读取数据的大小}//写操作ssize_t globalvar_write(struct file *filp,const char *buf,size_t len,loff_t *off){ struct globalvar_dev *dev=filp->private_data; //获取已指向分配数据的指针 if(copy_from_user(&dev->global_var,buf,sizeof(int))) //将设备变量值复制到用户空间 { return -EFAULT; } return sizeof(int); //返回写数据的大小}module_init(globalvar_init);module_exit(globalvar_exit);2、编写Makefile文件,内容如下:ifneq ($(KERNELRELEASE), ) obj-m := globalvar.o else KERNELDIR :=/lib/modules/2.6.32.41+drm33.18/buildPWD := $(shell pwd) all:$(MAKE) -C $(KERNELDIR) M=$(PWD) modules clean:$(MAKE) -C $(KERNELDIR) M=$(PWD) clean endif3、当前工作目录开始编译模块4- 5 -PC$cd ~/testPC$make4、加载模块,并用lsmod或dmesg查看PC$sudo insmod globalvar.koPC$lsmodModule Size Used byglobalvar 1291 0 binfmt_misc 6587 1 vmnet 40983 17 …… …… ……PC$dmesg…… ……[ 8175.7585] globalvar register success[ 8175.7588] add device success5、查看动态生成的设备号(主设备号)PC$cat /proc/devicesCharacter devices:…… ……1 usb_device226 drm251 globalvar252 usbmon…… ……6、使用mknod命令创建设备节点文件PC$sudo mknod /dev/globalvar c 251 0PC$ chgrp staff /dev/globalvarPC$chmod 6 /dev/globalvar 另外一种方法:在上述步骤4、5、6所实现的工作可以用另外一种简便的方法实现。详细内容请继续阅读下文:为了加载一个使用动态主设备号的设备驱动程序,对insmod的调用可以替换为一个简单的脚本文件,该脚本文件在调用insmod之后,利用awk这类工具读取/proc/devices以获取新分配的主设备号,然后创建对应的设备文件。下面是一个命名为globalvar_load的脚本文件,使用以模块方式发布的驱动程序的用户可以在系统rc.local文件中调用这个脚本。或是在需要此模块式时手工调用(本文中我们选用手工调用此脚本)。脚本内容如下:#!/bin/shmodule=\"globalvar\"5- 6 -mode=\"6\"/sbin/insmod ./$module.ko $* || exit 1rm -f /dev/ $ modulemajor=$(awk \"\\$2==\\\"$module\\\" {print \\$1}\" /proc/devices)mknod /dev/$module c $major 0if grep -q '^staff:' /etc/group; then group=\"staff\"else group=\"wheel\"fichgrp $group /dev/$modulechmod $mode /dev/$module这个脚本也同样适用于其它驱动程序,只要重新定义变量并根据需求调整mknod语句就可以了。7、编写测试程序//test.c#include #include #include #include main() { int fd, num; fd=open(\"/dev/globalvar\可读写方式打开设备 if(fd!=-1) { read(fd, &num, sizeof(int)); //读取设备变量 printf(\"The globalvar is %d\\n\ printf(\"Please input the num written to globalvar\\n\"); scanf(\"%d\ write(fd, &num, sizeof(int)); //写设备变量 read(fd, &num, sizeof(int)); //再次读取刚才写的值 printf(\"The globalvar is change to %d now!\\n\ close(fd); //关闭设备文件 } else printf(\"Device open failure\\n\");}8、编译test.cPC$gcc –o test test.cPC$sudo ./testThe globalvar is 0Please input the num written to globalvar6- 7 -100The globalvar is change to 100 now!结论:如果可以出现上面的的提示则证明字符设备驱动编写成功。7