XuSenfeng

个人站

复读了,更新随缘,有的文件不全或者图片缺失具体看我的笔记库(https://github.com/XuSenfeng/note)


块设备驱动

目录

块设备

块设备是针对存储设备的,比如SD卡、EMMC、NAND Flash、NorFlash、SPI Flash、机械硬盘、固态硬盘等

块设备只能以块为单位进行读写访问,块是linux虚拟文件系统(VFS)基本的数据传输单位。字符设备是以字节为单位进行数据传输的,不需要缓冲

块设备在结构上是可以进行随机访问的,对于这些设备的读写都是按块进行的,块设备使用缓冲区来暂时存放数据,等到条件成熟以后在一次性将缓冲区中的数据写入块设备中。

驱动

使用结构体block_device表示设备

struct block_device {
	dev_t			bd_dev;  /* not a kdev_t - it's a search key */
	int			bd_openers;
	struct inode *		bd_inode;	/* will die */
	struct super_block *	bd_super;
	struct mutex		bd_mutex;	/* open/close mutex */
	struct list_head	bd_inodes;
	void *			bd_claiming;
	void *			bd_holder;
	int				bd_holders;
	bool			bd_write_holder;
#ifdef CONFIG_SYSFS
	struct list_head	bd_holder_disks;
#endif
	struct block_device *	bd_contains;
	unsigned		bd_block_size;
	struct hd_struct *	bd_part;
	/* number of times partitions within this device have been opened. */
	unsigned		bd_part_count;
	int			bd_invalidated;
	struct gendisk *	bd_disk;
	struct request_queue *  bd_queue;
	struct list_head	bd_list;
	/*
	 * Private data.  You must have bd_claim'ed the block_device
	 * to use this.  NOTE:  bd_claim allows an owner to claim
	 * the same device multiple times, the owner must take special
	 * care to not mess up bd_private for that case.
	 */
	unsigned long		bd_private;

	/* The counter of freeze processes */
	int			bd_fsfreeze_count;
	/* Mutex for freeze */
	struct mutex		bd_fsfreeze_mutex;
};

重点是bd_disk结构体, 表示一个磁盘对象, 类型为struct gendisk

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

注册块设备, major:主设备号。name:块设备名字。如果major为0的话表示由系统自动分配主设备号,那么返回值就是系统分配的主设备号(1~255),如果返回负值那就表示注册失败

void unregister_blkdev(unsigned int major, const char *name)

注销块设备, major:要注销的块设备主设备号。name:要注销的块设备名字

  • gendisk结构体

描述某一个磁盘设备

struct block_device_operations包含操作函数

open函数用于打开指定的块设备。

release函数用于关闭(释放)指定的块设备。

rw_page函数用于读写指定的页。

ioctl函数用于块设备的I/O控制。

compat_ioctl函数和ioctl函数一样,都是用于块设备的I/O控制。区别在于在64位系统上,32位应用程序的ioctl会调用compat_iotl函数。在32位系统上运行的32位应用程序调用的就是ioctl函数。

getgeo函数用于获取磁盘信息,包括磁头、柱面和扇区等信息。

owner表示此结构体属于哪个模块,一般直接设置为THIS_MODULE。

struct gendisk *alloc_disk(int minors)

申请一个结构体, minors:次设备号数量,也就是gendisk对应的分区数量。返回值:成功:返回申请到的gendisk,失败:NULL。

void del_gendisk(struct gendisk *gp)

删除结构体gp:要删除的gendisk。返回值:无。

void add_disk(struct gendisk *disk)

添加到内核, disk:要添加到内核的gendisk。返回值:无。

void set_capacity(struct gendisk *disk, sector_t size)

设置磁盘的容量, disk:要设置容量的gendisk。size:磁盘容量大小,注意这里是扇区数量。块设备中最小的可寻址单元是扇区,一个扇区一般是512字节

truct kobject *get_disk(struct gendisk *disk)
   void put_disk(struct gendisk *disk)

内核会通过get_disk和put_disk这两个函数来调整gendisk的引用计数,根据名字就可以知道,get_disk是增加gendisk的引用计数,put_disk是减少gendisk的引用计数

  • struct request_queue: 请求队列

请求队列, 内核把读写请求发送到request_queue中, request_queue中是大量的request(请求结构体),而request又包含了bio,bio保存了读写相关数据

request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)

