首页 > 商业 >

环球头条:【Leo的手记】Linux设备驱动程序手记3-分层抽象与次设备号

2023-06-30 20:38:09 来源:哔哩哔哩

1.分层抽象,接口与封装

对于设备驱动程序而言,分离设备业务逻辑和底层硬件操作有利于驱动程序的扩展。而利用面向对象的思想对驱动程序进行抽象是一个很好的方法。其主要的方法就是将驱动程序的业务逻辑中关于底层硬件操作的部分通过某种接口加以规范。如同file_operations,由此便可以使用统一的接口进行业务逻辑相关操作而无需关注硬件底层差异。在系统结构图上来看相当于将业务逻辑作为上层实现,而具体的硬件操作作为下层依赖,形成不同层级的系统结构。而关于相关接口的实现则交由单独的平台相关的程序部分加以实现。此外还可以通过使用简易工厂模式对提供给业务逻辑对应接口的部分代码加以进一步封装。以此实现对于驱动程序代码的高效封装,复用和拓展。


(资料图片仅供参考)

2.次设备号与一类设备

诸如tty设备与磁盘设备等大多数的设备,其硬件实体在系统中往往存在不仅一个实例。驱动程序往往需要维护多个设备,或者说,驱动程序应该维护驱动程序所支持的一类设备。因此,如何在系统中创建一类设备的多个实例并在驱动程序中加以辨识和管理,则是一个必须纳入考量的事情。

对于Linux系统而言,每一个设备都有一个主设备号与次设备号,对于一个驱动程序而言,通常会在驱动程序加载的时候注册设备并获取主设备号,主设备号用于关联一组文件IO操作。因此,主设备号便可以用于区分一类设备,而次设备号便可以用于区分一类设备中的不同实例。

例如一个字符设备包含有多个设备实例,可以在注册设备后,创建多个设备实例。实例代码如下

3.驱动程序中获取次设备号

注册设备时所绑定的一系列文件IO操作是服务于一类设备的,其包含了通用的业务方法抽象。上文指出可以通过次设备号来区分不同的设备实例,因此在接口中如何获取次设备号则是需要讨论的。

在初始化时,通过device_create创建设备时,我们通过MKDEV组合了主设备号与次设备号并用其创建了设备,在/dev目录中我们也可以发现对于不同的设备,在使用ls打印时会显示相关的主设备号和次设备号。我们可以发现,设备号信息与设备对象是关联的。而我们在操作设备的不同实例时恰好就是访问了不同的设备文件,因此我们来看文件IO接口的原型。

我们可以看到,其每一个接口都至少指定了一个文件作为形参。事实上,Linux内核提供了一个iminor方法用于获取inode的次设备号。获取方法如下

而对于诸如open和release方法,其形参列表中存在单独的inode因此可以直接使用,而对于read,write等方法,则需要从其传入的file参数中获取inode信息。Linux内核提供了一个file_inode方法用于提取struct file结构体的inode。

由此便可获取当前所进行系统调用所访问的设备文件的次设备号。

拓展

file_inode方法实际上返回了struct file结构体中的f_inode成员。

iminor方法返回了struct inode结构体中的i_rdev成员。

4.驱动程序的分层抽象

我们可以看到,由于多个设备实例的存在,驱动程序需要考虑的情况变得庞大。如果将对于不同设备的底层差异的判断都交由接口所指定的对应函数实现, 则程序体量会膨胀。并且一旦对硬件进行扩展,便要修改驱动程序的对应源码。这使得驱动程序的开发维护变得很烂。

一个好的想法在上文中已经提到,我们可以将文件IO接口实现中对于底层的操作封装为抽象的接口,而由负责硬件的程序部分实现该接口,由此便完成了抽象。

因此,在驱动程序中我们可以将次设备号作为参数传递给相应的底层接口,只需要考虑对于这一类设备的业务逻辑即可。大大提高了代码的复用与易扩展性。

例如,如果我们抽象一个LED的驱动程序,我们可以将LED的操作和具体的硬件实现分离。在对应的IO接口中仅仅实现对业务逻辑的包装。在接口的实现中实现对业务逻辑的具体实现。

创建两个文件,led_和led_,编写Makefile。想要实现将多个c源码文件编译成一个内核模块文件,Makefile编写如下。

led_负责对字符设备进行注册,并且实现业务逻辑。

led_实现了f1c100s对于字符设备的底层实现。

想要实现对接口的定义,需要额外编写一个头文件led_,用于规范相关的接口。其实现如下

led_

led_

实现如下

led_

该文件主要实现底层的初始化,实现如下

5.测试程序

环球信息:6月新增5家公司IPO申请获受理 成都加快推动中小企业上市 最后一页
最近更新 MORE