0
点赞
收藏
分享

微信扫一扫

20150216简单的Linux字符设备驱动程序


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");

View Code


以下是个人理解,仅代表个人思想:


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

View Code



 接下来,我们就在此基础上实现一个gpio的按键输入程序。


举报

相关推荐

0 条评论