申请并初始化一个request_queue, rfn:请求处理函数指针,每个request_queue都要有一个请求处理函数void (request_fn_proc) (struct request_queue *q), lock:自旋锁指针,需要驱动编写人员定义一个自旋锁,然后传递进来。,请求队列会使用这个自旋锁, 主要用于机械硬盘, 会使用电梯算法

在绑定的函数中申请并进行处理

void blk_cleanup_queue(struct request_queue *q)

卸载块设备驱动的时候我们还需要删除掉前面申请到的request_queue, q:需要删除的请求队列。返回值:无。

  • 请求request

request里面有一个名为“bio”的成员变量,类型为bio结构体指针。前面说了,真正的数据就保存在bio里面

request *blk_peek_request(struct request_queue *q)

依次获取每个request

void blk_start_request(struct request *req)

处理这个请求

structrequest *blk_fetch_request(structrequest_queue *q)

也可以使用blk_fetch_request函数来一次性完成请求的获取和开启

struct request_queue *blk_alloc_queue(gfp_t gfp_mask)

用于只申请一个队列头

void blk_queue_make_request(struct request_queue *q, make_request_fn *mfn)

q:需要绑定的请求队列,也就是blk_alloc_queue申请到的请求队列。mfn:需要绑定的“制造”请求函数,函数原型如下void (make_request_fn) (struct request_queue *q, struct bio *bio), 一般blk_alloc_queue和blk_queue_make_request是搭配在一起使用的,用于那么非机械的存储设备、无需I/O调度器,比如EMMC、SD卡等。

  • bio结构体

每个request里面里面会有多个bio,bio保存着最终要读写的数据、地址等信息

上层会将bio提交给I/O调度器,I/O调度器会将这些bio构造成request结构,而一个物理存储设备对应一个request_queue,request_queue里面顺序存放着一系列的request。新产生的bio可能被合并到request_queue里现有的request中,也可能产生新的request

有可能会使用电梯调度算法, 主要是对硬盘使用, SD和emmc可以直接随机读取

1	struct bio {
2	struct bio     *bi_next;/* 请求队列的下一个bio*/
3	struct block_device *bi_bdev;/* 指向块设备*/
4	unsigned long bi_flags;/* bio状态等信息*/
5	unsigned long bi_rw;/* I/O操作,读或写*/
6	struct bvec_iter    bi_iter;/* I/O操作,读或写*/
7	....
30	structbio_vec      *bi_io_vec;/* bio_vec列表*/
31  ...
33 	};

包含的总要结构体:

bvec_iter结构体描述了要操作的设备扇区等信息

1	struct bvec_iter {
2		sector_t      bi_sector;/*I/O请求的设备起始扇区(512字节)*/
3		unsignedintbi_size;/* 剩余的I/O数量*/
4		unsignedintbi_idx;/* blv_vec中当前索引*/
5		unsignedintbi_bvec_done;/*当前bvec中已经处理完成的字节数*/
6	};

bio_vec结构体

1	struct bio_vec {
2		structpage *bv_page;/* 页*/
3		unsignedintbv_len;/* 长度*/
4		unsignedintbv_offset;/* 偏移*/
5	};

QQ图片20220907173747

#define __rq_for_each_bio(_bio, rq) \
	if ((rq->bio))          \
			for (_bio = (rq)->bio; _bio; _bio = _bio->bi_next)

遍历请求中所有bio并进行处理。这是一个宏

#define bio_for_each_segment(bvl, bio, iter)         \
	__bio_for_each_segment(bvl, bio, iter, (bio)->bi_iter)

bio包 含了 最 终 要操 作的 数 据 ,因此还需要遍历bio中的所有段此函数也是一个宏

unsigned long start = blk_rq_pos(req) << 9;

获取操作的设备块的扇区首地址, 左移九位就是实际的地址

unsigned long len = blk_rq_cur_bytes(req);

获取处理的数据的长度

void *buffer = bio_data(req->bio);

获取bio的信息, 如果是读数据就会把读取到的数据保存在这个缓冲区中

如果是写的话这个缓冲区保存要写入的数据

rq_data_dir(req)

获取读写的方向

  • 不使用电梯函数
struct bio_vec bvec;
struct bvec_iter iter;
offset = bio->bi_iter.bi_sector;    //要操作的扇区偏移
bio_for_each_segment(bvec, bio, iter);  //循环处理每一个段,两个数据结构体

