KVM虚拟机IO处理过程(一) ----Guest VM I/O 处理过程

虚拟化技术主要包含三部分内容:CPU虚拟化,内存虚拟化,设备虚拟化.本系列文章主要描述磁盘设备的虚拟化过程,包含了一个读操作的I/O请求如何从Guest Vm到其最终被处理的整个过程.本系列文章中引用到的linux内核代码版本为3.7.10,使用的虚拟化平台是KVM,qemu的版本是1.6.1.

用户程序想要访问IO设备需要调用操作系统提供的接口,即系统调用.当在用户程序中调用一个read操作时,系统先保存好read操作的参数,然后调用int 80命令(也可能是sysenter)进入内核空间,在内核空间中,读操作的逻辑由sys_read函数实现.

在讲sys_read的实现过程之前,我们先来看看read操作在内核空间需要经历的层次结构.从图中可以看出,read操作首先经过虚拟文件系统曾(vfs), 接下来是具体的文件系统层,Page cache层,通用块层(generic block layer),I/O调度层(I/O scheduler layer),块设备驱动层(block device driver layer),最后是块物理设备层(block device layer).

  • 虚拟文件系统层:该层屏蔽了下层的具体操作,为上层提供统一的接口,如vfs_read,vfs_write等.vfs_read,vfs_write通过调用下层具体文件系统的接口来实现相应的功能.
  • 具体文件系统层:该层针对每一类文件系统都有相应的操作和实现了,包含了具体文件系统的处理逻辑.
  • page cache层:该层缓存了从块设备中获取的数据.引入该层的目的是避免频繁的块设备访问,如果在page cache中已经缓存了I/O请求的数据,则可以将数据直接返回,无需访问块设备.
  • 通过块层:接收上层的I/O请求,并最终发出I/O请求.该层向上层屏蔽了下层设备的特性.
  • I/O调度层:   接收通用块层发出的 IO 请求,缓存请求并试图合并相邻的请求(如果这两个请求的数据在磁盘上是相邻的)。并根据设置好的调度算法,回调驱动层提供的请求处理函数,以处理具体的 IO 请求
  • 块设备驱动层:从上层取出请求,并根据参数,操作具体的设备.
  • 块设备层:真正的物理设备.

了解了内核层次的结构,让我们来看一下read操作的代码实现.

sys_read函数声明在include/linux/syscalls.h文件中,

[cpp] view plaincopy

  1. asmlinkage long sys_read(unsigned int fd, char __user *buf, size_t count);

其函数实现在fs/read_write.c文件中:

[cpp] view plaincopy

  1. SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
  2. {
  3. struct fd f = fdget(fd);
  4. ssize_t ret = -EBADF;
  5. if (f.file) {
  6. loff_t pos = file_pos_read(f.file);
  7. ret = vfs_read(f.file, buf, count, &pos); //调用vfs layer中的read操作
  8. file_pos_write(f.file, pos);//设置当前文件的位置
  9. fdput(f);
  10. }
  11. return ret;
  12. }

vfs_read函数属于vfs layer,定义在fs/read_write.c, 其主要功能是调用具体文件系统中对应的read操作,如果具体文件系统没有提供read操作,则使用默认的do_sync_read函数.

[cpp] view plaincopy

  1. ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
  2. {
  3. ssize_t ret;
  4. if (!(file->f_mode & FMODE_READ))
  5. return -EBADF;
  6. if (!file->f_op || (!file->f_op->read && !file->f_op->aio_read))
  7. return -EINVAL;
  8. if (unlikely(!access_ok(VERIFY_WRITE, buf, count)))
  9. return -EFAULT;
  10. ret = rw_verify_area(READ, file, pos, count);
  11. if (ret >= 0) {
  12. count = ret;
  13. if (file->f_op->read) {
  14. ret = file->f_op->read(file, buf, count, pos); //该函数由具体的文件系统指定
  15. } else
  16. ret = do_sync_read(file, buf, count, pos);  //内核默认的读文件操作
  17. if (ret > 0) {
  18. fsnotify_access(file);
  19. add_rchar(current, ret);
  20. }
  21. inc_syscr(current);
  22. }
  23. return ret;
  24. }

file->f_op的类型为struct file_operations, 该类型定义了一系列涉及文件操作的函数指针,针对不同的文件系统,这些函数指针指向不同的实现.以ext4 文件系统为例子,该数据结构的初始化在fs/ext4/file.c,从该初始化可以知道,ext4的read操作调用了内核自带的do_sync_read()函数

