Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >[046]块设备驱动初探

[046]块设备驱动初探

作者头像
王小二
发布于 2020-06-08 04:09:50
发布于 2020-06-08 04:09:50
2.1K00
代码可运行
举报
运行总次数:0
代码可运行

前言

研究IO也很久了,一直无法串联bio和块设备驱动,只知道bio经过IO调度算法传递到块设备驱动,怎么过去的,IO调度算法在哪里发挥作用,一直没有完全搞明白,查看了很多资料,终于对块设备驱动有所理解,也打通了bio到块设备。

一、传统块设备

我们先来实现一个基于内存的传统块设备驱动。

1.1 初始化一些东西

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//暂时使用COMPAQ_SMART2_MAJOR作为主设备号,防止设备号冲突
#define SIMP_BLKDEV_DEVICEMAJOR   COMPAQ_SMART2_MAJOR
//块设备名
#define SIMP_BLKDEV_DISKNAME "simp_blkdev"

//用一个数组来模拟一个物理存储
#define SIMP_BLKDEV_BYTES (16*1024*1024)
unsigned char simp_blkdev_data[SIMP_BLKDEV_BYTES];

static struct request_queue *simp_blkdev_queue;//请求队列
static struct gendisk *simp_blkdev_disk;//块设备

struct block_device_operations simp_blkdev_fops = {//块设备的操作函数
    .owner = THIS_MODULE, 
};

1.2 加载驱动

整个过程 1.创建request_queue(每个块设备一个队列),绑定函数simp_blkdev_do_request 2.创建一个gendisk(每个块设备就是一个gendisk) 3.将request_queue和gendisk绑定 4.注册gendisk

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static int __init simp_blkdev_init(void)
{
    int ret;
    //初始化请求队列
    simp_blkdev_queue = blk_init_queue(simp_blkdev_do_request, NULL);//这个方法将会在1.5仔细分析
    simp_blkdev_disk = alloc_disk(1);//申请simp_blkdev_disk
  
    //初始化simp_blkdev_disk
    strcpy(simp_blkdev_disk->disk_name, SIMP_BLKDEV_DISKNAME);//设备名
    simp_blkdev_disk->major = SIMP_BLKDEV_DEVICEMAJOR;//主设备号
    simp_blkdev_disk->first_minor = 0;//副设备号
    simp_blkdev_disk->fops = &simp_blkdev_fops;//块设备操作函数指针
    simp_blkdev_disk->queue = simp_blkdev_queue; 
    //设置块设备的大小,大小是扇区的数量,一个扇区是512B
    set_capacity(simp_blkdev_disk, SIMP_BLKDEV_BYTES>>9);
    add_disk(simp_blkdev_disk);//注册simp_blkdev_disk
    return 0;
}

1.3 simp_blkdev_do_request

