700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 一起分析Linux系统设计思想——05字符设备驱动框架剖析(四)

一起分析Linux系统设计思想——05字符设备驱动框架剖析(四)

时间:2020-02-27 15:58:14

相关推荐

一起分析Linux系统设计思想——05字符设备驱动框架剖析(四)

在学习资料满天飞的大环境下,知识变得非常零散,体系化的知识并不多,这就导致很多人每天都努力学习到感动自己,最终却收效甚微,甚至放弃学习。我的使命就是过滤掉大量的垃圾信息,将知识体系化,以短平快的方式直达问题本质,把大家从大海捞针的痛苦中解脱出来。

4 重温字符设备驱动(接上篇)

在前面的文章(一起分析Linux系统设计思想——05字符设备驱动框架剖析(二)_穿越临界点的博客-CSDN博客)中我们以一个非常简单的例子让大家感受了一下字符设备驱动。本篇我们再重温一下,挖掘挖掘其中的设计思想和技巧,以便日后分析其他驱动框架时做到举一反三。

把源码列举如下:

/* 最简单的内核态驱动程序:cdriver.c文件 */#include <linux/module.h>#include <linux/kernel.h>#include <linux/fs.h>#include <linux/init.h>/* 定义设备文件打开函数 */int cdriver_open(struct inode *inode, struct file *file){/* 什么也不做,只是打印(注意在内核态只能用printk,不能用printf)*/printk("cdriver open success!\n"); return 0;}/* 定义设备文件关闭函数 */int cdriver_release(struct inode *inode, struct file *file){/* 什么也不做,只是打印(注意在内核态只能用printk,不能用printf)*/printk("cdriver release success!\n"); return 0;}/* 定义设备文件操作接口对象,并初始化 */struct file_operations cdriver_fops = {.owner = THIS_MODULE, /*THIS_MODULE是编译器生成的,其实就是this指针,如果觉得抽象可以先不关注*/.open = cdriver_open, /*将我们定义的cdriver_open函数挂到open函数指针上*/.release = cdriver_release, /*将我们定义的cdriver_release函数挂到release函数指针上*/};/* 定义模块初始化函数 */int __init cdriver_init(void) /*__init是段属性,可以先忽略*/{/* 这里是核心动作——注册。注册主设备号(110),设备名称(“cdriver")和设备文件操作符首地址到内核中。 */register_chrdev(110, "cdriver", &cdriver_fops);return 0;}/* 定义模块退出函数*/void __exit cdriver_exit(void){unregister_chrdev(110, "cdriver"); /*取消注册*/}/* 下面这三行东西真正把驱动做到了“模块化” */module_init(cdriver_init); /*将cdriver_init放到指定段*/module_exit(cdriver_exit);MODULE_LICENSE("GPL"); /*注册GPL*/

4.1 对象的生命周期

Linux内核在设计驱动框架时使用的是面向对象设计思想。为了确保合适的代码在合适的时机被调用,需要时刻将对象的生命周期放在心里(就好像在Android编程时将Activity的生命周期熟记于心一样)。

如下图所示,是我们编写的最简单的内核驱动模块的生命周期。分为诞生期生存期死亡期。这三个阶段和大自然的生命周期是一致的。所以,我们可以把大自然的生命定义为碳基生命,而把计算机内的生命定义为硅基生命

其中生存期属于整个生命的核心时期,生存期间为用户态提供自己的服务,这就是它存在的价值。

在诞生期中需要强调的一点是,内核装载完驱动模块后并不是立马生效的,需要用户态代码调用open系统调用才可以使用该驱动模块。因此,初始化代码是放在module_init中还是放在open中要视情况而定。比如需要分时复用的引脚初始化代码就一定要放在open阶段中,而不应该放在模块加载中。

4.2 注册结构设计

下面重点分析注册结构的设计技巧。

4.2.1 键值对

register_chrdev(110, "cdriver", &cdriver_fops);

register_chrdev函数的入参包含主设备号、驱动名称和操作方法,分别对应着识别和使用对象的三要素IDNameMethod。这和键值对的索引方法是异曲同工的。

那为什么不把主设备号和驱动名称放在操作方法的结构体中去呢?按照封装来说这样做是更加紧凑的,但是使用起来就不如当前的设计更为方便。因为ID和Name一般是作为索引来用的,就好比是一本书的目录,将目录放到书本的内容中去便失去了方便查找的特性。

4.2.2 file_operations接口

为了平台化设计,Linux内核驱动设计时也使用了依赖反转(详细可参见博客:一起分析Linux系统设计思想——05字符设备驱动框架剖析(三)_穿越临界点的博客-CSDN博客) 的手法,而file_operations就是内核定义的通用接口,这个接口兼容了所有的驱动设备,这得益于它的成员设计。

file_operations的定义如下(只保留了极为关键的成员):

struct file_operations {struct module *owner;ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);int (*open) (struct inode *, struct file *);int (*release) (struct inode *, struct file *);};

open和release的关键性不必多说了,在前文介绍对象的生命周期时已经提及了。

下面重点提出read、write和ioctl成员设计背后的逻辑——访问三要素读、写、配置

下一篇,我们真正开始硬件的操作,点亮第一个LED。是不是有点小激动呢~

恭喜你又坚持看完了一篇博客,又进步了一点点!如果感觉还不错就点个赞再走吧,你的点赞和关注将是我持续输出的哒哒哒动力~~

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。