下图为字符设备驱动的结构框图,图中最重要就是:cdev结构体。
本质上讲写Char驱动就是对cdev结构进行定义:
下面小白带你们到<linux/cdev.h>
中看一下它的结构:
cdev 中定义了六个成员,其中有三个需要我们自己定义。
第一个 struct module *owner /*所属模块*/
, ;
这个很简单直接赋值为THIS_MODULE;
接下来这两个成员很重要: dev_t 和 file_operations
dev_t 是设备号,设备号是什么?
当你在系统中生成一个Char设备时,系统怎么知道你是哪个设备?这个时候就需要给每个设备一个唯一的标识符,dev_t 就是干这个的。
dev_t 是一个32位的整型,其中高12位为主设备号(major),低20位为次设备号(minor)。
major 和minor 可以自己定义:
通过MKDEV(int major,int minor)宏,可以将major和minor生成一个dev_t。
MKDEV 就是((u32)major <<12)| minor
的宏 。
也可以通过MAJOR(dev_t dev)
和 MINOR (dev_t dev)
来查看主设备号和次设备号。
file_operations中的函数是字符设备驱动设计的主要内容,这些函数实际上会被应用程序进行的Linux的open()、read()、close()等函数调用。
下图给出了部分file_operations中的函数:
对 cdev 进行赋值,linux中提供了专门的函数(可封装性)。
其中cdev_init()是用来初始化cdev:
cdev_add () 是用来向系统添加一个cdev,完成字符设备。
但是,cdev_add() 给系统添加一个cdev时,系统怎么去识别这个cdev呢?你可能会说不是每个cdev都有设备号吗?用设备号(dev_t)不就行了。
对,确实是用设备号,可是在用设备号之前,字符设备是不是应该告诉一下系统,这个设备号是我的,不允许其他设备使用它。
这个时候就需要注册,注册有两种:
1、是已经有设备号了,给系统报备一下,这时就使用:
register_chrdev_region()
2、没有设备号,让系统给它一个,系统将设备号保存到第一个参数返还,这时就使用:
alloc_chrdev_region() //(推荐使用,不容易出错)
这边需要注意一下: 两个注册函数都使用了__register_chrdev_region()
这个函数。
每次一次注册是要加锁的。
当major==0时就由系统来分配设备号,系统内部有个chrdevs的数组,所有注册的设备号都在这里,因此需要从数组中找一个NULL的给新设备用。
下面这段代码主要是检查已有的设备号(使用register_chrdev_region
)和系统内部是否有冲突。
最后,将新的cdev加到系统的cdev的链表中。
alloc_chrdev_region()
最后调用cdev_del()函数从系统中注销之后,系统应该调用unregister_chardev_region()来释放原先申请的设备号。