虚拟化技术主要包含三部分内容: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文件中,
  1. asmlinkage long sys_read(unsigned int fd, char __user *buf, size_t count);

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

  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函数.
  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()函数

  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中,

  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中获取所需的数据.

  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系列函数如下所示.

  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.

  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设备.
 
 
参考资料:
转载:http://blog.csdn.net/dashulu/article/details/16820281

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

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

    接着KVM虚拟机IO处理过程中Guest Vm IO处理过程(http://blog.csdn.net/dashulu/article/details/16820281),本篇文章主要描述IO从gue ...

  2. ubuntu18.04上搭建KVM虚拟机环境超完整过程

    看标题这是篇纯运维的文章.在中小型企业中,一般很少配置专业的运维人员,都是由开发人员兼着.同时,对有志于技术管理的开发人员来说,多了解一些运维及整个软件生命周期的知识,是很有帮助的,因为带团队不仅仅是 ...

  3. 一次 KVM 虚拟机磁盘占满的排查过程

    一次 KVM 虚拟机磁盘占满的排查过程 KVM 虚拟机系统为 CentOS,文件系统为 XFS. 现象如下: 使用 df -h 命令发现磁盘剩余空间为30k(总大小为30G),使用 df -i 发现 ...

  4. 6、安装kvm虚拟机

    6.1.虚拟机开启虚拟化: 6.2.检查linux虚拟机cpu是否开启了虚拟化: egrep -o 'vmx|svm' /proc/cpuinfo vmx 6.3.安装kvm管理和安装kvm虚拟机的软 ...

  5. [原] KVM虚拟机网络闪断分析

    背景 公司云平台的机器时常会发生网络闪断,通常在10s-100s之间. 异常情况 VM出现问题时,表现出来的情况是外部监控系统无法访问,猜测可能是由于系统假死,OVS链路问题等等.但是在出现网络问题的 ...

  6. 关于Linux虚拟化技术KVM的科普 科普二(KVM虚拟机代码揭秘)

    代码分析文章<KVM虚拟机代码揭秘--QEMU代码结构分析>.<KVM虚拟机代码揭秘--中断虚拟化>.<KVM虚拟机代码揭秘--设备IO虚拟化>.<KVM虚拟 ...

  7. KVM虚拟机网络闪断分析

    https://www.cnblogs.com/Bozh/p/5484838.html 背景 公司云平台的机器时常会发生网络闪断,通常在10s-100s之间. 异常情况 VM出现问题时,表现出来的情况 ...

  8. Linux 中使用 virsh 管理 KVM 虚拟机 (转)

    术语 虚拟化指的是:在相同的物理(硬件)系统上,同时运行多个操作系统,且这几个系统相互隔离的可能性,而那个硬件在虚拟化架构中被称作宿主机(host).虚拟机监视器(也被称为虚拟机管理程序(hyperv ...

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

    原文:https://github.com/jaywcjlove/handbook/blob/master/CentOS/CentOS7%E5%AE%89%E8%A3%85KVM%E8%99%9A%E ...

随机推荐

  1. git 错误error: failed to push some refs to

    今天使用VSCODE 学习node.js,  想在git上push代码 于是在git上建立了一个私有的长裤, 连接后push代码时提示如下错误: error: failed to push some ...

  2. Pycharm Html CSS JS 快捷方式创建元素

    div#div1>ol>li.id*4 tab键 <div id="div1"> <ol> <li id="id"&g ...

  3. OpenLDAP权限配置

    安装好了openldap之后,就是对它进行配置了,其中一项就是设置访问控制,限制普通用户只能修改/访问他们能修改/访问的项.这就是ACL需要做的事情. 设置方法 1.可以将 include行放在/et ...

  4. 【Java】【转】在命令行中编译和运行java

    原文:http://blog.csdn.net/u010900574/article/details/50792353 同时加载编译多个jar包和java文件 在个人平常使用或者当我们把代码部署到Li ...

  5. https的网站用了百度分享后网站在浏览器中不安全解决方法

    本文出至:新太潮流网络博客 一.先下载百度分享需要的所有东西 下载地址:http://pan.baidu.com/s/1boM7N8V 二.打开百度分享,获取到分享的代码 三.将下载好的百度分享需要的 ...

  6. MySQL: OPTIMIZE TABLE: Table does not support optimize, doing recreate + analyze instead

    show create table history;-------------------------- CREATE TABLE `foo` (  `itemid` bigint(20) unsig ...

  7. UIWebView如何加载本地图片

    UIWebView如何加载本地图片 UIWebView加载本地图片是有实用价值的.比方说,有时候我们需要本地加载静态页来显示相关帮助信息,而这些帮助信息当中含有很多很多的富文本,用代码实现难度较大,这 ...

  8. 第八章 计时器(DIGCLOCK)

    /*-------------------------------------- DIGCLOCK.C -- Digital Clock (c) Charles Petzold, 1998 ----- ...

  9. September 30th 2017 Week 39th Saturday

    The simplest answer is often the correct one. 最简单的答案通常是最正确的答案. Simplest is always best. Sometimes yo ...

  10. 【Alpha】Daily Scrum Meeting——blog2

    团队成员 吴吉键 201421122007(组长) 魏修祺 201421122008 孙劲林 201421122022 1. 会议当天照片 忘记拍了!(没有拍照片的习惯,没有第四人拍照) 2. 每个人 ...