标签:
20150216简单的Linux字符设备驱动程序
2015-02-16 李海沿
关于字符设备驱动程序详细的知识点,本文就不再介绍了,很多同志,看了知识点,还是一头雾水,写不出来,所以,本文从实战出发,带领各位同胞们来实现一个字符设备驱动程序,改程序可作为字符设备的通用模板。
好了废话不多说,先上驱动程序,在驱动程序中加入详细注释:
1 /****************************** 2 linux 字符设备驱动程序 3 *****************************/ 4 #include <linux/module.h> 5 #include <linux/init.h> 6 #include <linux/kernel.h> 7 #include <linux/delay.h> 8 #include <linux/types.h> 9 #include <linux/ioctl.h> 10 #include <linux/gpio.h> 11 #include <linux/fs.h> 12 #include <linux/device.h> //包含了用于自动创建设备节点的函数device_create 13 #include <linux/uaccess.h> //包含了copy_to_user 函数等 14 15 #define Driver_NAME "key_query" 16 #define DEVICE_NAME "key_query" 17 18 //command in ioctl 19 #define version 0 20 21 //用于保存主设备号 22 static int major=0; 23 24 //用于自动创建设备节点 代替了手动敲mknod命令 25 static struct class *drv_class = NULL; 26 static struct class_device *drv_class_dev = NULL; 27 28 29 /* 应用程序对设备文件/dev/key_query执行open(...)时, 30 * 就会调用key_open函数*/ 31 static int key_open(struct inode *inode, struct file *file) 32 { 33 printk("<0>function open!\n\n"); 34 35 return 0; 36 } 37 38 /*当应用程序中read(fd,buff,sizeof(buff))时调用此key_read函数*/ 39 static int key_read(struct file *filp, char __user *buff, size_t count, loff_t *offp) 40 { 41 printk("<0>function read!\n\n"); 42 return 0; 43 } 44 45 /* 当应用程序中使用write函数时,调用此函数**/ 46 static ssize_t key_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) 47 { 48 printk("<0>function write!\n\n"); 49 50 return 1; 51 } 52 53 static int key_release(struct inode *inode, struct file *filp) 54 { 55 printk("<0>function release!\n\n"); 56 return 0; 57 } 58 /* 当用户调用ioctl(fd,version,NULL);时,会进入此函数, 59 * 在SWITCH中配对command,然后执行相应的语句 60 * 注意command 一定为整数,需在前面定义*/ 61 static int key_ioctl(struct inode *inode,struct file *flip,unsigned int command,unsigned long arg) 62 { 63 printk("<0>function ioctl!\n\n"); 64 switch (command) { 65 case version: 66 printk("<0>hello,the driver version is 0.1.0\n\n"); 67 break; 68 default: 69 printk("<0>command error \n"); 70 printk("<0>ioctl(fd, (unsigned int)command, (unsigned long) arg;\n"); 71 printk("<0>command: <version>\n\n"); 72 return -1; 73 } 74 return 0; 75 } 76 77 /* 这个结构是字符设备驱动程序的核心 78 * 当应用程序操作设备文件时所调用的open、read、write等函数, 79 * 最终会调用这个结构中指定的对应函数 80 */ 81 static struct file_operations key_fops = { 82 .owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */ 83 .open = key_open, 84 .read = key_read, 85 .write = key_write, 86 .release= key_release, 87 .ioctl = key_ioctl, 88 }; 89 90 /* 91 * 执行insmod命令时就会调用这个函数 92 */ 93 static int __init key_init(void) 94 { 95 printk("<0>\nHello,this is %s module!\n\n",Driver_NAME); 96 //register and mknod 97 //注册字符设备,系统会自动分配一个主设备号,保存在major中 98 major = register_chrdev(0,Driver_NAME,&key_fops); 99 //自动在/dev/目录下创建设备节点 100 drv_class = class_create(THIS_MODULE,Driver_NAME); 101 drv_class_dev = device_create(drv_class,NULL,MKDEV(major,0),NULL,DEVICE_NAME); /*/dev/key_query*/ 102 103 return 0; 104 } 105 106 /* 107 * 执行rmmod命令时就会调用这个函数 108 */ 109 static void __exit key_exit(void) 110 { 111 printk("<0>\nGoodbye,%s!\n\n",Driver_NAME); 112 113 //卸载字符设备,释放主设备号 114 unregister_chrdev(major,Driver_NAME); 115 //卸载字符设备的设备节点 116 device_unregister(drv_class_dev); 117 class_destroy(drv_class); 118 119 } 120 121 /* 这两行指定驱动程序的初始化函数和卸载函数 */ 122 module_init(key_init); 123 module_exit(key_exit); 124 125 /* 描述驱动程序的一些信息,不是必须的 */ 126 MODULE_AUTHOR("Lover雪儿"); 127 MODULE_VERSION("0.1.0"); 128 MODULE_DESCRIPTION("IMX257 key Driver"); 129 MODULE_LICENSE("GPL");
以下是个人理解,仅代表个人思想:
static int __init key_init(void)
如果单片机程序的角度来理解字符设备,那么上面程序的其实就是相当于我们的main函数。
在linux系统中,我们使用insmod 加载驱动时,
最先调用的,最先进入的就是__init key_init函数,所以我们所有初始化的代码都可以放在这个函数里,
比如注册设备,创建设备驱动,申请内存,如果说是功能有关GPIO的话,那么GPIO的引脚的初始化也可以放在此函数中,所以总结的一句话,
__init key_init函数就是负责初始化的函数。
static void __exit key_exit(void)
相应的,与__init key_init函数的功能相反,当我们不要用这个驱动的时候,系统就会调用这个函数。
前面我们的__init key_init函数实现了初始化,注册设备,申请的内存等功能,为了资源的合理利用,所有的系统资源,当我们不要用的时候,我们就应该释放。好比说,借了别人的东西暂时使用,不用的时候,就应该归还别人。
所有我们要释放的代码,那就是放在这个函数中执行。
比较简单理解。
module_init(key_init);
module_exit(key_exit);
当我们写两个函数key_init key_exit 函数时,操作系统没有那么智能的就知道这两个就是初始化和退出函数
这两行代码就是指定我们的初始化和退出的函数,
也就是把我们前面写的两个函数告诉linux操作系统,我们初始化和退出的函数
static int key_open(struct inode *inode, struct file *file)
但我们在应用程序中使用代码
open("dev/xxx",O_RDWR);
打开设备时,就会调用这个函数,这个函数中可以实现我们设备被打开时要执行的一些操作。如果没什么操作,可以不写。
static int key_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
当应用程序中read(fd,buff,sizeof(buff))时调用此key_read函数
所以,在此函数中,我们可以使用copy_to_user函数,来把内核空间中的数据传递到我们应用程序中。
static ssize_t key_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
当我们应用程序中执行写入操作时,这个函数就发挥作用了,当然
在此函数中还需要使用Copy_from_user来配合,将应用程序中的数据传递到内核空间,
因为应用程序空间和内核空间是相互独立的,不能互相访问,必须借助于copy_from_use 和 copy_to_user 这两个函数。
static int key_ioctl(struct inode *inode,struct file *flip,unsigned int command,unsigned long arg)
当我们的驱动程序可以实现多中会互相冲突的功能时,可以在此函数中使用command来区分不同的功能,
然后再switch函数中实现不同功能的代码。
应用程序中使用ioctl(fd,version,NULL); 会进入此函数。
比如说,当我们的驱动程序可以实现流水灯,花样灯等不同的功能时,我们就可以定义两个整形COMMAND,在此函数实现该功能。
static struct file_operations key_fops = {
.owner = THIS_MODULE,
.open = key_open,
.read = key_read,
.write = key_write,
.release= key_release,
.ioctl = key_ioctl,
};
这个结构是字符设备驱动程序的核心
当应用程序操作设备文件时所调用的open、read、write等函数,
最终会调用这个结构中指定的对应函数
通俗来讲,这个结构体就是告诉linux操作系统,我们哪些已经实现的函数分别是驱动程序的 open,write,read,ioctl。
当我们实现了上面的几个函数时,也就是实现了一个简单的驱动程序了。
下面我们附上 Makefile的代码
1 ifeq ($(KERNELRELEASE),) 2 KERNELDIR ?= /home/study/system/linux-2.6.31 3 PWD := $(shell pwd) 4 modules: 5 $(MAKE) -C $(KERNELDIR) M=$(PWD) modules 6 modules_install: 7 $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install 8 clean: 9 rm -rf *.o *~ core .depend *cmd *.ko *.mod.c .tmp_versions *.markers *.order *.symvers 10 11 else 12 obj-m := key.o 13 endif
接下来,我们就在此基础上实现一个gpio的按键输入程序。
标签:
原文地址:http://www.cnblogs.com/lihaiyan/p/4294494.html