0%

《趣谈linux操作系统》小结(二十九) - 块设备

块设备

输入输出系统的设备包括字符设备和块设备。块设备也和字符设备类似,需要注册、打开、读写操作。

硬盘是最常见的块设备。块设备一般需要被格式化为文件系统,然后通过文件系统接口调用操作。

块设备也需要mknod到/dev/xxx下面,/dev 路径下面是 devtmpfs 文件系统。和字符设备一样,需要为/dev/xxx分配一个inode,inode 里面的 i_rdev 被设置成了块设备的设备号 dev_t。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
{
inode->i_mode = mode;
if (S_ISCHR(mode)) {
inode->i_fop = &def_chr_fops;
inode->i_rdev = rdev;
} else if (S_ISBLK(mode)) {
inode->i_fop = &def_blk_fops;
inode->i_rdev = rdev;
} else if (S_ISFIFO(mode))
inode->i_fop = &pipefifo_fops;
else if (S_ISSOCK(mode))
; /* leave it no_open_fops */
}

这个inode是不能操作文件系统的, 我们需要把他mount到一个文件夹下面。如果这个块设备原来被格式化为一种文件系统的格式,例如 ext4,那我们调用的就是 ext4 相应的 mount 操作。ext4文件系统就是调用的ext4_mount函数,然后调用mount_bdev。mount_bdev主要就是做了2个工作:

  • 调用blkdev_get_by_path 根据 /dev/xxx 这个名字,找到相应的设备并打开它;块设备是在这里打开的,不是调用的file_ops的函数.
  • sget 根据打开的设备文件,填充 ext4 文件系统的 super_block

如果块设备分区了,分别格式化成不同的文件系统, 就有下面这张复杂的关系图了。

图片替换文本

gendisk 只有一个实例,指向 /dev/sda。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

struct gendisk {
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);
......
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 kobject *slave_dir;
......
};

struct disk_part_tbl 结构里是一个 struct hd_struct 的数组,用于表示各个分区。struct block_device_operations fops 指向对于这个块设备的各种操作。struct request_queue queue 是表示在这个块设备上的请求队列。struct hd_struct 是用来表示某个分区的,在上面的例子中,有两个 hd_struct 的实例,分别指向 /dev/sda1、 /dev/sda2。

1
2
3
4
5
6
7
8
9
10
11
12
13
14

struct hd_struct {
sector_t start_sect;
sector_t nr_sects;
......
struct device __dev;
struct kobject *holder_dir;
int policy, partno;
struct partition_meta_info *info;
......
struct disk_stats dkstats;
struct percpu_ref ref;
struct rcu_head rcu_head;
};

在 hd_struct 中,比较重要的成员变量保存了如下的信息:从磁盘的哪个扇区开始,到哪个扇区结束。而 block_device 既可以表示整个块设备,也可以表示某个分区,所以对于上面的例子,block_device 有三个实例,分别指向 /dev/sda1、/dev/sda2、/dev/sda。

block_device 的成员变量 bd_disk,指向的 gendisk 就是整个块设备。这三个实例都指向同一个 gendisk。bd_part 指向的某个分区的 hd_struct,bd_contains 指向的是整个块设备的 block_device。

图片替换文本

这个流程挺复杂的,但是归根到底都逃不出增删改查的操作,相互关联通过指针来关联,先注册回调函数,然后操作时调用回调。

行动,才不会被动!

欢迎关注个人公众号 微信 -> 搜索 -> fishmwei,沟通交流。