SCULL字符设备驱动搭建流程
本节对实现LDD3书中前六章的scull字符设备驱动的步骤进行了梳理。搭建步骤依据书中所讲内容,逐步完善。
-
hello模块实现,设备驱动的基础入门
-
scull裸机模块的实现,字符设备驱动开发方式
-
pipe实现
-
access.c实现
我的实现在https://gitee.com/yuan-jing-hahahaha/linux_device_drivers_lab.git中,其中examples是官方例程,scull是我基于linux6.2的实现,scull/test是一些测试程序(文件忘记clean,如果文件太乱可以先make clean一下,使用时再make)。
1. hello
hello模块编写是为了让我们熟悉内核驱动编写的步骤。
-
内核模块需要编写init和exit函数,用于内核模块在插入和删除的时候调用。
-
内核模块传参用法
-
内核模块编译Makefile编写
2. scull字符设备驱动
scull裸机模块涉及字符设备驱动的方方面面,可以让我们熟悉字符设备驱动的开发方法。
字符设备能够实现的前提:
-
申请设备号
-
初始化cdev结构,其中包括operations操作的定义,cdev_add将设备号和某个cdev结构关联,使得通过设备号我们就可以找到对应的cdev结构
-
创建设备节点,也就是用设备号创建字符设备文件,即将设备号cdev结构和文件绑定在一起。该文件是用户操作设备的入口,对文件进行open等操作时,它就可以通过cdev结构中的operation找到实际执行的函数,执行。
scull驱动编写步骤:
-
编写init和exit,在init中完成设备号申请以及cdev结构初始化等,在scull裸机程序中还完成了对scull_device的初始化;exit做一些善后工作,注销掉之前有影响的操作
-
编写open和close,这里这两个函数比较简单,没做什么重要工作
-
编写read和write,这里是整个设备实现的核心逻辑,和驱动本身没什么关系,主要是逻辑设计,在和硬件有关的驱动中还会和硬件特性相关。
-
编写ioctl,这里的ioctl实现了对quantum等参数的修改,实现功能很简单,主要为了使熟悉ioctl方法的使用。(现在用.unlocked_ioctl)
-
编写llseek,这里可以实现文件指针的移动,可以实现追加写入。(在第六章的后面讲到,可以先看)
-
编写Makefile
-
编写scull_load和scull_unload,这两个文件主要为了插入删除模块和创建删除设备节点(用户能够操作设备的入口!)
3. pipe实现
pipe是在阻塞IO教程中引入的,其实是另一种内存分配的实现,类似于管道,是一个循环队列,一端写,另一端读完就不保存了。该驱动有以下几个意义:
-
管道的逻辑实现
-
阻塞IO编写示例,没数据时读阻塞,数据满时写阻塞
-
多个驱动模块的组织方式,pipe和scull是两个驱动,但是是通过一个驱动模块入口组织在一起的
编写步骤和scull类似,以下是几点注意和特点:
-
设备号的设计:pipe并没有重新分配主设备号,而是使用scull的主设备号。因为他们都是属于scull设备,所以共用一个更成一个系统,更合理。
-
init中只做了设备号和cdev的相关处理,pipe设备初始化放在open中做。因为pipe设备关闭后就没了,其生命周期在于它打开的时候。scull因为生命周期在于模块装载在系统中的时候,只要模块没卸载,那个模块就还在,所以将scull设备初始化放在了init中。
-
ioctl的实现放在scull_unlocked_ioctl中一起实现。
-
pipe并没有导出自己的init和exit,而是放到scull的init中调用,这样的话,只需要insmod scull就可以达到启用pipe的作用。
4. access实现
access实现的是4种访问限制,各具特色,且其代码组织方式也很有趣。因为只需要在原scull的open和release上做修改,所以其他部分还是用的scull的函数实现。
-
独享设备:每个进程互斥访问,用锁就可以实现。
-
单用户访问:这里使用了内核提供的方法,保存并对比用户uid来实现限制,逻辑也比较简单。当然此教程适用于所有的单XX访问,只要有一个可以标识对比的变量。
-
阻塞型单用户访问:加了等待队列,即在无法打开时并不是直接退出,而是会根据NONBLOCK来判断是否实现阻塞。
-
打开时复制设备:这里我觉得是最有意思的,就是一种虚拟设备的实现技巧。什么意思呢,如果只用终端来作为区分,那么就是对于每一个终端来说,他们是自己独立的设备上进行操作,终端A读写的是一个设备,终端B读写的是一个设备,两个设备并不干扰,但是实际上只有一个设备,这种独立性是软件维护的。
-
它有一个全局设备scull_c_device,其中的cdev部分很重要,这是实体的一个设备,用于通过设备节点能够找到内核函数入口。
-
设备链表,链表每一项都是为每一个终端分配的一个设备。在open中会从链表中找到或者新分配一个设备对应于当前设备,赋值给filp->privation_data,在read等操作中会修改这个里的数据结构。可以看到每个终端有自己的数据结构,因而相互独立,这就是虚拟的多个设备。
-
-
代码组织:因为access中有四个驱动部分,对他们四个驱动写了一个init和exit,供scull_init调用。
5. 调试与竟态
这两部分分别是第四章和第五章的内容,我在初次看的时候感觉很懵懂,只是在记忆理解一些概念,很困难。后来发现这两部分是程序在设计中可以贯穿整个程序设计。因此,不必再初次阅读时即要求自己明了,但是也要看一遍对操作有个认识,然后在实际的使用中,你会不停的回顾一些小点,去理解它,即需要时你将理解它。在scull的调式中,有一部分和proc有关,这里我们也可以找到第四章的相关内容,只是我还没有实现,可以在想要实现的时候实现它。
6. 其他
一些方面的理解:
终于理解,其实内核模块插不插入这个事情并不重要,重要的是我们要用这个驱动,必须要执行一段内核代码,只不过插入这个动作可以触发这段代码的执行。还有,字符设备文件即文件只是一个节点,入口!