前言
@和原子哥一起学习Linux
开发环境:I.MX6Ull开发板
参考书籍:
《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.6.pdf》
《linux设备驱动开发详解-基于最新的linux4.0内核》
个人学习笔记,欢迎讨论
一、什么是块设备?
块设备驱动其实就是存储设备驱动,块设备只能以块为单位进行读写访问。
二、使用梳理
linux 内核使用 gendisk 来描述一个磁盘设备, 使用request_queue、 request 和 bio去读写数据,每个磁盘(gendisk)都要分配一个 request_queue。
内核将对块设备的读写都发送到请求队列 request_queue 中, request_queue 中是大量的request(请求结构体),而 request 又包含了 bio, bio 保存了读写相关数据。
对于机械硬盘而言,将那些杂乱的访问按照一定的顺序进行排列可以有效提高磁盘性能, linux 里面针对不同的存储设备实现了不同的 I/O 调度算法。
1)机械硬盘这样的存储设备,需要 I/O 调度器来优化数据读写过程,需要使用请求队列读写设备
2)对于 EMMC、 SD 卡这样的非机械设备,可以进行完全随机访问,所以就不需要复杂的 I/O 调度器,不需要请求队列
1.使用请求队列
1.1 初始化
省略错误 。。。
//申请内存
ramdisk.ramdiskbuf = kzalloc(RAMDISK_SIZE, GFP_KERNEL);
。。。
//初始化自旋锁,后面用
spin_lock_init(&ramdisk.lock);
//注册块设备
ramdisk.major = register_blkdev(0, RAMDISKNAME);
。。。
//申请设备gendisk,申请3个分区
ramdisk.gendisk = alloc_disk(RAMDISK_MINOR);
。。。
//分配初始化请求队列
ramdisk.queue = blk_init_queue(do_ramdisk_request, &ramdisk.lock);
。。。
//注册disk
ramdisk.gendisk->major = ramdisk.major;
ramdisk.gendisk->first_minor = 0; //起始设备号
ramdisk.gendisk->fops = &ramdisk_fops;
ramdisk.gendisk->queue = ramdisk.queue;
sprintf(ramdisk.gendisk->disk_name, RAMDISKNAME);
//使用 set_capacity 函数设置本块设备容量大小
set_capacity(ramdisk.gendisk, RAMDISK_SIZE/512);
//将 gendisk 添加到内核中,也就是向内核添加一个磁盘设备。
add_disk(ramdisk.gendisk);
return 0;
1.2 队列请求处理函数
static void ramdisk_transfer(struct request *req)
{
unsigned long start = blk_rq_pos(req) << 9; //扇区地址转字节地址
unsigned long len = blk_rq_cur_bytes(req);
//使用 bio_data 函数获取请求中 bio 保存的数据
void *buffer = bio_data(req->bio);
if (rq_data_dir(req) == READ)
memcpy(buffer, (char *)ramdisk.ramdiskbuf + start, len);
else
memcpy((char *)ramdisk.ramdiskbuf + start, buffer, len);
}
static void do_ramdisk_request(struct request_queue *q)
{
struct request *req;
int err = 0;
//循环处理队列
req = blk_fetch_request(q);
while (req != NULL) {
ramdisk_transfer(req);
//* 判断是否为最后一个请求,如果不是的话就获取下一个请求
if (!__blk_end_request_cur(req, err))
req = blk_fetch_request(q);
}
}
2.不使用请求队列
2.1 初始化
省略错误 。。。
//申请内存
ramdisk.ramdiskbuf = kzalloc(RAMDISK_SIZE, GFP_KERNEL);
。。。
//注册设备
ramdisk.major = register_blkdev(0, RAMDISKNAME);
。。。
//申请设备gendisk,申请3个分区
ramdisk.gendisk = alloc_disk(RAMDISK_MINOR);
。。。
//分配初始化请求队列
ramdisk.queue = blk_alloc_queue(GFP_KERNEL);
。。。
//设置制造请求
blk_queue_make_request(ramdisk.queue, ramdisk_make_request_fn);
//注册disk
ramdisk.gendisk->major = ramdisk.major;
ramdisk.gendisk->first_minor = 0; //起始设备号
ramdisk.gendisk->fops = &ramdisk_fops;
ramdisk.gendisk->queue = ramdisk.queue;
sprintf(ramdisk.gendisk->disk_name, RAMDISKNAME);
//使用 set_capacity 函数设置本块设备容量大小
set_capacity(ramdisk.gendisk, RAMDISK_SIZE/512);
//将 gendisk 添加到内核中,也就是向内核添加一个磁盘设备。
add_disk(ramdisk.gendisk);
return 0;
2.2 制造请求函数
static void ramdisk_make_request_fn(struct request_queue *q, struct bio *bio)
{
int offset;
struct bio_vec bvec;
struct bvec_iter iter;
unsigned long len = 0;
offset = bio->bi_iter.bi_sector << 9; //获取设备偏移地址
bio_for_each_segment(bvec, bio, iter) {
char *ptr = page_address(bvec.bv_page) + bvec.bv_offset;
len = bvec.bv_len;
if (bio_data_dir(bio) == READ)
memcpy(ptr, (char *)ramdisk.ramdiskbuf + offset, len);
else if(bio_data_dir(bio) == WRITE) /* 写数据 */
memcpy((char *)ramdisk.ramdiskbuf + offset, ptr, len);
offset += len;
}
set_bit(BIO_UPTODATE, &bio->bi_flags);
bio_endio(bio, 0);
return;
}
运行验证
加载模块
fdisk -l //查看磁盘信息
mkfs.vfat /dev/ramdisk//格式化/dev/ramdisk
mount /dev/ramdisk /tmp//挂载后就可以访问了