1.调用调度算法的elv_next_request方法获得下一个处理的request 2.如果是读,将simp_blkdev_data拷贝到request.buffer, 3.如果是写,将request.buffer拷贝到simp_blkdev_data 4.调用end_request通知完成

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static void simp_blkdev_do_request(struct request_queue *q) 
{
    struct request *req;
    while ((req = elv_next_request(q)) != NULL) {//根据调度算法获得下一个request
        switch (rq_data_dir(req)) {//判断读还是写
        case READ:
            memcpy(req->buffer, simp_blkdev_data + (req->sector << 9), 
            req->current_nr_sectors << 9);
            end_request(req, 1);//完成通知
            break;
        case WRITE:
            memcpy(simp_blkdev_data + (req->sector << 9),req->buffer, 
            req->current_nr_sectors << 9); 
            end_request(req, 1);//完成通知
            break;
        default:
             /* No default because rq_data_dir(req) is 1 bit */
             break;
        }
}

1.4 卸载驱动

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static void __exit simp_blkdev_exit(void)
{
    del_gendisk(simp_blkdev_disk);//注销simp_blkdev_disk
    put_disk(simp_blkdev_disk);//释放simp_blkdev_disk
    blk_cleanup_queue(simp_blkdev_queue);//释放请求队列
}

千万别忘记下面代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
module_init(simp_blkdev_init); 
module_exit(simp_blkdev_exit);

1.5 blk_init_queue

看了上面的代码,可能还是无法清晰的了解request_queue如何串联bio和块设备驱动,我们深入看一下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
simp_blkdev_queue = blk_init_queue(simp_blkdev_do_request, NULL);//调用blk_init_queue

struct request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
{
    return blk_init_queue_node(rfn, lock, NUMA_NO_NODE);//跳转1.5.1
}
EXPORT_SYMBOL(blk_init_queue);

//1.5.1
struct request_queue *
blk_init_queue_node(request_fn_proc *rfn, spinlock_t *lock, int node_id)
{
    struct request_queue *q;
    q = blk_alloc_queue_node(GFP_KERNEL, node_id, lock);
    if (!q)
        return NULL;

    q->request_fn = rfn;//也就是simp_blkdev_do_request
    if (blk_init_allocated_queue(q) < 0) {//转1.5.2
        blk_cleanup_queue(q);
        return NULL;
    }
    return q;
}
EXPORT_SYMBOL(blk_init_queue_node);

//1.5.2
int blk_init_allocated_queue(struct request_queue *q)
{
    ...
    blk_queue_make_request(q, blk_queue_bio);//转1.5.3
    if (elevator_init(q))//初始化IO调度算法
        goto out_exit_flush_rq;
    return 0;
    ...
}
EXPORT_SYMBOL(blk_init_allocated_queue);

//1.5.3
void blk_queue_make_request(struct request_queue *q, make_request_fn *mfn)
{
    ...
    q->make_request_fn = mfn;//mfn也就是blk_queue_bio
    ...
}
EXPORT_SYMBOL(blk_queue_make_request);

static blk_qc_t blk_queue_bio(struct request_queue *q, struct bio *bio)//完成bio如何插入到request_queue
{
    //IO调度算法发挥作用的地方
}
整个调用完成之后,会绑定当前块设备的request_queue两个重要方法
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
q->make_request_fn = blk_queue_bio;//linux默认实现
q->request_fn = simp_blkdev_do_request;//驱动自己实现
1.5.1 make_request_fn(struct request_queue *q, struct bio *bio)

submit_bio会调用make_request_fn将bio封装成request插入到request_queue,默认会使用linux系统实现的blk_queue_bio。如果我们替换make_request_fn,会导致IO调度算法失效,一般不会去改。

1.5.2 request_fn(struct request_queue *q)

这个方法一般是驱动实现,也就是simp_blkdev_do_request,从request_queue中取出合适的request进行处理,一般会调用调度算法的elv_next_request方法,获得一个推荐的request。

1.5.3 bio-块设备

通过make_request_fn和request_fn,我们将bio和块设备驱动串联起来了。 而且IO调度算法会在这两个函数发挥作用。

给自己挖了两个坑 1.整个过程中受到了IO调度算法,IO调度算法如何发挥作用? 2.make_request_fn之后如何触发request_fn?

二、超高速块设备

传统块设备访问是通过磁头,IO调度算法可以优化多个IO请求的时候移动磁头的顺序。

IO调度算法

假如你是图书管理员,十个人找你借十本书,在图书馆的不同角落,你肯定会选择一条最短的线路去拿这十本书。其实这就是IO调度算法

超高速块设备

假如这个图书馆只有一个窗口,借书的人只要说出书名,书就会从窗口飞出来,这样子还需要什么管理员,更不需要什么IO调度算法,这个图书馆就是超高速块设备。

上面写的基于内存的块设备不就是一个超高速块设备嘛,我们能不能写一个没有中间商的驱动

2.1 simp_blkdev_init

我们需要重写一下init代码,不调用blk_init_queue。直接用下面的2.1.1和2.1.2的方法。 init之后,我们会将make_request_fn设置成simp_blkdev_make_request

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static int __init simp_blkdev_init(void)
{
    int ret;
    //初始化请求队列
    simp_blkdev_queue = blk_alloc_queue(GFP_KERNEL);//2.1.1
    //将simp_blkdev_make_request绑定到request_queue的make_request_fn。
    blk_queue_make_request(simp_blkdev_queue, simp_blkdev_make_request);//2.1.2
    simp_blkdev_disk = alloc_disk(1);//申请simp_blkdev_disk

    //初始化simp_blkdev_disk
    strcpy(simp_blkdev_disk->disk_name, SIMP_BLKDEV_DISKNAME);//设备名
    simp_blkdev_disk->major = SIMP_BLKDEV_DEVICEMAJOR;//设备号
    simp_blkdev_disk->first_minor = 0;
    simp_blkdev_disk->fops = &simp_blkdev_fops;//块设备操作函数指针
    simp_blkdev_disk->queue = simp_blkdev_queue; 
    set_capacity(simp_blkdev_disk, SIMP_BLKDEV_BYTES>>9);//设置块设备的大小,大小是扇区的数量,一个扇区是512B
    add_disk(simp_blkdev_disk);//注册simp_blkdev_disk
    return 0;

err_alloc_disk:
    blk_cleanup_queue(simp_blkdev_queue);
err_alloc_queue:
    return ret;
}

2.2 simp_blkdev_make_request

跳过中间商,直接将simp_blkdev_data拷贝到bio的page,调用bio_endio通知读写完成, 从头到尾request_queue和request就没有用到

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static int simp_blkdev_make_request(struct request_queue *q, struct bio *bio) {
    struct bio_vec *bvec;
    int i;
    void *dsk_mem;
    //获得块设备内存的起始地址,bi_sector代表起始扇区
    dsk_mem = simp_blkdev_data + (bio->bi_sector << 9);
    bio_for_each_segment(bvec, bio, i) {//遍历每一个块
        void *iovec_mem;
        switch (bio_rw(bio)) {
            case READ:
            case READA:
                //page代表高端内存无法直接访问,需要通过kmap映射到线性地址
                iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;//页数加偏移量获得对应的内存地址
                memcpy(iovec_mem, dsk_mem, bvec->bv_len);//将数据拷贝到内存中
                kunmap(bvec->bv_page);//归还线性地址
                break;
            case WRITE:
                iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset; 
                memcpy(dsk_mem, iovec_mem, bvec->bv_len); 
                kunmap(bvec->bv_page);
                break;
            default:
                printk(KERN_ERR SIMP_BLKDEV_DISKNAME": unknown value of bio_rw: %lu\n", bio_rw(bio));
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24) 
                bio_endio(bio, 0, -EIO);//报错
#else
                bio_endio(bio, -EIO);//报错
#endif
                return 0;
        }
        dsk_mem += bvec->bv_len;//移动地址
    }

#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24) 
                bio_endio(bio, bio->bi_size, 0);
#else
                bio_endio(bio, 0);
#endif
                return 0;
}

