一起分析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。是不是有点小激动呢~


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

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

穿越临界点

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值