块设备驱动 <概念和字符设备区别一>

系列文章目录

块设备概念和字符设备区别

块设备驱动框架

块设备驱动程序及验证


提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

驱动分为三大部分:字符设备驱动,块设备驱动,网络设备驱动;

字符设备: 以字节为单位的即时数据传输的设备。

块设备:以块为单位的数据传输(读写)非随机即时性访问;


一、块设备概念及和字符设备区别?

块设备主要是存储类设备: 例如 SD卡、EMMC、NAND FLASH Nor Flash、 SPI Flash QSpi Flash、机械硬盘 、固态硬盘等。

块设备驱动就是对于这些存储类设备的操作。

块设备传输特点:

1、以块为单位对数据操作(读写),块是linux虚拟文件系统(VFS)传输的单位,而虚拟文件系统提供了对文件系统的读写操作;

2、块设备在文件或者说是结构上可以随机访问,但是对物理设备都是按照块进行的。块设备驱动都会有缓冲区暂存数据,一定程度后一次性将缓冲区写入块设备(例如缓冲区快满了,数据全部请求完成等等);

为什么需要缓冲区?

1)块设备一般都需要擦除再写入并有一定的擦除次数标准;如果经常性的擦除会导致块设备寿命缩短;

2)块设备结构不同I/O算法也不同,如SD卡这类可以任意跳转扇区读取,但机械硬盘就不行,因为存在磁头,读取磁道数据需要先移动磁头;为了提供性能或者寿命有着不同的I/O算法;最常见的就是电梯算法来保护块设备寿命。

二、块设备驱动框架

1.常用结构体

block_device 结构体:include\linux\fs.h

重点关注成员:  

struct gendisk *    bd_disk; //通用磁盘结构

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 */
    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;
    u8            bd_partno;
    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 backing_dev_info *bd_bdi;
    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;
} __randomize_layout;
1、注册块设备
和字符设备驱动一样,我们需要向内核注册新的块设备、申请设备号,块设备注册函数为
register_blkdev,函数原型如下:
int register_blkdev(unsigned int major, const char *name)
函数参数和返回值含义如下:
major: 主设备号。
name: 块设备名字。
返回值: 如果参数 major 在 1~255 之间的话表示自定义主设备号,那么返回 0 表示注册成
功,如果返回负值的话表示注册失败。如果 major 为 0 的话表示由系统自动分配主设备号,那
么返回值就是系统分配的主设备号(1~255),如果返回负值那就表示注册失败。
2、注销块设备
和字符设备驱动一样,如果不使用某个块设备了,那么就需要注销掉,函数为
unregister_blkdev,函数原型如下:
void unregister_blkdev(unsigned int major, const char *name)
函数参数和返回值含义如下:
major: 要注销的块设备主设备号。
name: 要注销的块设备名字。
返回值: 无

gendisk结构:include/linux/genhd.h

struct gendisk {
    /* major, first_minor and minors are input parameters only,
     * don't use directly.  Use disk_devt() and disk_max_parts().
     */
    int major;                       /*主设备号 */
    int first_minor;                 /* 第一个次设备号开始值 */
    int minors;                     /* 分区数量 */

    char disk_name[DISK_NAME_LEN];    /* 设备名  */
    char *(*devnode)(struct gendisk *gd, umode_t *mode);

    unsigned int events;        /* supported events */
    unsigned int async_events;    /* async events, subset of all */
    struct disk_part_tbl __rcu *part_tbl;  /* 磁盘对应分区表 */
    struct hd_struct part0;
    const struct block_device_operations *fops;   /* 块设备操作集 类似 file_operations*/
    struct request_queue *queue;
    void *private_data;
    int flags;
    struct kobject *slave_dir;
    struct timer_rand_state *random;
    atomic_t sync_io;        /* RAID */
    struct disk_events *ev;
#ifdef  CONFIG_BLK_DEV_INTEGRITY
    struct kobject integrity_kobj;
#endif    /* CONFIG_BLK_DEV_INTEGRITY */
    int node_id;
    struct badblocks *bb;
};

内核提供操作gendisk的API集:

1、 申请 gendisk
使用 gendisk 之前要先申请, allo_disk 函数用于申请一个 gendisk,函数原型如下:
struct gendisk *alloc_disk(int minors)
函数参数和返回值含义如下:
minors: 次设备号数量, 也就是 gendisk 对应的分区数量。
返回值: 成功:返回申请到的 gendisk,失败: NULL。
2、删除 gendisk
如果要删除 gendisk 的话可以使用函数 del_gendisk,函数原型如下:
void del_gendisk(struct gendisk *gd);
gp: 要删除的 gendisk。返回值: 无