[cpp] view plaincopy

  1. const struct file_operations ext4_file_operations = {
  2. .llseek     = ext4_llseek,
  3. .read       = do_sync_read,
  4. .write      = do_sync_write,
  5. .aio_read   = generic_file_aio_read,
  6. .aio_write  = ext4_file_write,
  7. .unlocked_ioctl = ext4_ioctl,
  8. #ifdef CONFIG_COMPAT
  9. .compat_ioctl   = ext4_compat_ioctl,
  10. #endif
  11. .mmap       = ext4_file_mmap,
  12. .open       = ext4_file_open,
  13. .release    = ext4_release_file,
  14. .fsync      = ext4_sync_file,
  15. .splice_read    = generic_file_splice_read,
  16. .splice_write   = generic_file_splice_write,
  17. .fallocate  = ext4_fallocate,
  18. };

do_sync_read()函数定义fs/read_write.c中,

[cpp] view plaincopy

  1. ssize_t do_sync_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos)
  2. {
  3. struct iovec iov = { .iov_base = buf, .iov_len = len };
  4. struct kiocb kiocb;
  5. ssize_t ret;
  6. init_sync_kiocb(&kiocb, filp);//初始化kiocp,描述符kiocb是用来记录I/O操作的完成状态
  7. kiocb.ki_pos = *ppos;
  8. kiocb.ki_left = len;
  9. kiocb.ki_nbytes = len;
  10. for (;;) {
  11. ret = filp->f_op->aio_read(&kiocb, &iov, 1, kiocb.ki_pos);//调用真正做读操作的函数,ext4文件系统在fs/ext4/file.c中配置
  12. if (ret != -EIOCBRETRY)
  13. break;
  14. wait_on_retry_sync_kiocb(&kiocb);
  15. }
  16. if (-EIOCBQUEUED == ret)
  17. ret = wait_on_sync_kiocb(&kiocb);
  18. *ppos = kiocb.ki_pos;
  19. return ret;
  20. }

在ext4文件系统中filp->f_op->aio_read函数指针只想generic_file_aio_read, 该函数定义于mm/filemap.c文件中,该函数有两个执行路径,如果是以O_DIRECT方式打开文件,则读操作跳过page cache直接去读取磁盘,否则调用do_generic_sync_read函数尝试从page cache中获取所需的数据.

[cpp] view plaincopy

  1. ssize_t
  2. generic_file_aio_read(struct kiocb *iocb, const struct iovec *iov,
  3. unsigned long nr_segs, loff_t pos)
  4. {
  5. struct file *filp = iocb->ki_filp;
  6. ssize_t retval;
  7. unsigned long seg = 0;
  8. size_t count;
  9. loff_t *ppos = &iocb->ki_pos;
  10. count = 0;
  11. retval = generic_segment_checks(iov, &nr_segs, &count, VERIFY_WRITE);
  12. if (retval)
  13. return retval;
  14. /* coalesce the iovecs and go direct-to-BIO for O_DIRECT */
  15. if (filp->f_flags & O_DIRECT) {
  16. loff_t size;
  17. struct address_space *mapping;
  18. struct inode *inode;
  19. struct timex txc;
  20. do_gettimeofday(&(txc.time));
  21. mapping = filp->f_mapping;
  22. inode = mapping->host;
  23. if (!count)
  24. goto out; /* skip atime */
  25. size = i_size_read(inode);
  26. if (pos < size) {
  27. retval = filemap_write_and_wait_range(mapping, pos,
  28. pos + iov_length(iov, nr_segs) - 1);
  29. if (!retval) {
  30. retval = mapping->a_ops->direct_IO(READ, iocb,
  31. iov, pos, nr_segs);
  32. }
  33. if (retval > 0) {
  34. *ppos = pos + retval;
  35. count -= retval;
  36. }
  37. /*
  38. * Btrfs can have a short DIO read if we encounter
  39. * compressed extents, so if there was an error, or if
  40. * we‘ve already read everything we wanted to, or if
  41. * there was a short read because we hit EOF, go ahead
  42. * and return.  Otherwise fallthrough to buffered io for
  43. * the rest of the read.
  44. */
  45. if (retval < 0 || !count || *ppos >= size) {
  46. file_accessed(filp);
  47. goto out;
  48. }
  49. }
  50. }
  51. count = retval;
  52. for (seg = 0; seg < nr_segs; seg++) {
  53. read_descriptor_t desc;
  54. loff_t offset = 0;
  55. /*
  56. * If we did a short DIO read we need to skip the section of the
  57. * iov that we‘ve already read data into.
  58. */
  59. if (count) {
  60. if (count > iov[seg].iov_len) {
  61. count -= iov[seg].iov_len;
  62. continue;
  63. }
  64. offset = count;
  65. count = 0;
  66. }
  67. desc.written = 0;
  68. desc.arg.buf = iov[seg].iov_base + offset;
  69. desc.count = iov[seg].iov_len - offset;
  70. if (desc.count == 0)
  71. continue;
  72. desc.error = 0;
  73. do_generic_file_read(filp, ppos, &desc, file_read_actor);
  74. retval += desc.written;
  75. if (desc.error) {
  76. retval = retval ?: desc.error;
  77. break;
  78. }
  79. if (desc.count > 0)
  80. break;
  81. }
  82. out:
  83. return retval;
  84. }

