Linux块设备驱动01

1. 简介

Linux块设备是针对存储设备,比如SD卡、EMMC、Nand Flash等。
与字符设备相对,主要有以下特点:

  • 块设备只能以块为单位进行读写访问,块是Linux虚拟文件系统(VFS)的基本 数据传输单位,一般为512字节或者4K,而字符设备是以字节为单位进行数据传输的,不需要缓冲。
  • 块设备在结构上是可以随机访问的。Linux块设备以固定大小的块(通常为512字节或4KB)为单位进行数据读写。这种块设备的结构允许系统直接访问任意块的数据,而不必按照顺序逐个读取。因此,Linux块设备可以实现随机访问,提高数据读写的效率。
  • 块设备支持缓冲和缓存。操作系统通常会使用缓冲区来存储从块设备读取或写入的数据。这可以提高系统的性能,因为它可以减少对实际物理设备的访问次数。
  • 块设备可以被多个进程同时访问。多个进程可以并发地读取或写入块设备,而无需互斥访问。这使得块设备非常适合用于共享和分布式系统。
  • 块设备可以通过文件系统进行组织和管理。文件系统是操作系统中用于管理和访问数据的一种抽象层。它可以在块设备上创建文件和目录,并提供对这些文件和目录的高级操作。

2. 块设备驱动框架

Linux块设备驱动框架是Linux内核用于管理和控制块设备的框架,主要包括:

  • 块设备结构体,用于表示和管理块设备的信息
  • 请求队列:块设备驱动框架通过请求队列来管理和调度I/O请求,确保数据的顺序性和一致性。
  • I/O接口:用于实现块设备的读写操作
  • 注册和注销接口

2.1 block_device 块设备结构体

linux 内 核 使 用 block_device 表 示 块 设 备 , block_device 为 一 个 结 构 体 , 定 义 在include/linux/fs.h 文件中,结构体内容如下:

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成员变量,类型为gendisk 结构体。

2.2 注销、注册块设备函数

注册块设备函数
int register_blkdev(unsigned int major, const char *name)

  • major: 主设备号
  • name:块设备名字
  • 返回值:如果major在1-255之间,返回0表示注册成功,返回负值表示注册失败;如果major为0,返回系统自动分配的设备号(1~255),如果返回负值,表示注册失败

注销块设备函数
void unregister_blkdev(unsigned int major, const char *name)

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

2.3 gendisk 结构体

Linux使用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;			/* major number of driver */
	int first_minor;
	int minors;                     /* maximum number of minors, =1 for
                                         * disks that can't be partitioned. */

	char disk_name[DISK_NAME_LEN];	/* name of major driver */
	char *(*devnode)(struct gendisk *gd, umode_t *mode);

	unsigned int events;		/* supported events */
	unsigned int async_events;	/* async events, subset of all */

	/* Array of pointers to partitions indexed by partno.
	 * Protected with matching bdev lock but stat and other
	 * non-critical accesses use RCU.  Always access through
	 * helpers.
	 */
	struct disk_part_tbl __rcu *part_tbl;
	struct hd_struct part0;

	const struct block_device_operations *fops;
	struct request_queue *queue;
	void *private_data;

	int flags;
	struct device *driverfs_dev;  // FIXME: remove
	struct kobject *slave_dir;

	struct timer_rand_state *random;
	atomic_t sync_io;		/* RAID */
	struct disk_events *ev;
#ifdef  CONFIG_BLK_DEV_INTEGRITY
	struct blk_integrity *integrity;
#endif
	int node_id;
};
  • major为磁盘设备的主设备号
  • first_minor为磁盘的第一个次设备号
  • minor为磁盘的次设备号数量,也就是磁盘的分区数量,这些分区的主设备号一样,次设备号不同
  • part_tbl为磁盘对应的分区表。disk_part_tbl类型,核心是一个hd_struct的结构体数组,此数组每一项都对应一个分区信息。
  • fops为块设备操作集
  • queue为磁盘对应的请求队列,所有针对该磁盘设备的请求都放到该请求队列中,驱动程序需要处理此队列的所有请求。

2.4 gendisk操作函数

2.4.1 申请gendisk

在使用gendisk之前,要先进行申请,函数原型:
struct gendisk *alloc_disk(int minors)

  • minors:次设备号的数量,也就是gendisk对应的分区数量
  • 返回值:成功返回申请的gendisk,失败返回NULL

2.4.2 删除gendisk

使用del_gendisk删除gendisk,函数原型:
void del_gendisk(struct gendisk *gp)

  • gp:要删除的gendisk

2.4.3 将gendisk添加到内核

使用alloc_gendisk申请到的gendisk系统还无法使用,需要使用add_disk将申请到的gendisk添加到内核中,函数原型:
void add_disk(struct gendisk *disk)

2.4.4 设置gendisk的容量

每一个磁盘都有容量,所以在初始化gendisk的时候也需要设备容量,函数原型如下:
void set_capacity(struct gendisk *disk, sector_t size)

  • disk:要设置容量的gendisk
  • size:磁盘容量的大小,这里指的是扇区的数量。块设备中的最小可寻址单元是扇区,一般一个扇区是512字节,但是有些也不是。不管物理扇区是多少,内核和块设备驱动之间的扇区都是512字节(4K)。例如,2MB的磁盘,扇区数量就是(2 * 1024 * 1024)/ 512 = 4096

2.4.5 调整gendisk的引用计数

内核通过get_disk和put_disk这两个函数来调整引用计数

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

2.5 block_device_operations结构体

与字符设备的file_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 *, int rw);	// 读写指定的页
	int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
	int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
	long (*direct_access)(struct block_device *, sector_t,
					void **, unsigned long *pfn, long size);
	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;
};

2.6 块设备I/O请求

在block_device_operations结构体中,并没有read和write函数。块设备使用request_queue、request和bio实现读写操作

2.6.1 请求队列request queue

内核对于块设备的读写都发送到请求队列request_queue中,request_queue中是大量的request,request又包含bio,在bio中保存了读写相关的数据,比如从块设备的哪个地址开始读、读取的长度是多少,如果是写入、还包括要写入的数据。
在gendisk结构体中,有一个request_queue结构体指针类型的成员变量queue,在编写块设备驱动的时候,每个磁盘(gendisk)都要分配一个request_queue

请求队列使用流程:

  1. 初始化请求队列
    首先需要申请并初始化request_queue,使用blk_init_queue函数来完成request_queue的申请与初始化,函数原型:
    request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
    - rfn:是请求处理函数指针,原型void (request_fn_proc) (struct request_queue *q),需要由驱动人员实现
    - lock,自旋锁指针
  2. 删除请求队列
    当卸载块设备驱动的时候,需要删除前面申请的reques_queue,使用blk_cleanup_queue
    函数原型void blk_cleanup_queue(struct request_queue *q)
  3. 分配请求队列并绑定制造请求函数
    blk_init_queue函数完成了请求队列的申请和处理函数䣌绑定,这个一般用于像机械硬盘这样的存储设备,需要I/O调度器优化数据读写过程。但是对于EMMC\SD卡这样的非机械设备,可以进行完全随机访问,所以不需要复杂的I/O调度器。
    对于非机械设备,可以先申请request_queue,然后将申请到的reques_queue与制造请求的函数绑定在一起。
    申请request_queue函数blk_queue,函数原型:
    struct request_queue *blk_alloc_queue(gfp_t gfp_mask)
    • gfp_mask:内存分配掩码,一般为GFK_KERNEL。
  • 22
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值