3、将gendisk添加到内核

使用alloc_disk申请到gendisk以后系统还不 能使用,必须使用add_gendisk添加到内核;
void add_gendisk(struct gendisk * gd);
gd:要添加到内核的gendisk返回值:无

4、设置 gendisk 容量
每一个磁盘都有容量,所以在初始化 gendisk 的时候也需要设置其容量,使用函数
set_capacity,函数原型如下:
void set_capacity(struct gendisk *disk, sector_t size)
函数参数和返回值含义如下:
disk: 要设置容量的 gendisk。
size: 磁盘容量大小,注意这里是扇区数量。块设备中最小的可寻址单元是扇区,一个扇区
一般是 512 字节,有些设备的物理扇区可能不是 512 字节。不管物理扇区是多少,
驱动之间的扇区都是 512 字节。所以 set_capacity 函数设置的大小就是块设备实际容量除以
512 字节得到的扇区数量。比如一个 2MB 的磁盘,其扇区数量就是(2*1024*1024)/512=4096

5、调整 gendisk 引用计数
内核会通过 get_disk 和 put_disk 这两个函数来调整 gendisk 的引用计数,根据名字就可以
知道, get_disk 是增加 gendisk 的引用计数, put_disk 是减少 gendisk 的引用计数,这两个函数原
型如下所示:
truct kobject *get_disk(struct gendisk *disk)
void put_disk(struct gendisk *disk)

block_device_operations 结构体 include\linux\blkdev.h

struct block_device_operations {
	int (*open) (struct block_device *, fmode_t);
	void (*release) (struct gendisk *, fmode_t);
	int (*rw_page)(struct block_device *, sector_t, struct page *, bool);
	int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
	int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
	unsigned int (*check_events) (struct gendisk *disk,
				      unsigned int clearing);
	/* ->media_changed() is DEPRECATED, use ->check_events() instead */
	int (*media_changed) (struct gendisk *);
	void (*unlock_native_capacity) (struct gendisk *);
	int (*revalidate_disk) (struct gendisk *);
	int (*getgeo)(struct block_device *, struct hd_geometry *);
	/* this callback is with swap_lock and sometimes page table lock held */
	void (*swap_slot_free_notify) (struct block_device *, unsigned long);
	struct module *owner;
	const struct pr_ops *pr_ops;
};

open:                   打开块设备文件
release:            关闭块设备文件
rw_page:             读写指定页
ioctl:                    块设备的I/O控制32位系统上
compat:               块设备io控制64位系统上32位引用程序调用ioctl时底层调用这个
getgeo:                获取磁盘信息,磁头、柱面、扇区
media_changed: 修改承载媒介
owner:                 所属模块

块设备读取数据流程 

1、访问流程

之前就说过,块设备的读写操作都是通过VFS来进行的,block_device_operations没有直接提供读写函数

如下: vfs->通过通用块层->struct bio 来访问一个扇区

struct bio 描述了整个请求的块io的信息;

这个哥们儿写的还不错BIO画的图比较清晰

(1条消息) 关于BIO结构分析_zhanghaiyang9999的专栏-CSDN博客_bio结构

bio最后一个成员是数组,但是数组长度是0;但是

struct bio {
	struct bio		*bi_next;	/*指向下一个bio */
	struct gendisk		*bi_disk; //将要操作gendisk 
	u8			bi_partno;        //分区号
	blk_status_t		bi_status;
	unsigned int		bi_opf;		
	unsigned short		bi_flags;	/* status, etc and bvec pool number */
	unsigned short		bi_ioprio;
	unsigned short		bi_write_hint;
	struct bvec_iter	bi_iter;
	unsigned int		bi_phys_segments;
	unsigned int		bi_seg_front_size;
	unsigned int		bi_seg_back_size;
	atomic_t		__bi_remaining;
	bio_end_io_t		*bi_end_io;
	void			*bi_private;
#ifdef CONFIG_BLK_CGROUP
	struct io_context	*bi_ioc;
	struct cgroup_subsys_state *bi_css;
#ifdef CONFIG_BLK_DEV_THROTTLING_LOW
	void			*bi_cg_private;
	struct blk_issue_stat	bi_issue_stat;
#endif
#endif
	union {
#if defined(CONFIG_BLK_DEV_INTEGRITY)
		struct bio_integrity_payload *bi_integrity; /* data integrity */
#endif
	};
    unsigned short		bi_vcnt;	/* how many bio_vec's */
    unsigned short		bi_max_vecs;	/* max bvl_vecs we can hold */
    atomic_t		__bi_cnt;	/* pin count */
	struct bio_vec		*bi_io_vec;	/* the actual vec list */
	struct bio_set		*bi_pool;