do_generic_file_read定义在mm/filemap.c文件中,该函数调用page cache层中相关的函数.如果所需数据存在与page cache中,并且数据不是dirty的,则从page cache中直接获取数据返回.如果数据在page cache中不存在,或者数据是dirty的,则page cache会引发读磁盘的操作.该函数的读磁盘并不是简单的只读取所需数据的所在的block,而是会有一定的预读机制来提高cache的命中率,减少磁盘访问的次数.

page cache层中真正读磁盘的操作为readpage系列,readpage系列函数具体指向的函数实现在fs/ext4/inode.c文件中定义,该文件中有很多个struct address_space_operation对象来对应与不同日志机制,我们选择linux默认的ordered模式的日志机制来描述I/O的整个流程, ordered模式对应的readpage系列函数如下所示.

[cpp] view plaincopy

  1. static const struct address_space_operations ext4_ordered_aops = {
  2. .readpage       = ext4_readpage,
  3. .readpages      = ext4_readpages,
  4. .writepage      = ext4_writepage,
  5. .write_begin        = ext4_write_begin,
  6. .write_end      = ext4_ordered_write_end,
  7. .bmap           = ext4_bmap,
  8. .invalidatepage     = ext4_invalidatepage,
  9. .releasepage        = ext4_releasepage,
  10. .direct_IO      = ext4_direct_IO,
  11. .migratepage        = buffer_migrate_page,
  12. .is_partially_uptodate  = block_is_partially_uptodate,
  13. .error_remove_page  = generic_error_remove_page,
  14. };

为简化流程,我们选取最简单的ext4_readpage函数来说明,该函数实现位于fs/ext4/inode.c中,函数很简单,只是调用了mpage_readpage函数.mpage_readpage位于fs/mpage.c文件中,该函数生成一个IO请求,并提交给Generic block layer.

[cpp] view plaincopy

  1. int mpage_readpage(struct page *page, get_block_t get_block)
  2. {
  3. struct bio *bio = NULL;
  4. sector_t last_block_in_bio = 0;
  5. struct buffer_head map_bh;
  6. unsigned long first_logical_block = 0;
  7. map_bh.b_state = 0;
  8. map_bh.b_size = 0;
  9. bio = do_mpage_readpage(bio, page, 1, &last_block_in_bio,
  10. &map_bh, &first_logical_block, get_block);
  11. if (bio)
  12. mpage_bio_submit(READ, bio);
  13. return 0;
  14. }

Generic block layer会将该请求分发到具体设备的IO队列中,由I/O Scheduler去调用具体的driver接口获取所需的数据.

至此,在Guest vm中整个I/O的流程已经介绍完了,后续的文章会介绍I/O操作如何从Guest vm跳转到kvm及如何在qemu中模拟I/O设备.

参考资料:

1. read系统调用剖析:http://www.ibm.com/developerworks/cn/linux/l-cn-read/

转载:http://blog.csdn.net/dashulu/article/details/16820281

时间: 2024-10-22 12:18:24

KVM虚拟机IO处理过程(一) ----Guest VM I/O 处理过程的相关文章

KVM虚拟机IO处理过程(二) ----QEMU/KVM I/O 处理过程

