前言
在计算机的世界里,我们可以将业务进行抽象简化为两种场景——计算密集型和IO密集型。这两种场景下的表现,决定这一个计算机系统的能力。数据库作为一个典型的基础软件,它的所有业务逻辑同样可以抽象为这两种场景的混合。因此,一个数据库系统性能的强悍与否,往往跟操作系统和硬件提供的计算能力、IO能力紧密相关。
除了硬件本身的物理极限,操作系统在软件层面的处理以及提供的相关机制也尤为重要。因此,想要数据库发挥更加极限的性能,对操作系统内部相关机制和流程的理解就很重要。
本篇文章,我们就一起看下Linux中一个IO请求的生命周期。Linux发展到今天,其内部的IO子系统已经相当复杂。每个点展开都能自成一篇,所以本次仅是对块设备的写IO做一个快速的漫游,后续再对相关专题进行详细分解。
从用户态程序出发
首先需要明确的是,什么是块设备?我们知道IO设备可以分为字符设备和块设备,字符设备以字节流的方式访问数据,比如我们的键盘鼠标。而块设备则是以块为单位访问数据,并且支持随机访问,典型的块设备就是我们常见的机械硬盘和固态硬盘。
一个应用程序想将数据写入磁盘,它需要通过系统调用来完成:open打开文件 —> write写入文件 —> close关闭文件。
下面是write系统调用的定义,我们可以看到,应用程序只需要指定三个参数:
- 想要写入的文件
- 写入数据所在的内存地址
- 写入数据的长度
SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
size_t, count)
{
struct fd f = fdget_pos(fd);
ssize_t ret = -EBADF;
if (f.file) {
loff_t pos = file_pos_read(f.file);
ret = vfs_write(f.file, buf, count, &pos);
if (ret >= 0)
file_pos_write(f.file, pos);
fdput_pos(f);
}
return ret;
}
而剩下的工作就进入到内核中的虚拟文件系统(VFS)中进行处理。
虚拟文件系统(VFS)
在Linux中一切皆文件,它提供了虚拟文件系统vfs的机制,用来抽象各种资源,使应用程序无需关心底层细节,只需要通过open、read/write、close这几个通用接口便可以管理各种不同的资源。不同的文件系统通过实现各自的通用接口来满足不同的功能。
devtmpfs
挂载在/dev目录,devtmpfs中的文件代表各种设备。因此,对devtmpfs文件的读写操作,就是直接对相应设备的操作。
应用程序如果打开的是一个块设备文件,则说明它直接对一个块设备进行读写,调用块设备的write函数:
const struct file_operations def_blk_fops = {
.open = blkdev_open,
... ...
.read = do_sync_read,
.write = do_sync_write,
... ...
};
<