2.2 没有中间商

因为我们直接把数据的访问实现在make_request_fn,也就是simp_blkdev_make_request。 这样子就摆脱了request_queue和IO调度算法。没有中间商,访问速度杠杠的。

kernel中的zram设备就是基于内存没有中间商赚差价的块设备,代码很类似,有兴趣的可以看一下。

三、总结

经过那么长时间的学习,捅破层层的窗户纸,终于把IO打通了,但是文件系统,IO调度算法,每一模块都是值得我深入仔细研究,真正的挑战才刚刚开始。

代码参考

写一个块设备驱动.pdf

资料参考

《Linux内核设计与实现》 《Linux内核完全注释》 Linux.Generic.Block.Layer.pdf https://zhuanlan.zhihu.com/c_132560778

四、完整代码

没有在内核中编译过,运行过

4.1 传统块设备

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#define SIMP_BLKDEV_DEVICEMAJOR   COMPAQ_SMART2_MAJOR//暂时使用COMPAQ_SMART2_MAJOR作为主设备号
#define SIMP_BLKDEV_DISKNAME "simp_blkdev"
#define SIMP_BLKDEV_BYTES (16*1024*1024)

unsigned char simp_blkdev_data[SIMP_BLKDEV_BYTES];

static struct request_queue *simp_blkdev_queue;//请求队列
static struct gendisk *simp_blkdev_disk;

