《驱动学习 - 字符设备驱动》

1.1字符设备驱动基础

字符设备驱动:设备对数据的处理是按照字节流的形式进行的。

 

在linux中,“一切皆文件”(除了网络设备),这表示设备最终都会体现为一个文件。设备文件通常位于/dev目录下、

内核通常用主设备号区别一类设备,次设备号用于区分同一类设备的不同个人或不同分区。

 

手动创建设备文件

mknod   /dev/vser0 c 256 0

mknod是make node的缩写。用于创建一个节点(设备文件也叫设备节点)、在linux系统中,一个节点代表一个文件。

 

1.2 字符设备驱动框架

 example 1.1

 1 #include <linux/init.h>
 2 #include <linux/kernel.h>
 3 #include <linux/module.h>
 4 
 5 #include <linux/fs.h>
 6 
 7 
 8 #define VSER_MAJOR     256
 9 #define VSER_MINOR     0
10 #define VSER_DEV_CNT   1
11 #define VSER_DEV_NAME  "vser"
12 
13 static int __init vser_init(void)
14 {
15     int ret;
16     dev_t dev;
17 
18     /* MKDEV宏将主设备号和次设备号合并成一个设备号 */
19     dev = MKDEV(VSER_MAJOR, VSER_MINOR);
20 
21     /* 将构造的设备号注册到内核中(静态) */
22     ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);
23     if(ret)
24         goto reg_err;
25     return 0;
26 
27 reg_err:
28     return ret;
29 }
30 
31 static void __exit vser_exit(void)
32 {
33     dev_t dev;
34 
35     dev = MKDEV(VSER_MAJOR, VSER_MINOR);
36 
37     /* 从内核中注销设备号 */
38     unregister_chrdev_region(dev, VSER_DEV_CNT);
39 }
40 
41 module_init(vser_init);
42 module_exit(vser_exit);
43 
44 MODULE_LICENSE("GPL");

  注意:使用goto函数进行集中错误处理在驱动中非常常见,虽然这和一般的C语言编程规则相悖(因为C语言面向程序的语言,要求模块单入口,单出口,但是goto破坏了这种结构)。 

  register_chrdev_region注册设备号的方式称为静态注册设备号,这样如果两个驱动都是用同样的设备号,那么后加载的驱动将会失败,因为设备号冲突。可以使用动态分配设备号的方式避免这个问题。alloc_chrdev_region函数。

 

  MKDEV该宏的作用是将主设备号左移20位和次设备号相或。

  MAJOR和MINOR分别是从设备号中取出主设备号和次设备号。

#define MINORBITS            20
#define MINORMASK           ((1U << MINORBITS) - 1)


#define MAJOR(dev)         ((unsigned int) ((dev) >> MINORBITS))       
#define MINOR(dev)         ((unsigned int) ((dev) & MINORMASK)) 
#define MKDEV(ma, mi)      ((ma) << MINORBITS | (mi))

 

example 1.2

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

#include <linux/fs.h>
#include <linux/cdev.h>


#define VSER_MAJOR     256
#define VSER_MINOR     0
#define VSER_DEV_CNT   1
#define VSER_DEV_NAME  "vser"

/* 代表一个具体得字符设备 */ static struct cdev vsdev;
/* 操作该设备得一些方法 */
static struct file_operations vser_ops = { .owner = THIS_MODULE, }; static int __init vser_init(void) { int ret; dev_t dev; /* MKDEV宏将主设备号和次设备号合并成一个设备号 */ dev = MKDEV(VSER_MAJOR, VSER_MINOR); /* 将构造的设备号注册到内核中(静态) */ ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME); if(ret) goto reg_err; cdev_init(&vsdev, &vser_ops); vsdev.owner = THIS_MODULE;
   /* 初始化vsdev中得部分成员,将vsdev中得ops指针指向vser_ops */ ret
= cdev_add(&vsdev, dev, VSER_DEV_CNT); if(ret) goto add_err; return 0; add_err: unregister_chrdev_region(dev, VSER_DEV_CNT); reg_err: return ret; } static void __exit vser_exit(void) { dev_t dev; dev = MKDEV(VSER_MAJOR, VSER_MINOR); /* 删除cdev对象 */ cdev_del(&vsdev); /* 从内核中注销设备号 */ unregister_chrdev_region(dev, VSER_DEV_CNT); } module_init(vser_init); module_exit(vser_exit); MODULE_LICENSE("GPL");

  cdev_init函数初始化了vsdev中得部分成员。另外一个最重要得操作就是将vsdev中得ops指针指向了vser_ops,这样通过设备号找到vsdev对象后,就能找到相关得操作方法集合,并调用其中的方法。

  owner是一个指向struct module类型变量的指针,THIS_MODULE是包含驱动的模块中的struct module类型对象的地址,类似于C++中的this指针。这样就能够通过vsdev或vser_fops找到对应的模块。

  cdev_add函数将cdev对象初始化后添加到内核中的cdev_map散列表中。

  在例1.2中的cdev对象是静态定义的,我们也可以进行动态分配,对应的函数是cdev_alloc。

 

posted @ 2019-07-22 08:53  一个不知道干嘛的小萌新  阅读(1236)  评论(0编辑  收藏  举报