当前课程知识点:Linux 内核分析与应用 >  第9章 设备驱动 >  9.7工程实践-编写块设备驱动的基础(上) >  Video

返回《Linux 内核分析与应用》慕课在线视频课程列表

Video在线视频

Video

下一节:Video

返回《Linux 内核分析与应用》慕课在线视频列表

Video课程教案、知识点、字幕

大家好我是《奔跑吧Linux内核》一书的作者笨叔

今天我给大家简单介绍一下如何编写一个简单的块设备驱动程序

在前面的内容中我们介绍了Linux

设备驱动程序中最常见的一种驱动

字符设备驱动

它是以字节流为单位的设备

今天要介绍的块设备驱动

它的最大特点是以块为单位进行传输的一种设备

这个特性和我们常见的磁盘非常吻合

在介绍块设备驱动之前

我们来简单回顾一下传统的机械硬盘的一些基本结构

它是由磁头(head)柱面(cylinder)扇区(sector)等组成

那什么是磁头呢

磁头是固定在可移动的机械臂上用来读写数据的

那什么是磁道呢

磁道是每个盘面都有多个同心圆组成

每个同心圆我们称之为一个磁道

那什么是柱面呢

多个盘面相同的磁道 (位置相同) 就共同组成了一个柱面

那什么是扇区呢

从磁盘中心往外画一条直线

那么可以将磁道划分为若干个弧段

每个磁道上的一个弧段

我们称之为一个扇区

那扇区是磁盘最小的组成单元

通常是 512 个字节

所以像磁盘这样的硬件设备

它的最小读写单元就是一个扇区(sector)

从磁盘的特性我们可以看到

块设备另外一个非常显著的特点

就是块设备是可以随机访问的

比如同一个文件

两个扇区大小的内容

在文件中是顺序存放的

但是在磁盘里

它可能不是顺序存放的

它有可能存放在两个不同的磁道上

的两个不同的扇区里面

而我们以键盘来做字符设备的话

比如我们输入“hello world”这几个字符

我们的键盘驱动只能以

键盘发出的“hello world”字节流来接收

我们不能只接收第一个字符然后跳过第二个字符

直接去接收第三个字符

这样是不行的

这就是字符设备和块设备第二个显著特点

除了传统的机械硬盘和光盘

其实还是有很多其他的块设备和存储技术

比如ramdisk SSD RAID

存储网络虚拟化存储等等

下面和大家介绍块设备驱动的整体结构这幅图是

块设备驱动在Linux内核中的架构框图

最上面的是文件系统层

包括我们最常用的文件系统比如EXT4ramfs等等

这里面还包括Linux的虚拟文件系统层VFS

对于块设备来说

我们通常不会像字符设备驱动一样

在应用程序中直接打开和操作块设备

而是通过文件系统来访问块设备的

第二层是通用块层

Generic Block Layer

这一层为各种类型的块设备

它建立一个统一的模型

这一层的主要工作是

接收文件系统层发出的磁盘请求

并最终向磁盘设备发出IO请求

所以它隐藏了很多底层硬件块设备的细节

为块设备提供一个抽象的一个模型

那 第三层是IO调度层

这个IO调度层

接收通用块层发出的IO请求

缓存这些请求然后根据IO调度算法

来合并相邻的请求

然后调用驱动层提供的处理函数

发送这些IO请求到硬件设备

这里IO调度层有多种调度算法

比如电梯调度算法

CFQ调度算法

因为传统机械硬盘物理结构特性

内核不会简单的按照请求

的顺序来提交给

块设备的那么IO调度器会根据场景进行优化

来进行合并和排序这样一些预操作

最下面的一层就是

块设备驱动

这一层就是实际的

块设备的驱动程序了比如磁盘驱动

光驱驱动 SSD驱动

一般来说

块设备驱动包含块设备的注册

打开关闭以及具体的IO处理

从上面的介绍我们知道了Linux内核的块设备之系统的架构

也了解了块设备驱动为了解决上层应用程序访问块设备

和底层块设备硬件之间的衔接问题

还建立了一个通用块层

但是要深入了解块设备驱动

我们还需要了解Linux内核为块设备驱动

设计的哪些主要的数据结构

要去理解为什么要抽象和设计这些数据结构

那我们首先介绍第一个数据结构就是block_device

数据结构

block_device数据结构用来抽象和描述一个块设备