struct block_device_operations simp_blkdev_fops = {
    .owner = THIS_MODULE, 
};

static int __init simp_blkdev_init(void)
{
    int ret;
    elevator_t *old_e;
    //初始化请求队列
    simp_blkdev_queue = blk_init_queue(simp_blkdev_do_request, NULL);
    if (!simp_blkdev_queue) {
       ret = -ENOMEM;
       goto err_init_queue;
    } 
    old_e = simp_blkdev_queue->elevator;//检查默认的调度器
    if (IS_ERR_VALUE(elevator_init(simp_blkdev_queue, "noop")))//检查切换调度器"noop"是否成功
        printk(KERN_WARNING "Switch elevator failed, using default\n"); 
    else
        elevator_exit(old_e);//释放老的调度器
    simp_blkdev_disk = alloc_disk(1);//申请simp_blkdev_disk
    if (!simp_blkdev_disk) {
        ret = -ENOMEM;
        goto err_alloc_disk;
    }
    //初始化simp_blkdev_disk
    strcpy(simp_blkdev_disk->disk_name, SIMP_BLKDEV_DISKNAME);//设备名
    simp_blkdev_disk->major = SIMP_BLKDEV_DEVICEMAJOR;//设备号
    simp_blkdev_disk->first_minor = 0;
    simp_blkdev_disk->fops = &simp_blkdev_fops;//块设备操作函数指针
    simp_blkdev_disk->queue = simp_blkdev_queue; 
    set_capacity(simp_blkdev_disk, SIMP_BLKDEV_BYTES>>9);//设置块设备的大小,大小是扇区的数量,一个扇区是512B
    add_disk(simp_blkdev_disk);//注册simp_blkdev_disk
    return 0;

err_alloc_disk:
    blk_cleanup_queue(simp_blkdev_queue);
err_init_queue:
    return ret;
}


static void __exit simp_blkdev_exit(void)
{
    del_gendisk(simp_blkdev_disk);//注销simp_blkdev_disk
    put_disk(simp_blkdev_disk);//释放simp_blkdev_disk
    blk_cleanup_queue(simp_blkdev_queue);//释放请求队列
}