接着KVM虚拟机IO处理过程中Guest Vm IO处理过程(http://blog.csdn.net/dashulu/article/details/16820281),本篇文章主要描述IO从guest vm跳转到kvm和qemu后的处理过程. 首先回顾一下kvm的启动过程(http://blog.csdn.net/dashulu/article/details/17074675).qemu通过调用kvm提供的一系列接口来启动kvm. qemu的入口为vl.c中的main函数,main函数通过

KVM虚拟机快照链创建,合并,删除及回滚研究

1 QEMU,KVM,libvirt关系 QEMU QEMU提供了一个开源的服务器全虚拟化解决方案,它可以使你在特定平台的物理机上模拟出其它平台的处理器,比如在X86 CPU上虚拟出Power的CPU,此时的guest OS感觉不到虚拟机的存在,就像运行在物理机上,QEMU可以单独使用模拟CPU和各种外设,也可以作为一个用户空间工具和运行在内核中的KVM结合使用以充分发挥KVM的功能,QEMU的wiki KVM KVM是一个基于内核的虚拟机(Linux中一个可加载模块),在硬件支持虚拟化(int

CENTOS6.4上KVM虚拟机环境搭建

关键词: KVM,虚拟机,windows7, VNC, 桥接网络,br0, SCSI, IDE 环境: host: CENTOS6.4 guest: windows 7 sp1 主要步骤: 安装软件包 配置桥接网络 配置VNC 安装虚拟机 1.安装软件包 yum install qemu-kvm yum install libvirt   yum install libvirt-python 其实CENTOS6.4上已经安装了这三个软件包,不用再装了. rpm -qa | grep qumu-k

CentOS 6.3系统安装配置KVM虚拟机

作业环境 服务器端 操作系统:CentOS 6.3 final x86_64 IP: 133.133.10.50 Hostname:myKVM KVM:qemu-kvm-0.12.1.2-2.295.el6_3.2.x86_64 客户端 Ubuntu和Win7,先在服务器端装好VNC,通过VNC连接服务器CentOS 一.安装KVM及相关软件 1.KVM 需要有 CPU 的支持(Intel vmx 或 AMD svm),在安装 KVM 之前检查一下 CPU 是否提供了虚拟技术的支持: [[ema

KVM虚拟机快照研究(一)

KVM虚拟机的快照用来保存虚拟机在某个时间点的内存.磁盘或者设备状态,如果将来有需要可以把虚拟机的状态回滚到这个时间点. 根据被做快照的对象不同,快照可以分为磁盘快照和内存快照,两者加起来构成了一个系统还原点,记录虚拟机在某个时间点的全部状态:根据做快照时虚拟机是否在运行,快照又可以分为在线快照和离线快照. 磁盘快照根据存储方式的不同,又分为内部快照和外部快照:内部快照只支持qcow2格式的虚拟机镜像,把快照及后续变动都保存在原来的qcow2文件内:外部快照在创建时,快照被保存在单独一个文件中,

(转)CentOS7安装KVM虚拟机详解

原文:https://github.com/jaywcjlove/handbook/blob/master/CentOS/CentOS7%E5%AE%89%E8%A3%85KVM%E8%99%9A%E6%8B%9F%E6%9C%BA%E8%AF%A6%E8%A7%A3.md 基于 CentOS Linux release 7.2.1511 (Core) 的环境下命令行的方式安装KVM的详细过程. 目录 检测是否支持KVM 安装 KVM 环境 安装虚拟机 命令行配置系统 连接虚拟机 虚拟机其它管理

KVM虚拟化之安装KVM虚拟机(一)

KVM虚拟化 1.KVM虚拟化介绍与应用场景 什么是KVM虚拟化? KVM,内核级虚拟化技术 Kernel-based Virtual Machine .KVM的虚拟化需要硬件支持(如Intel VT技术或者AMD V技术).是基于硬件的完全虚拟化.虚拟化就是通过模拟计算机硬件(cpu,内存,硬盘,网卡)来实现在一台物理服务器上运行同时多个不同的操作系统,使每个操作系统之间都是互相隔离的,并且应用程序都可以在相互独立的空间内运行而互不影响,可以实现资源的动态分配.灵活调度.跨域共享,提高资源利用

KVM虚拟机安装管理——qemu-kvm方式

KVM (kernel-based virtual machine,内核虚拟机),是一个开源的系统虚拟化模块,自Linux 2.6.20之后集成在Linux的各个主要发行版本中.它使用Linux自身的调度器进行管理,虚拟化效率高.管理简便. 1)系统环境  操作系统 CentOS release 6.3 内核版本 2.6.32-279.el6.x86_64 服务器型号 Dell  R410 基本配置 32G内存.8核CPU.4T硬盘 2)安装KVM软件,加载内核模块 2.1)安装kvm 内核模块

在Linux系统 CentOS 6 下安装KVM虚拟机

一.KVM简介 KVM是开源软件,全称是kernel-based virtual machine(基于内核的虚拟机),是一个开源的系统虚拟化模块,基于硬件的完全虚拟化,不过需要硬件支持(如Intel VT技术或者AMD V技术).自Linux 2.6.20之后集成在Linux的各个主要发行版本中.它使用Linux自身的调度器进行管理,所以相对于Xen,其核心源码很少.KVM目前已成为学术界的主流VMM之一. 二.KVM安装 1. 准备工作 关闭iptables防火墙 BIOS开启CPU支持虚拟化