Linux内核通常把

block_device数据结构和虚拟文件系统VFS关联起来

它的作用有点类似

虚拟文件系统和块设备子系统的一个粘合剂

用户程序一般是不会直接操作块设备的

通常是通过文件系统的接口来访问块设备

我们来介绍第二个数据结构

gendisk

Linux内核把磁盘类的设备

关于磁盘通用的那部分

提取出来使用一个gendisk

这样的一个数据结构来描述它

所以它实际上也是一种抽象

那么这个gendisk数据结构

它是可以表示一个已经分区的磁盘

也可以表示一个未分区的磁盘

那我们来看一下

gendisk它的一些重要成员

第一个是major

那major它表示的是这个

块设备的主设备号

那么它通常用来指定

当前设备对应的哪个设备驱动

那么下面的一个成员叫first

minor和minors

这两个是用来表示从设备号的一个范围

那么minors表示当前

这个gendisk对象所能包含的最大从设备个数

disk_name表示当前

块设备对象名称

part

tbl表示gendisk对象

的分区表信息fops

表示块设备的一组文件操作集合

queue它表示

当前这个gendisk对象

所代表的块设备的IO请求队列

下面我来介绍第三个非常重要的数据结构

就是block_device_operations

对于字符设备驱动

Linux内核里面有一个整套的操作方法

包含在file_operations数据结构里

那么对于块设备驱动而言Linux内核

也为它准备了一套类似的操作方法集

相对于字符设备的操作方法

块设备的函数

操作方法集明显要弱化了很多

这里主要的操作方法有

open打开块设备release

关闭块设备ioctl用来实现块设备的标准命令

media_changed 检查驱动器的介质是否改变

getgeo

获取磁盘器的一些信息

大家发现块设备的操作方法集

里已经没有了read write的这个方法了

并不是说块设备不需要实现读写函数

正是因为块设备的一些特性

已经把块设备的读写方法

使用了特别的读写请求队列来实现了

这个差异也是块设备和字符设备的一个重要的特点

块设备一般来讲

和系统交互的数据量是很大的

而字符设备一般来说交互的数据量不大

那么因此对于块设备子系统来说

针对这些不同的需求和特性

Linux内核进行了单独的优化

下面我们来介绍第四个数据结构就是请求队列

request_queue数据结构

那么对于来自通用块层的请求

在提交到块设备驱动的时候

我们需要构造一个请求就是request

也要把这些request

插入到块设备的请求队列里面

那么因此文件系统

有新的请求时就会把新的请求

加入到这个

请求队列里面

这里面通常会通过submit_io这个API

把来自文件系统的这个请求

添加到请求队列里面

只要请求队列不为空那么

队列里面对应的这些块设备驱动

程序就会从请求队列里面获取请求

然后把这些对应的请求发送到具体的块设备驱动里去处理

下面我们来介绍第五个数据结构bio数据结构

块设备子系统最重要的一个功能就是把文件系统层

发来的数据请求进行处理

而这个处理过程中最核心的一个数据

对象就是bio

那么bio有点类似一个网络的协议包

那么它在文件系统和块设备子系统里会来回地流动

把要读的数据

和要写的数据来回地搬移

通常来讲bio

它代表来自文件系统的一个请求

每个bio都代表不同的上

访问上下文

可能来自不同的文件系统

或者来自不同的应用程序

所以bio对象包含了

块设备处理一个请求

他所需要的所有的信息

上面给大家介绍了

块设备系统里面最常用的数据结构

那么这些数据结构怎么和文件进行一个关联呢

下面我们举一个例子

比如说文件系统里面有一个

test文件

我们在用户空间里使用

open函数来打开它

那么在程序中它就会

返回一个文件句柄fd

那么在内核空间里

这个被打开的文件

怎么和存储它的块设备关联起来的

我们来看这个图

当在用户空间打开一个文件的时候

在内核空间就会有一个

file数据结构和它对应

这个file数据结构有一个f_mapping字段

它是address_space数据结构

那么 这个数据结构是用来管理文件映射

到内存页面的

那么这个数据结构里面有一个

host这一个成员

它是inode数据结构

那么这个inode数据结构就是索引节点

用来存放文件和目录的基本信息

每个文件或者目录都有一个inode

那么这个inode数据结构里面有一个i_bdef

成员

那么它就是我们今天讲过的block_device数据结构