//负责处理块设备的请求
static void simp_blkdev_do_request(struct request_queue *q) 
{
    struct request *req;
    while ((req = elv_next_request(q)) != NULL) {//根据调度算法获得下一个request
        //sector有点类似于起始地址,有点类似于请求的块数
        if ((req->sector + req->current_nr_sectors) << 9 > SIMP_BLKDEV_BYTES) {
            printk(KERN_ERR SIcurrent_nr_sectorsMP_BLKDEV_DISKNAME": bad request: block=%llu, count=%u\n", (unsigned long long)req->sector, req->current_nr_sectors);
            end_request(req, 0);
            continue; 
        }
        switch (rq_data_dir(req)) {//判断读还是写
        case READ:
            memcpy(req->buffer, simp_blkdev_data + (req->sector << 9), 
            req->current_nr_sectors << 9);
            end_request(req, 1);//结束请求
            break;
        case WRITE:
            memcpy(simp_blkdev_data + (req->sector << 9),req->buffer, 
            req->current_nr_sectors << 9); 
            end_request(req, 1);//结束请求
            break;
        default:
             /* No default because rq_data_dir(req) is 1 bit */
             break;
        }
}

module_init(simp_blkdev_init); 
module_exit(simp_blkdev_exit);

4.2 超高速块设备

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#define SIMP_BLKDEV_DEVICEMAJOR   COMPAQ_SMART2_MAJOR//暂时使用COMPAQ_SMART2_MAJOR作为主设备号
#define SIMP_BLKDEV_DISKNAME "simp_blkdev"
#define SIMP_BLKDEV_BYTES (16*1024*1024)

unsigned char simp_blkdev_data[SIMP_BLKDEV_BYTES];

static struct request_queue *simp_blkdev_queue;//请求队列
static struct gendisk *simp_blkdev_disk;

struct block_device_operations simp_blkdev_fops = {
    .owner = THIS_MODULE, 
};

static int __init simp_blkdev_init(void)
{
    int ret;
    //初始化请求队列
    simp_blkdev_queue = blk_alloc_queue(GFP_KERNEL);
    if (!simp_blkdev_queue) {
       ret = -ENOMEM;
       goto err_alloc_queue;
    }
    //这样子搞可以完全拜托调度器
    blk_queue_make_request(simp_blkdev_queue, simp_blkdev_make_request);
    simp_blkdev_disk = alloc_disk(1);//申请simp_blkdev_disk
    if (!simp_blkdev_disk) {
        ret = -ENOMEM;
        goto err_alloc_disk;
    }
    //初始化simp_blkdev_disk
    strcpy(simp_blkdev_disk->disk_name, SIMP_BLKDEV_DISKNAME);//设备名
    simp_blkdev_disk->major = SIMP_BLKDEV_DEVICEMAJOR;//设备号
    simp_blkdev_disk->first_minor = 0;
    simp_blkdev_disk->fops = &simp_blkdev_fops;//块设备操作函数指针
    simp_blkdev_disk->queue = simp_blkdev_queue; 
    set_capacity(simp_blkdev_disk, SIMP_BLKDEV_BYTES>>9);//设置块设备的大小,大小是扇区的数量,一个扇区是512B
    add_disk(simp_blkdev_disk);//注册simp_blkdev_disk
    return 0;

err_alloc_disk:
    blk_cleanup_queue(simp_blkdev_queue);
err_alloc_queue:
    return ret;
}


static void __exit simp_blkdev_exit(void)
{
    del_gendisk(simp_blkdev_disk);//注销simp_blkdev_disk
    put_disk(simp_blkdev_disk);//释放simp_blkdev_disk
    blk_cleanup_queue(simp_blkdev_queue);//释放请求队列
}

//将bio放到request_queue中
static int simp_blkdev_make_request(struct request_queue *q, struct bio *bio) {
    struct bio_vec *bvec;
    int i;
    void *dsk_mem;
    if ((bio->bi_sector << 9) + bio->bi_size > SIMP_BLKDEV_BYTES) {//如果访问的空间
        printk(KERN_ERR SIMP_BLKDEV_DISKNAME ": bad request: block=%llu, count=%u\n",
            (unsigned long long)bio->bi_sector, bio->bi_size);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24) 
        bio_endio(bio, 0, -EIO);
#else
        bio_endio(bio, -EIO);
#endif
        return 0;
    }
    //获得块设备内存的起始地址,bi_sector代表起始扇区
    dsk_mem = simp_blkdev_data + (bio->bi_sector << 9);
    bio_for_each_segment(bvec, bio, i) {//遍历每一个块
        void *iovec_mem;
        switch (bio_rw(bio)) {
            case READ:
            case READA:
                //page代表高端内存无法直接访问,需要通过kmap映射到线性地址
                iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;//页数加偏移量获得对应的内存地址
                memcpy(iovec_mem, dsk_mem, bvec->bv_len);//将数据拷贝到内存中
                kunmap(bvec->bv_page);//归还线性地址
                break;
            case WRITE:
                iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset; 
                memcpy(dsk_mem, iovec_mem, bvec->bv_len); 
                kunmap(bvec->bv_page);
                break;
            default:
                printk(KERN_ERR SIMP_BLKDEV_DISKNAME": unknown value of bio_rw: %lu\n", bio_rw(bio));
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24) 
                bio_endio(bio, 0, -EIO);//报错
#else
                bio_endio(bio, -EIO);//报错
#endif
                return 0;
        }
        dsk_mem += bvec->bv_len;//移动地址
    }

