目录
6.5 块设备操作
6.5.1 块设备的表示
6.5.2 数据结构
6.5.3 向系统添加磁盘和分区
6.5.4 打开块设备文件
本专栏文章将有70篇左右,欢迎+关注,查看后续文章。
6.5 块设备操作
特点:
随机访问任意位置。
固定块大小的传输。
块设备在内存进行缓存。
扇区(sector):
最小寻址单位。
固定的硬件单位,常数,软件不可修改。
常见大小:512B。
块(block):
长度:扇区的整数倍。软件可修改。
用于在内核和设备之间传输,受内存页限制
常见大小:512B,1024B,2048B,4096B。最大不超过页大小。
一个文件系统内有大块,也有小块。
优点:处理不同大小文件时分别优化性能。
块设备层工作有:
1. 寻址块设备。
2. 预读算法,预读块设备到内存。
6.5.1 块设备的表示
块设备的请求队列管理,包括:
1. 重排读写块请求。
2. 在内存中缓存读写的内容。
struct bdev_inode { //关联块设备与inode
struct block_device bdev; 块设备
struct inode vfs_inode; 块设备的inode
};
struct block_device *bdget(dev_t dev)
根据设备号找到对应块设备。
dev_t -> inode -> struct bdev_inode -> struct block_device
每个块设备有一个请求队列。包含了:
读写请求
IO调度器 :用于重排请求。
特征数据:扇区,块长度。
上图中通用硬盘,用struct gendisk表示。
void add_disk(struct gendisk *disk);
将通用硬盘添加到内核。
对块设备的读写请求不会立即执行,而是协同汇总一起发给设备。
所以块设备文件的file_operations没有实现读写函数。
6.5.2 数据结构
1. 块设备
一个flash或硬盘中有多个分区。
如/dev/mtblockN, 或/dev/sdaN
struct gendisk:表示一个flash或硬盘。
struct block_device:每个分区有一个该结构的实例。
struct block_device {
dev_t bd_dev; //设备号
int bd_openers; //打开该块设备的次数
struct inode *bd_inode; //bdev伪文件系统中,该块设备的inode
struct super_block *bd_super; //设备挂载的文件系统
struct mutex bd_mutex;
struct list_head bd_inodes;
struct block_device *bd_contains;
unsigned bd_block_size;
struct hd_struct *bd_part; //该块设备上的某个分区
unsigned bd_part_count; //引用分区的次数
int bd_invalidated;
struct gendisk *bd_disk; //块设备在通用磁盘层的表示。
struct request_queue *bd_queue;
struct list_head bd_list; //连接系统所有可用的block_device
unsigned long bd_private; //私有数据
};
bd_invalidated:
若为1,则该分区在内核中信息无效,因为磁盘分区已改变。
blkdev_open:
打开块设备并独占使用。
2. 通用硬盘和分区
struct gendisk { //驱动层,表示一个已分区的硬盘。
int major; //主设备号。
int first_minor; //第一从设备号。
int minors; //从设备号总数。
//每个分区有各自的从设备号。
char disk_name[DISK_NAME_LEN]; // 驱动程序的名字
char *(*devnode)(struct gendisk *gd, umode_t *mode);
//用于生成设备节点。创建设备文件时调用。
unsigned short events; //磁盘事件
unsigned int async_events; //异步的磁盘事件
struct disk_part_tbl *part_tbl; //分区表
struct hd_struct part0;
struct block_device_operations *fops;
struct request_queue *queue; //管理IO请求
void *private_data; //磁盘的私有数据
int flags; //磁盘的特性或状态
struct kobject *slave_dir;
};
成员part0:
不对应于磁盘上的实际分区,而是代表整个磁盘。允许以分区的方式对整个磁盘操作。
起始扇区:
part0的起始扇区为是 0,表示磁盘的第一个扇区。
大小:
part0 的大小等于整个磁盘的大小。
操作:
对part0的操作实际上是对整个磁盘的操作(如格式化)
每个分区都有一个struct hd_struct实例。
struct hd_struct {
sector_t start_sect; //分区在磁盘的起始扇区。
sector_t nr_sects; //该分配的总扇区数。
struct kobject *holder_dir;
};
struct gendisk *alloc_disk(int minors);
参数minors:从设备数目。
作用:分配struct gendisk,包括每个分区的hd_struct指针。
void del_gendisk(struct gendisk *gp);
3. 几个结构体的联系
一个磁盘(struct gendisk)有多个分区。
其中每个分区都有一个:
struct block_device(打开该分区设备文件时创建)
struct hd_struct
struct gendisk:一个磁盘。
struct hd_struct:磁盘中一个分区。
struct block_device:VFS层,打开一个分区的设备文件时创建该结构体实例。
struct hd_struct的kobject->parent指向struct gendisk的kobject。
4. 块设备操作
//不由VFS调用,而是被def_blk_fops调用
struct block_device_operations {
int (*open) (struct block_device *, fmode_t);
void (*release) (struct gendisk *, fmode_t);
int (*media_changed) (struct gendisk *);
//检查存储介质是否改变(磁盘是否被移除了)
int (*revalidate_disk) (struct gendisk *);
//替换一个新存储介质时使用。
};
5. 请求队列
请求队列:包含对一个块设备的读写请求。
struct gendisk {
struct request_queue *queue;
}
struct block_device {
struct request_queue *bd_queue;
}
struct request_queue {
struct list_head queue_head;
//连接块设备的IO请求(struct request),内核会重排链表以提高性能。
struct elevator_queue *elevator;
request_fn_proc *request_fn;
//向队列添加新读写IO请求。
//该函数需要驱动各自实现。
make_request_fn *make_request_fn;
//创建新请求。
softirq_done_fn *softirq_done_fn;
//异步处理IO请求时,用于通知请求已处理完毕。
struct request_list rq; //request实例的缓存
unsigned long nr_requests //队列可管理的请求最大数目
unsigned long queue_flags; //队列标志
}
struct request_queue *blk_init_queue_node(request_fn_proc *rfn,
spinlock_t *lock, int node_id)
作用:生成一个标准的请求队列。
6.5.3 向系统添加磁盘和分区
1. 添加分区
struct hd_struct *add_partition(struct gendisk *disk, int partno,
sector_t start, sector_t len, int flags, ,)
作用:向通用硬盘(struct gendisk)添加一个新分区。
内容:
1. 分配struct hd_struct实例。并初始化分区信息。
2. 设置p->kobj,然后kobject_add();
2. 添加硬盘
void add_disk(struct gendisk *disk):
作用:向系统添加通用硬盘。
6.5.4 打开块设备文件
创建块设备的设备文件时(mknod),调用init_special_inode函数。
void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
{
inode->i_mode = mode;
if (S_ISBLK(mode)) {
inode->i_fop = &def_blk_fops;
}
}
struct file_operations def_blk_fops = {
.open = blkdev_open,
};
所以打开块设备的设备文件时:
VFS将调用blkdev_open(struct inode * inode, struct file * filp)