Linux 块设备驱动 (1)

本文详细介绍了Linux块设备驱动Sampleblk的初始化、退出过程,包括块设备注册、驱动状态数据结构分配、Request Queue初始化、操作函数表初始化和磁盘创建。同时,解释了策略函数在块设备驱动中的作用和实现,以及如何在Linux内核4.6.0环境下编译和加载模块。
摘要由CSDN通过智能技术生成

1. 背景

Sampleblk 是一个用于学习目的的 Linux 块设备驱动项目。其中 day1 的源代码实现了一个最简的块设备驱动,源代码只有 200 多行。本文主要围绕这些源代码,讨论 Linux 块设备驱动开发的基本知识。

开发 Linux 驱动需要做一系列的开发环境准备工作。Sampleblk 驱动是在 Linux 4.6.0 下开发和调试的。由于在不同 Linux 内核版本的通用 block 层的 API 有很大变化,这个驱动在其它内核版本编译可能会有问题。开发,编译,调试内核模块需要先准备内核开发环境,编译内核源代码。这些基础的内容互联网上随处可得,本文不再赘述。

此外,开发 Linux 设备驱动的经典书籍当属 Device Drivers, Third Edition 简称 LDD3。该书籍是免费的,可以自由下载并按照其规定的 License 重新分发。

2. 模块初始化和退出

Linux 驱动模块的开发遵守 Linux 为模块开发者提供的基本框架和 API。LDD3 的 hello world 模块提供了写一个最简内核模块的例子。而 Sampleblk 块驱动的模块与之类似,实现了 Linux 内核模块所必需的模块初始化和退出函数,

module_init(sampleblk_init);
module_exit(sampleblk_exit);

与 hello world 模块不同的是,Sampleblk 驱动的初始化和退出函数要实现一个块设备驱动程序所必需的基本功能。本节主要针对这部分内容做详细说明。

2.1 sampleblk_init

归纳起来,sampleblk_init 函数为完成块设备驱动的初始化,主要做了以下几件事情,

2.1.1 块设备注册

调用 register_blkdev 完成 major number 的分配和注册,函数原型如下,

int register_blkdev(unsigned int major, const char *name);

Linux 内核为块设备驱动维护了一个全局哈希表 major_names这个哈希表的 bucket 是 [0..255] 的整数索引的指向 blk_major_name 的结构指针数组。

static struct blk_major_name {
    struct blk_major_name *next;
    int major;
    char name[16];
} *major_names[BLKDEV_MAJOR_HASH_SIZE];

register_blkdevmajor 参数不为 0 时,其实现就尝试在这个哈希表中寻找指定的 major 对应的 bucket 里的空闲指针,分配一个新的blk_major_name,按照指定参数初始化 majorname。假如指定的 major 已经被别人占用(指针非空),则表示 major 号冲突,反回错误。

major 参数为 0 时,则由内核从 [1..255] 的整数范围内分配一个未使用的反回给调用者。因此,虽然 Linux 内核的主设备号 (Major Number) 是 12 位的,不指定 major 时,仍旧从 [1..255] 范围内分配。

Sampleblk 驱动通过指定 major 为 0,让内核为其分配和注册一个未使用的主设备号,其代码如下,

sampleblk_major = register_blkdev(0, "sampleblk");
if (sampleblk_major < 0)
    return sampleblk_major;
2.1.2 驱动状态数据结构的分配和初始化

通常,所有 Linux 内核驱动都会声明一个数据结构来存储驱动需要频繁访问的状态信息。这里,我们为 Sampleblk 驱动也声明了一个,

struct sampleblk_dev {
    int minor;
    spinlock_t lock;
    struct request_queue *queue;
    struct gendisk *disk;
    ssize_t size;
    void *data;
};

为了简化实现和方便调试,Sampleblk 驱动暂时只支持一个 minor 设备号,并且可以用以下全局变量访问,

struct sampleblk_dev *sampleblk_dev = NULL;

下面的代码分配了 sampleblk_dev 结构,并且给结构的成员做了初始化,

sampleblk_dev = kzalloc(sizeof(struct sampleblk_dev), GFP_KERNEL);
if (!sampleblk_dev) {
    rv = -ENOMEM;
    goto fail;
}

sampleblk_dev->size = sampleblk_sect_size * sampleblk_nsects;
sampleblk_dev->data = vmalloc(sampleblk_dev->size);
if (!sampleblk_dev->data) {
    rv = -ENOMEM;
    goto fail_dev;
}
sampleblk_dev->minor = minor;
2.1.3 Request Queue 初始化

使用 blk_init_queue 初始化 Request Queue 需要先声明一个所谓的策略 (Strategy) 回调和保护该 Request Queue 的自旋锁。然后将该策略回调的函数指针和自旋锁指针做为参数传递给该函数。

在 Sampleblk 驱动里,就是 sampleblk_request 函数和 sampleblk_dev->lock

spin_lock_init(&sampleblk_dev->lock);
sampleblk_dev->queue = blk_init_queue(sampleblk_request,
    &sampleblk_dev->lock);
if (!sampleblk_dev->queue) {
    rv = -ENOMEM;
    goto fail_data;
}

策略函数 sampleblk_request 用于执行块设备的 read 和 write IO 操作,其主要的入口参数就是 Request Queue 结构:struct request_queue。关于策略函数的具体实现我们稍后介绍。

当执行 blk_init_queue 时,其内部实现会做如下的处理,

  1. 从内存中分配一个 struct request_queue 结构。
  2. 初始化 struct request_queue 结构。对调用者来说,其中以下部分的初始化格外重要,
    • blk_init_queue 指定的策略函数指针会赋值给 struct request_queuerequest_fn 成员。<
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值