那么在这个block_device数据结构里面

有一个叫做bd_disk成员那么它指向了

gendisk数据结构

在gendisk数据结构里面有一个queue成员

它指向request_queue数据结构

那么来自文件系统的一个个请求

会被添加到request_queue队列里面

这个就是我们说的请求队列了

那么实际上的块设备驱动

就会从这个请求队列里面取request

然后进行处理最后把处理发送到硬件设备里面

这样就完成了一个完整的处理流程了

我们了解了块设备中的主要数据结构的设计理念

下面和大家介绍一下块设备驱动中

最常用的API接口函数

第一个是块设备的注册函数register_blkdev函数

这个函数有两个参数

major参数是块设备使用的主设备号

第二参数name是设备名

如果第一个参数major等于0的话

那么内核会自动分配一个新的主设备号

这个函数成功的话会返回一个主设备号

如果返回负数的话表示失败

第二个函数是注销函数

unregister_blkdev

它也有两个参数

传递的参数必须和register

blkdev

函数保持一致

下面介绍第三个API接口函数

初始化请求队列blk_init_queue

这个函数为当前设备分配一个request请求队列

第一个参数rfn

表示请求处理函数

它是一个函数指针

第二个参数lock

是请求队列要使用的一个锁

那么通常我们

设备驱动里面需要初始化这个锁

那么第四个函数是

注销请求队列

blk_cleanup_queue

下面我们看下一个

API函数分配gendisk对象

alloc_disk这个函数动态分配一个gendisk数据结构

第一个参数minors表示这个磁盘使用的次设备号的数量

也就是磁盘分区的数量

那么下一个函数是

注册gendisk对象

这个函数是add_dissk

那么这个函数会

向Linux内核注册一个磁盘设备

这个和字符设备的

cdev_add函数非常类似

都是向系统注册一个设备

这个函数的参数是刚才使用alloc

disk函数分配的gendisk的对象

那接下来一个

接口函数是释放gendisk函数

del_gendisk这个函数

当磁盘设备不再使用的时候

我们可以使用这个函数来释放一个gendisk对象

那么下面一个

接口函数是初始化一个BIO

大家可以使用 bio_init这个函数

那么接下来是一个提交BIO

submit

bio这个函数

当需要和块设备进行数据传输

或者对块设备发送IO请求的时候

内核需要发送请求对象bio到请求队列里面

那么时候可以调用submit

io接口函数来完成

那么这个函数有两个参数

第一个参数rw

表示发送请求是读呢还是写

那么第二个参数bio对象

如果

驱动程序需要单独调用submit

bio这个API的时候

那么驱动程序需要去负责填充bio

实验是最好的学习方法

我们在这里留了一个块设备的实验

我们使用

系统的内存作为存储介质来构造一个块设备

这个就是ramdisk

那么这个实验要求大家编写一个

块设备驱动来生成一个ramdisk

要求在这个ramdisk里面

可以使用ext4文件格式工具来进行格式化

然后对其进行读写操作

比如说我要存一个文件

或生成一个目录等等

另外还需要实现一个ioctl的操作来实现

实现HDIO_

GETGEO这个ioctl的命令

那么这个命令可以

读出这个块设备的一些参数

比如说有多少个磁头

多少个柱面

多少个扇区信息

那么最后要求写一个简单的用户

空间的测试程序

来读出这个块设备的一些参数

那么在节目最后啊

我们留一个有一点难度的思考题

有兴趣的同学可以顺着这个思考题去阅读Linux内核的代码

我们假设系统使用eMMC作为存储介质

文件系统采用ext4

那么使用C语言的open函数来打开一个文件

然后使用write函数

来写入内容

比如字符串“hello world”

请阅读Linux内核代码

跟踪从用户空间write函数

到eMMC存储设备

了解这个写入了动作

它经历了内核中哪些内核模块

这期间里面

它使用了哪些内核主要的数据结构

然后画出代码的流程图

那这个就是

这个进阶的思考题

这个思考题的阅读量是很大的

它涉及到虚拟文件系统层VFS

ext4文件系统

page cache管理

还有我们今天讲的bio的管理

还有我们今天讲的通用块层

以及整个块

设备的子系统

还有eMMC的块

设备驱动的程序等等

那么对理解

Linux内核整个存储架构是非常有帮助的

那我们今天的节目就到这里

谢谢大家