#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24) 
                bio_endio(bio, bio->bi_size, 0);
#else
                bio_endio(bio, 0);
#endif
                return 0;
}

module_init(simp_blkdev_init); 
module_exit(simp_blkdev_exit);
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Linux驱动开发: 块设备驱动开发
块是一种具有一定结构的随机存取设备,对这种设备的读写是按块进行的,他使用缓冲区来存放暂时的数据,待条件成熟后,从缓存一次性写入设备或者从设备一次性读到缓冲区。 块设备是与字符设备并列的概念, 这两类设备在 Linux 中驱动的结构有较大差异,总体而言, 块设备驱动比字符设备驱动要复杂得多,在 I/O 操作上表现出极大的不同,缓冲、 I/O 调度、请求队列等都是与块设备驱动相关的概念。
DS小龙哥
2022/01/17
32.5K0
Linux驱动开发: 块设备驱动开发
14.块设备驱动
1、字符设备驱动:   当我们的应用层读写(read()/write())字符设备驱动时,是按字节/字符来读写数据的,期间没有任何缓存区,因为数据量小,不能随机读取数据,例如:按键、LED、鼠标、键盘等 2、块设备:   块设备是i/o设备中的一类, 当我们的应用层对该设备读写时,是按扇区大小来读写数据的,若读写的数据小于扇区的大小,就会需要缓存区, 可以随机读写设备的任意位置处的数据,例如 普通文件(.txt,.c等),硬盘,U盘,SD卡。 3、块设备结构: 段(Segments):由若干个块组成。是Linux内存管理机制中一个内存页或者内存页的一部分。 块 (Blocks): 由Linux制定对内核或文件系统等数据处理的基本单位。通常由1个或多个扇区组成。(对Linux操作系统而言) 扇区(Sectors):块设备的基本单位。通常在512字节到32768字节之间,默认512字节 应用程序进行文件的读写,通过文件系统将文件的读写转换为块设备驱动操作硬件。
嵌入式与Linux那些事
2021/05/20
1K0
Linux下驱动开发_块设备驱动开发(内存模拟存储)
块设备驱动块是Linux下3大设备驱动框架之一,块设备主要是针对存储类型的设备设计的驱动,配合文件系统完成数据存储。在应用层的cp、cd、touch、vim、mount等等可以操作文件,可以操作目录的命令都会通过文件系统,通过块设备驱动完成对底层存储设备的访问,实现数据读取或者写入。
DS小龙哥
2022/10/31
4.7K0
Linux下驱动开发_块设备驱动开发(内存模拟存储)
23.Linux-块设备驱动(详解)
诺谦
2018/01/03
3.5K0
23.Linux-块设备驱动(详解)
22.Linux-块设备驱动之框架详细分析(详解)
本节目的:     通过分析块设备驱动的框架,知道如何来写驱动 1.之前我们学的都是字符设备驱动,先来回忆一下 字符设备驱动: 当我们的应用层读写(read()/write())字符设备驱动时,是按字
诺谦
2018/01/03
2.3K0
22.Linux-块设备驱动之框架详细分析(详解)
Linux块设备驱动详解
  传统的机械硬盘一般为3.5英寸硬盘,并由多个圆形蝶片组成,每个蝶片拥有独立的机械臂和磁头,每个堞片的圆形平面被划分了不同的同心圆,每一个同心圆称为一个磁道,位于最外面的道的周长最长称为外道,最里面的道称为内道,通常硬盘厂商会将圆形蝶片最靠里面的一些内道(速度较慢,影响性能)封装起来不用;道又被划分成不同的块单元称为扇区,每个道的周长不同,现代硬盘不同长度的道划分出来的扇区数也是不相同的,而磁头不工作的时候一般位于内道,如果追求响应时间,则数据可存储在硬盘的内道,如果追求大的吞吐量,则数据应存储在硬盘的外道;
