IO体系结构是什么样的?
系统如何判断设备数据是否就绪方式?
- 目前系统判断设备上的数据是否就绪采用了
轮询
和中断
两种方式。轮询
方式是不断的重复询问设备上的数据是否可用,如果可用,CPU就读取数据;中断
方式中系统为每个CPU提供了中断线,可由各个系统设备共享。每个中断通过一个唯一的标识,内核对使用的每个中断提供一个中断服务。中断将暂停正常系统工作,在外设的数据已经就绪,需要由内核或者应用处理,外设会引发一个中断,系统就不需要频繁检查是否有新的数据可用,外设有新数据的情况会自动通知系统。
内核如何管理磁盘设备?
- 内核目前采用udevd方式来管理用户层动态创建的设备文件。udevd就是一个守护进程。当内核检测到一个设备时候,对应创建和初始化一个
kobject
对象,此对象借助sysfs文件系统导出到用户层,同时内核还会像用户态发送一个热插拔消息。如果系统启动期间发现新新磁盘设备,内核产生的可插拔消息包含了磁盘驱动给新磁盘分配的主从设备号,接着udevd监听这些消息,完成新磁盘的注册,完成以后会在/dev中创建对应的项,用户可以访问该磁盘设备了。udevd机制中 ,/dev
不在放到磁盘文件系统中,而是使用tmpfs,系统重启或者关机数据会自动消失,是基于内存的fs.
块设备和vfs是啥关系
- 内核中设备文件都是关联一个inode,用于管理文件的属性,通过函数
init_special_inode
对一个设备文件进行初始化并创建一个inode.进程和vfs的IO交互式是通过struct file
结构,每个struct file
关联一个inode,具体的联系关系如下:
代码语言:javascript
复制// inode 结构,省略一些字段
struct inode {
umode_t i_mode;
const struct inode_operations *i_op;
struct super_block *i_sb;
unsigned long i_ino;
dev_t i_rdev;
loff_t i_size;
union {
const struct file_operations *i_fop;
void (*free_inode)(struct inode *);
};
union {
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
struct cdev *i_cdev;
char *i_link;
unsigned i_dir_seq;
};
} __randomize_layout;
// 字符设备、块设备特殊设备的inode初始化函数
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 */
else
printk(KERN_DEBUG "init_special_inode: bogus i_mode (%o) for"
" inode %s:%lun", mode, inode->i_sb->s_id,
inode->i_ino);
}
EXPORT_SYMBOL(init_special_inode);
块设备相比字符设备有啥不同?
- 块设备可以在数据中任何位置进行访问,而字符设备是有可能但不是必然的行为;数据的传输总是按照固定的长度的块进行传输,即使请求一个字节的数据,块设备的驱动程序也会从块设备读取一个完成的块数据,而字符设备只能返回单个字节;对于块设备的访问有容量较大的缓存,针对已经读取的块数据缓存在BufferCache中,下次再次读取时候直接从缓冲获取。这里提到的块是一个特定长度的序列,是用于保存内核和设备之间传输数据,块大小一般使用
mkfs
命令格式化文件系统时候可以指定(仅仅在一个特定的范围),块的最大长度一般特定体系结构(这里是指CPU),在IA-32系统中支持块的长度是4906,因为内存page长度是4K;IA-64和Alpha系统支持8K的块长度。
块设备、磁盘、分区在内核中如何表示?
- 块设备是用
struct block_device
结构表示;struct gendisk
用于表示磁盘;struct hd_struct
用于表示分区。他们的之间的关系如下:
代码语言:javascript
复制struct block_device {
// bd_dev保存块设备号
dev_t bd_dev;
// bd_openers表示块设备打开的次数
int bd_openers;
// bd_inode指向bdev伪文件系统的inode,未来会废弃
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;
u8 bd_partno;
// db_part用来表示块设备上的分区
struct hd_struct * bd_part;
/* number of times partitions within this device have been opened. */
unsigned bd_part_count;
spinlock_t bd_size_lock; /* for bd_inode->i_size updates */
// bd_disk已经分区的磁盘的抽象层
struct gendisk * bd_disk;
struct backing_dev_info *bd_bdi;
/* The counter of freeze processes */
int bd_fsfreeze_count;
/* Mutex for freeze */
struct mutex bd_fsfreeze_mutex;
} __randomize_layout;
内核中的bio起到什么作用?
bio
用于系统和设备之间的数据传输,bio是位于通用的块设备层介于磁盘文件系统和驱动程序之间的通用块设备层Generic Block Layer
。bio的主要管理结构关联到一个数组,数组各项都指向一个page实例,这些页用于从设备接收数据、向设备发送数据。
IO调度算法有哪些?
- 内核的IO调度器是由各种调度和重排IO操作的算法集合,内核中提供三种内核算法
noop
、deadline
、cfq
;noop
调度算法按照新来请求先来先到的原则依次添加到请求队列,以便进行处理,请求会进行合并但是不会进行重排,该调度算法仅对于能够自行重排请求的硬件或者没有寻道时间开销的硬件设备(ssd)是一个合理的选择。deadline
算法视图最小化磁盘寻道(读写磁盘的移动)的次数,并尽可能确保请求在一定时间内处理完成,内核会使用定时器机制实现单个请求的到期时间,该算法是按照时延优先原则。cfq
算法是完全公平算法,同一个进程的IO请求,总是在同一个队列处理,时间片会分配到每个队列,内核使用一个轮转算法处理各个队列,确保IO带宽以公平的方式咋不同队列之间共享,该算法是按照平均处理数据量优先的原则。内核中每一种IO调度算法都是以struct elevator_type
来表示。
代码语言:javascript
复制// cfq调度算法
static struct elevator_type iosched_cfq = {
.ops.sq = {
// 检查下一新的请求是否与可以和现在的请求合并
.elevator_merge_fn = cfq_merge,
// 请求合并后调用
.elevator_merged_fn = cfq_merged_request,
// 将两个请求合并为一个请求
.elevator_merge_req_fn = cfq_merged_requests,
.elevator_allow_bio_merge_fn = cfq_allow_bio_merge,
.elevator_allow_rq_merge_fn = cfq_allow_rq_merge,
.elevator_bio_merged_fn = cfq_bio_merged,
// 从给定的队列中选择下一步应该调度执行的请求
.elevator_dispatch_fn = cfq_dispatch_requests,
// 添加请求到队列
.elevator_add_req_fn = cfq_insert_request,
.elevator_activate_req_fn = cfq_activate_request,
.elevator_deactivate_req_fn = cfq_deactivate_request,
.elevator_completed_req_fn = cfq_completed_request,
.elevator_former_req_fn = elv_rb_former_request,
.elevator_latter_req_fn = elv_rb_latter_request,
.elevator_init_icq_fn = cfq_init_icq,
.elevator_exit_icq_fn = cfq_exit_icq,
// 创建新请求时调用
.elevator_set_req_fn = cfq_set_request,
// 释放回内存子系统时调用
.elevator_put_req_fn = cfq_put_request,
.elevator_may_queue_fn = cfq_may_queue,
// 请求队列初始化时候调用
.elevator_init_fn = cfq_init_queue,
// 请求队列释放时候调用
.elevator_exit_fn = cfq_exit_queue,
.elevator_registered_fn = cfq_registered_queue,
},
.icq_size = sizeof(struct cfq_io_cq),
.icq_align = __alignof__(struct cfq_io_cq),
.elevator_attrs = cfq_attrs,
.elevator_name = "cfq",
.elevator_owner = THIS_MODULE,
};
// deadline调度算法
static struct elevator_type iosched_deadline = {
.ops.sq = {
.elevator_merge_fn = deadline_merge,
.elevator_merged_fn = deadline_merged_request,
.elevator_merge_req_fn = deadline_merged_requests,
.elevator_dispatch_fn = deadline_dispatch_requests,
.elevator_completed_req_fn = deadline_completed_request,
.elevator_add_req_fn = deadline_add_request,
.elevator_former_req_fn = elv_rb_former_request,
.elevator_latter_req_fn = elv_rb_latter_request,
.elevator_init_fn = deadline_init_queue,
.elevator_exit_fn = deadline_exit_queue,
},
.elevator_attrs = deadline_attrs,
.elevator_name = "deadline",
.elevator_owner = THIS_MODULE,
};
// noop 调度算法
static struct elevator_type elevator_noop = {
.ops.sq = {
.elevator_merge_req_fn = noop_merged_requests,
.elevator_dispatch_fn = noop_dispatch,
.elevator_add_req_fn = noop_add_request,
.elevator_former_req_fn = noop_former_request,
.elevator_latter_req_fn = noop_latter_request,
.elevator_init_fn = noop_init_queue,
.elevator_exit_fn = noop_exit_queue,
},
.elevator_name = "noop",
.elevator_owner = THIS_MODULE,
};