Linux 内核分析与应用课程列表:

第1章 概述

-1.1 Linux操作系统概述

--1.1 Linux 操作系统概述

-1.2 Linux内核结构以及内核模块编程

--Video

-1.3 Linux内核源码中的双链表结构

--Video

-1.4 源码分析-内核中的哈希表

--Video

-1.5 动手实践-Linux内核模块的插入和删除

--Video

-第1章 概述--章节测验

-第1章导学--引领你进入Linux内核的大门

第2章 内存寻址

-2.1 内存管理之内存寻址

--Video

-2.2 段机制

--Video

-2.3分页机制

--Video

-2.4 动手实践-把虚拟地址转换成物理地址

--Video

-第2章 内存寻址--章节测验

-第二章导学-从零打造自己的操作系统

第3章 进程管理

-3.1 进程概述

--Video

-3.2 Linux进程创建

--Video

-3.3 Linux进程调度

--Video

-3.4 动手实践-打印进程描述符task_struct中的字段

--Video

-3.5工程实践-基于内核模块的负载监控

--Video

-第3章 进程管理--章节测验

-第三章导学-进程背后琳琅满目的宝贝到哪里挖?

第4章 内存管理

-4.1 Linux内存管理机制

--Video

-4.2 进程用户空间管理机制

--Video

-4.3 物理内存分配与回收机制(上)

--Video

-4.4 物理内存分配与回收机制(下)

--Video

-4.5 动手实践-Linux内存映射基础(上)

--Video

-4.6 动手实践-Linux内存映射实现(中)

--Video

-4.7 动手实践-Linux内存映射测试(下)

--Video

-4.8 初学者对内存管理的常见疑惑

--初学者对内存管理的常见疑惑(一)

--初学者对内存管理的常见疑惑(二)

--初学者对内存管理的常见疑惑(三)

-第4章 内存管理--章节测验

第5章 中断

-5.1 中断机制概述

--Video

-5.2 中断处理机制

--Video

-5.3 中断下半部处理机制

--Video

-5.4 时钟中断机制

--Video

-5.5 动手实践-中断上半部的代码分析及应用

--Video

-5.6 动手实践-中断下半部的代码分析及应用

--Video

-第5章 中断--章节测验

第6章 系统调用

-6.1 Linux中的各种API

--Video

-6.2 系统调用机制

--Video

-6.3 动手实践-添加系统调用(系统调用日志收集系统)

--Video

-第6章 系统调用--章节测验

第7章 内核同步

-7.1 内核同步概述

--Video

-7.2 内核同步机制

--Video

-7.3 动手实践-内核多任务并发实例(上)

--Video

-7.4 动手实践-内核多任务并发实例(下)

--Video

-第7章 内核同步--章节测验

第8章 文件系统

-8.1 虚拟文件系统的引入

--Video

-8.2 虚拟文件系统的主要数据结构

--Video

-8.3 文件系统中的各种缓存

--Video

-8.4 页高速缓存机制以及读写

--Video

-8.5 动手实践-编写一个文件系统(上)

--Video

-8.6 动手实践-编写一个文件系统(中)

--Video

-8.7 动手实践-编写一个文件系统(下)

--Video

-第8章 文件系统--章节测验

第9章 设备驱动

-9.1 设备驱动概述

--Video

-9.2 I/O空间管理

--Video

-9.3 设备驱动模型

--Video

-9.4 字符设备驱动程序简介

--Video

-9.5 块设备驱动程序简介

--Video

-9.6 动手实践-编写字符设备驱动程序

--Video

-9.7工程实践-编写块设备驱动的基础(上)

--Video

-9.8 工程实践-块设备驱动程序分析(中)

--Video

-9.9 工程实践-块设备驱动程序实现(下)

--Video

-第9章 设备驱动--章节测验

致谢与说明

-致谢与说明

--Video

直播视频:从Linux内核学习到自主操作系统研发

-从Linux内核学习到自主操作系统研发

附录:实验代码、课件以及相关素材

-各章实验代码

-《Linux内核分析与应用》课件

-《Linux操作系统原理与应用》教材课堂视频

Video笔记与讨论

也许你还感兴趣的课程:

© 柠檬大学-慕课导航 课程版权归原始院校所有,
本网站仅通过互联网进行慕课课程索引,不提供在线课程学习和视频,请同学们点击报名到课程提供网站进行学习。