	/*
	 * We can inline a number of vecs at the end of the bio, to avoid
	 * double allocations for a small number of bio_vecs. This member
	 * MUST obviously be kept at the very end of the bio.
	 */
	struct bio_vec		bi_inline_vecs[0];
};

struct bvec_iter {
	sector_t	   bi_sector;	/* device address in 512 byte sectors */
	unsigned int   bi_size;	/* residual I/O count */
	unsigned int   bi_idx;		/* current index into bvl_vec */
	unsigned int   bi_done;	/* number of bytes completed */
	unsigned int   bi_bvec_done;	/* number of bytes completed in current bvec */
};

成员结构关系图: 

2、请求队列 requeset_queue

block_device_operations 中并没有read|write函数,如何进行读取数据?

内核(也就是虚拟文件系统)会把对块设备的请求放到请求队列(requeset_queue)中,requeset_queue包含大量的requeset请求结构,而requet中又包含了bio,bio保存了读写相关数据,如从那个地址开始读取、读取的长度,结束地址等等,

requeset_queue结构超大

转载的这个图:图片来源

这里写图片描述

实验:

#include <linux/module.h>
#include <linux/blkdev.h>
#include <linux/bitops.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <asm/setup.h>

#include <linux/gfp.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/hdreg.h>
#include <linux/blkdev.h>
#include <linux/blkpg.h>
#include <linux/kernel.h>
#include <linux/mutex.h>
#include <linux/uaccess.h>
#include <linux/workqueue.h>

/**************************************** 
*文件名: ramdisk.c
*功能:	内存模拟硬盘,实现块设备驱动,使用请求队列
*参考: driver/block/z2ram.c
****************************************/
struct ramdisk_dev{
	int major;						/* 保存主设备号 */
	unsigned char *ramdisk_buff;	/* 申请内存(模拟硬盘)空间 */
	struct request_queue  *queue;	/* 请求队列 */
	spinlock_t lock; 				/* 自旋锁*/
	struct gendisk *gendisk; 		/* gendis */
};

static struct ramdisk_dev *g_ramdisk_dev;

#define RAMDISK_PARTION_NUM 	4
#define RAMDISK_SIZE 			(2*1024*1024)
#define RAMDISK_NAME 			"cjmyram"

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;
}

static void ramdisk_transfer(struct request *req)
{
	unsigned long start = blk_rq_pos(req) << 9; /* blk_rq_pos 获取到的是扇区地址,左移9 位转换为字节地址  512 = 1<<9*/
	unsigned long len = blk_rq_cur_bytes(req); /* 大小  当前段中剩余的字节数*/
	/* bio 中的数据缓冲区
	* 读:从磁盘读取到的数据存放到 buffer 中
	* 写: buffer 保存这要写入磁盘的数据
	*/
	void *buffer = bio_data(req->bio);
	printk("ramdisk_transfer start:0x%lx \n", start);
	if(rq_data_dir(req) == READ) /* 读数据 */
		memcpy(buffer, g_ramdisk_dev->ramdisk_buff + start, len);
	else if(rq_data_dir(req) == WRITE) /* 写数据 */
		memcpy(g_ramdisk_dev->ramdisk_buff + start, buffer, len);
}

static void do_ramdisk_request(struct request_queue *q)
{
	int err = 0;
	struct request *req;
	/* 循环处理请求队列中的每个请求 */
	req = blk_fetch_request(q);
	printk("do_ramdisk_request \n");
	while(req != NULL) {
		
		/* 针对请求做具体的传输处理 */
		ramdisk_transfer(req);
		
		/* 判断是否为最后一个请求,如果不是的话就获取下一个请求
		* 循环处理完请求队列中的所有请求。
		*/
		if (!__blk_end_request_cur(req, err))
		req = blk_fetch_request(q);
	}
}

void ramdisk_release(struct gendisk *disk, fmode_t mode)
{
	printk("ramdisk release");
}


int ramdisk_open(struct block_device *dev, fmode_t mode)
{
	printk("ramdisk open\r\n");
	return 0;
}