这是用来处理每一个bio的函数

机械硬盘结构

盘片, 一个机械硬盘有多个盘片, 每一个盘片都有对应的读写磁头, 一般在5之内, 盘上面一圈一圈的叫磁道, 从圆心划线可以把他分割为多个弧段, 每个磁道上的一个弧段称为一个扇区, 扇区是最小单位, 每一个有512字节, 每一个盘面的编号相同的磁道组成的圆柱叫做柱面

容量的计算 = 磁头数 x 磁道数 x 每道扇区数 x 每一个扇区的字节数

使用

初始化

//注册块设备
    ramdisk.major = register_blkdev(0, RAMDISK_NAME);
    if(ramdisk.major<0)
    {
        ret  = -EINVAL;
        goto fail_ramdisk_register_file;
    }
    printk("major = %d\r\n", ramdisk.major);

    //申请gendisk
    ramdisk.gendisk = alloc_disk(RAMDISK_MINOR);
    if(!ramdisk.gendisk)
    {
        ret  = -EINVAL;
        goto gendisk_fail;
    }
    //初始化自旋锁
    spin_lock_init(&ramdisk.lock);

申请处理

  • 使用电梯处理函数
//申请并初始化队列
ramdisk.queue =  blk_init_queue(ramdisk_request_fn, &ramdisk.lock);
if(!ramdisk.queue)
{
    ret  = -EINVAL;
    goto blk_queue_fail;
}



//具体的数据处理过程
static void ramdisk_transfer(struct request *req)
{
    unsigned long start = blk_rq_pos(req) << 9; //获取要操作的扇区的首地址
    unsigned long len = blk_rq_cur_bytes(req);
    void *buffer = bio_data(req->bio);

    if(rq_data_dir(req)==READ)
    {
        memcpy(buffer, ramdisk.ramdiskbuf + start, len);
    }else
    {
        memcpy(ramdisk.ramdiskbuf + start, buffer, len);
    }

}
  • 不使用
ramdisk.queue =  blk_alloc_queue(GFP_KERNEL);

if(!ramdisk.queue)
{
    ret  = -EINVAL;
    goto blk_queue_fail;
}
//绑定制造请求函数

blk_queue_make_request(ramdisk.queue, ramdisk_make_request);



//制造请求函数
static void ramdisk_make_request(struct request_queue *queue, struct bio *bio)
{
    int offset;
    struct bio_vec bvec;
    struct bvec_iter iter;
    offset = bio->bi_iter.bi_sector<<9;    //要操作的扇区偏移
    bio_for_each_segment(bvec, bio, iter){
        char *ptr = page_address(bvec.bv_page) + bvec.bv_offset;    //获取缓冲区
        int len = bvec.bv_len;
        //循环处理每一个段
        if(bio_data_dir(bio)==READ)
        {
            memcpy(ptr, ramdisk.ramdiskbuf + offset, len);
        }else
        {
            memcpy(ramdisk.ramdiskbuf + offset, ptr, len);
        }
        offset += len;
    }

    set_bit(BIO_UPTODATE, &bio->bi_flags);
    bio_endio(bio, 0);   //告知处理结束 
}

设置参数

    ramdisk.gendisk->major = ramdisk.major;
    ramdisk.gendisk->first_minor = 0;
    ramdisk.gendisk->fops = &ramdisk_fops;
    ramdisk.gendisk->private_data = &ramdisk;
    ramdisk.gendisk->queue = ramdisk.queue;
    sprintf(ramdisk.gendisk->disk_name, RAMDISK_NAME);
    set_capacity(ramdisk.gendisk, RAMDISK_SIZE/512); //设置容量
    add_disk(ramdisk.gendisk);

    return 0;

blk_queue_fail:
    put_disk(ramdisk.gendisk);
gendisk_fail:
    unregister_blkdev(ramdisk.major, RAMDISK_NAME);
fail_ramdisk_register_file:
    kfree(ramdisk.ramdiskbuf);
ramalloc_fail:
    return ret;

其他

static int ramdisk_getgeo(struct block_device *dev, struct hd_geometry *geo)
{
    //用来获取硬盘的信息
    geo->heads = 2; //磁头
    geo->cylinders = 32; //柱面
    geo->sectors = RAMDISK_SIZE/(2*32*512); //一个磁道的扇区数量

    return 0;
}