嵌入式与Linux那些事
2021/05/20
5.6K0
内存模拟块设备驱动程序设计
/*既然上面分析了,块设备的工作原理。 那如何写一个块设备呢?*/ /*怎么写一个块设备驱动程序? * 1. 分配一个gendisk结构,用alloc_disk函数 * 2. 分配一个request队列,用blk_init_queue函数 * 3. 设置gendisk结构 * 3.1 设置主设备号,次设备号 * 3.2 设置block_device_operations结构 * 3.3 设置queueu结构 * 4. 注册gendisk: 用add_disk函数 */ /***既然知道
DragonKingZhu
2022/05/08
1.3K0
块设备工作原理分析
/*分析 块设备的工作原理*/ void ll_rw_block(int rw, int nr, struct buffer_head *bhs[]) { int i; for (i = 0; i < nr; i++) { struct buffer_head *bh = bhs[i]; if (!trylock_buffer(bh)) continue; //写操作 if (rw == WRITE) { if (test_clear_buffer_dirty(bh))
DragonKingZhu
2022/05/08
5350
技术干货 | 漫游Linux块IO
在计算机的世界里,我们可以将业务进行抽象简化为两种场景——计算密集型和IO密集型。这两种场景下的表现,决定这一个计算机系统的能力。数据库作为一个典型的基础软件,它的所有业务逻辑同样可以抽象为这两种场景的混合。因此,一个数据库系统性能的强悍与否,往往跟操作系统和硬件提供的计算能力、IO能力紧密相关。
沃趣科技
2022/12/31
1.7K0
技术干货 | 漫游Linux块IO
详解 | Linux系统是如何实现存储并读写文件的?
Linux系统文件操作主要是通过块设备驱动来实现的。 块设备主要指的是用来存储数据的设备,类似于SD卡、U盘、Nor Flash、Nand Flash、机械硬盘和固态硬盘等。块设备驱动就是用来访问这些存储设备的,其与字符设备驱动不同的是:
开源519
2020/09/29
1.9K0
详解 | Linux系统是如何实现存储并读写文件的?
聊聊块设备核心数据结构
块设备架构 通用块设备层: 负责从文件系统层传递下来的磁盘O请求,用户态发起的读写操作,经过vfs到实际文件系统ext4,最终需要经过通用块设备层,实际文件系统传递过来的IO操作是通过submit_bio将bio结构传递给通用块设备层。submit_bio将bio请求到磁盘request请求的转换(请求的合并和IO优化),并将request请求挂入到磁盘请求的队列中,然后进行处理。 IO 调度层:负责磁盘IO调度的优化,目前内核支持noop(先来先处理)、cfq(按照每个进程的IO请求的公平原则,基于数据量
用户4700054
2022/08/17
8090
聊聊块设备核心数据结构
聊聊块设备那点事
IO体系结构是什么样的? 系统如何判断设备数据是否就绪方式? 目前系统判断设备上的数据是否就绪采用了轮询和中断两种方式。轮询方式是不断的重复询问设备上的数据是否可用,如果可用,CPU就读取数据;中断方式中系统为每个CPU提供了中断线,可由各个系统设备共享。每个中断通过一个唯一的标识,内核对使用的每个中断提供一个中断服务。中断将暂停正常系统工作,在外设的数据已经就绪,需要由内核或者应用处理,外设会引发一个中断,系统就不需要频繁检查是否有新的数据可用,外设有新数据的情况会自动通知系统。 内核如何管理磁盘设备
用户4700054
2022/08/17
1.2K0
聊聊块设备那点事
Linux文件系统之 — 通用块处理层
由于不同块设备(如磁盘,机械硬盘等)有着不同的设备驱动程序,为了让文件系统有统一的读写块设备接口,Linux实现了一个 通用块层。如下图中的红色部分:
用户7686797
2020/11/20
2.4K0
Linux文件系统之 — 通用块处理层
聊聊内核中IO子系统-上篇
I/O子系统概貌 VFS:内核提供不同实现文件系统的抽象,应用端一般请求到vfs,vfs在调用实际文件系统的posix语义函数,可以理解为vfs作为用户态和实际文件系统的之间的转换桥梁,为用户态提供对于底层磁盘文件系统无感知的文件系统服务层。 Page Cache: 缓存文件系统的数据,这里包括文件系统元数据和文件系统数据,在块缓存之上构建页缓存(常说的Buffer/Cache). Mapping Layer:如果内核需要从块设备上读取数据,就必须知道数据在物理设备上的位置,这个是由映射层Mappin
用户4700054
2022/08/17
5900
聊聊内核中IO子系统-上篇
宋宝华:Linux文件读写(BIO)波澜壮阔的一生
网上关于BIO和块设备读写流程的文章何止千万,但是能够让你彻底读懂读明白的文章实在难找,可以说是越读越糊涂!
Linux阅码场
2019/12/25
4.8K0
深入浅出MMC子系统
本文基于内核版本4.1.15分析,随着内核版本升级,部分数据结构会发生变化,但是整体流程没有发生变化。
哆哆jarvis
2022/08/23
1.5K0
深入浅出MMC子系统
Linux驱动开发13个实用案例
以下为你提供20个Linux驱动开发的实用案例,涵盖字符设备驱动、块设备驱动、网络设备驱动等不同类型,包含应用场景、技巧、代码示例和操作步骤。先赞再看后评论,腰缠万贯财进门。
威哥爱编程
2025/02/21
1290
Linux | 块设备了解之三层结构
块设备是文件系统的底层支撑,完成数据的存储和访问。块设备也能脱离文件系统以螺设备的形式工作。
heidsoft
2023/03/18
1.3K0
Linux | 块设备了解之三层结构
Linux MTD子系统(二)——mtdblock驱动分析
在之前的文章Linux MTD子系统(一)中有提到过mtd块设备,mtd块设备是在MTD设备之上模拟的块设备。 它的作用实际上只有一个——便于我们使用mount(umount)挂载(卸载)MTD设备中的文件系统,例如yaffs2,JFFS2等等。
知否知否应是绿肥红瘦
2025/02/19
840
Linux MTD子系统(二)——mtdblock驱动分析
Linux内核(5.10)-IO全路径-文件系统到磁盘-或远端iscsi/nvmeof协议盘
DAX: 磁盘(disk)的访问模式有三种 BUFFERED、DIRECT、DAX。前面提到的由于page cache存在可以避免耗时的磁盘通信就是BUFFERED访问模式的集中体现;但是如果我要求用户的write请求要实时存储到磁盘里,不能只在内存中更新,那么此时我便需要DIRECT模式;大家可能听说过flash分为两种nand flash和nor flash,nor flash可以像ram一样直接通过地址线和数据线访问,不需要整块整块的刷,对于这种场景我们采用DAX模式。所以file_operations的read_iter和write_iter回调函数首先就需要根据不同的标志判断采用哪种访问模式, kernel在2020年12月的patch中提出了folio的概念,我们可以把folio简单理解为一段连续内存,一个或多个page的集合
晓兵
2023/12/07
1.6K0
Linux内核(5.10)-IO全路径-文件系统到磁盘-或远端iscsi/nvmeof协议盘
相关推荐
Linux驱动开发: 块设备驱动开发
更多 >
领券
社区富文本编辑器全新改版!诚邀体验~
全新交互,全新视觉,新增快捷键、悬浮工具栏、高亮块等功能并同时优化现有功能,全面提升创作效率和体验
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文