static const struct block_device_operations ramdisk_fops =
{
	.owner		= THIS_MODULE,
	.open		= ramdisk_open,
	.release	= ramdisk_release,
	.getgeo 	= ramdisk_getgeo,
};
static int mysbull_major = 0;

static int myramdisk_init(void)
{
	unsigned int ret = 0;

	/* 1、分配空间 并初始化*/
	g_ramdisk_dev = vmalloc(sizeof(struct ramdisk_dev));  	/* 整体分配空间 */
	memset(g_ramdisk_dev, 0, sizeof(struct ramdisk_dev));
	g_ramdisk_dev->ramdisk_buff = vmalloc(RAMDISK_SIZE);	/* 给模拟硬盘的内存分配空间*/
	memset(g_ramdisk_dev->ramdisk_buff, 0, RAMDISK_SIZE);

	spin_lock_init(&g_ramdisk_dev->lock);								/* 初始化自旋锁 */
	g_ramdisk_dev->major = 0;
	
	printk(" requeset mem ok \n ");
	
	/* 2、注册块设备 */
	g_ramdisk_dev->major = register_blkdev(mysbull_major, RAMDISK_NAME);
	if (!g_ramdisk_dev->major)
	{
		goto register_blkdev_fail;
	}
	
	printk(" register_blkdev ok \n ");
	/* 3、构建gendisk   */
	g_ramdisk_dev->gendisk = alloc_disk(RAMDISK_PARTION_NUM);		  	/* 创建gendisk */
	if(!g_ramdisk_dev->gendisk) {
		ret = -EINVAL;
		goto gendisk_alloc_fail;
	}
	printk(" alloc gendisk ok \n ");

	/* 4、 初始化请求队列          实现请求队列处理函数 */
    g_ramdisk_dev->queue = blk_init_queue(do_ramdisk_request, &g_ramdisk_dev->lock);
    if (!g_ramdisk_dev->queue )
	{
		printk(" blk_init_queue error \n");
		goto out_queue;
	}
	printk(" blk_init_queue gendisk ok \n");

	/* 5、设置gendisk结构 */
	g_ramdisk_dev->gendisk->major = g_ramdisk_dev->major;
	g_ramdisk_dev->gendisk->first_minor = 0;
	g_ramdisk_dev->gendisk->fops = &ramdisk_fops;
	sprintf(g_ramdisk_dev->gendisk->disk_name, RAMDISK_NAME);
	g_ramdisk_dev->gendisk->queue = g_ramdisk_dev->queue;
	set_capacity(g_ramdisk_dev->gendisk, RAMDISK_SIZE/512); /* 设备容量(单位为扇区)*/
	printk("set capacity \n");
	add_disk(g_ramdisk_dev->gendisk);
	printk("add disk \n");
	
	return ret;

out_queue:
	put_disk(g_ramdisk_dev->gendisk);

gendisk_alloc_fail:
	unregister_blkdev(g_ramdisk_dev->major, RAMDISK_NAME);
	
register_blkdev_fail:
	printk("major:%d \n", g_ramdisk_dev->major);
	kfree(g_ramdisk_dev->ramdisk_buff);
	kfree(g_ramdisk_dev);
	return ret;
}


static void myramdisk_exit(void)
{
	del_gendisk(g_ramdisk_dev->gendisk);
	put_disk(g_ramdisk_dev->gendisk);
	/* 清除请求队列 */
	blk_cleanup_queue(g_ramdisk_dev->queue);
	/* 注销块设备 */
	unregister_blkdev(g_ramdisk_dev->major, RAMDISK_NAME);
	kfree(g_ramdisk_dev->ramdisk_buff);
	kfree(g_ramdisk_dev);
}


module_init(myramdisk_init);
module_exit(myramdisk_exit);
MODULE_LICENSE("GPL");


总结

 块设备驱动步骤:

1、申请私有数据空间    vmalloc
2、注册块设备        register_blkdev
3、构建gendisk        alloc_disk 并初始化gendisk  设置容量set_capacity
4、初始化请求队列实现请求队列处理函数 blk_init_queue();
    实现请求队列处理函数
            1、获取请求(request) request = blk_fetch_request(request_queue)
            2、处理请求,获取扇区地址,剩余字节数, 读取/写入bio数据bio_data(req->bio);
            3、获取下个请求  request = blk_fetch_request
5、将gendisk注册到块设备链表    add_disk 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值