zoukankan      html  css  js  c++  java
  • 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.   
    6.     if (f.file) {  
    7.         loff_t pos = file_pos_read(f.file);  
    8.         ret = vfs_read(f.file, buf, count, &pos); //调用vfs layer中的read操作  
    9.         file_pos_write(f.file, pos);//设置当前文件的位置  
    10.         fdput(f);  
    11.     }  
    12.     return ret;  
    13. }  
     
        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.   
    5.     if (!(file->f_mode & FMODE_READ))  
    6.         return -EBADF;  
    7.     if (!file->f_op || (!file->f_op->read && !file->f_op->aio_read))  
    8.         return -EINVAL;  
    9.     if (unlikely(!access_ok(VERIFY_WRITE, buf, count)))  
    10.         return -EFAULT;  
    11.   
    12.     ret = rw_verify_area(READ, file, pos, count);  
    13.     if (ret >= 0) {  
    14.         count = ret;  
    15.         if (file->f_op->read) {  
    16.             ret = file->f_op->read(file, buf, count, pos); //该函数由具体的文件系统指定  
    17.         } else  
    18.             ret = do_sync_read(file, buf, count, pos);  //内核默认的读文件操作  
    19.         if (ret > 0) {  
    20.             fsnotify_access(file);  
    21.             add_rchar(current, ret);  
    22.         }  
    23.         inc_syscr(current);  
    24.     }  
    25.   
    26.     return ret;  
    27. }  
        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.   
    7.     init_sync_kiocb(&kiocb, filp);//初始化kiocp,描述符kiocb是用来记录I/O操作的完成状态  
    8.     kiocb.ki_pos = *ppos;  
    9.     kiocb.ki_left = len;  
    10.     kiocb.ki_nbytes = len;  
    11.   
    12.     for (;;) {  
    13.         ret = filp->f_op->aio_read(&kiocb, &iov, 1, kiocb.ki_pos);//调用真正做读操作的函数,ext4文件系统在fs/ext4/file.c中配置  
    14.         if (ret != -EIOCBRETRY)  
    15.             break;  
    16.         wait_on_retry_sync_kiocb(&kiocb);  
    17.     }  
    18.   
    19.     if (-EIOCBQUEUED == ret)  
    20.         ret = wait_on_sync_kiocb(&kiocb);  
    21.     *ppos = kiocb.ki_pos;  
    22.     return ret;  
    23. }  
        在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.   
    11.     count = 0;  
    12.     retval = generic_segment_checks(iov, &nr_segs, &count, VERIFY_WRITE);  
    13.     if (retval)  
    14.         return retval;  
    15.   
    16.     /* coalesce the iovecs and go direct-to-BIO for O_DIRECT */  
    17.     if (filp->f_flags & O_DIRECT) {  
    18.         loff_t size;  
    19.         struct address_space *mapping;  
    20.         struct inode *inode;  
    21.   
    22.         struct timex txc;  
    23.         do_gettimeofday(&(txc.time));  
    24.   
    25.         mapping = filp->f_mapping;  
    26.         inode = mapping->host;  
    27.         if (!count)  
    28.             goto out; /* skip atime */  
    29.         size = i_size_read(inode);  
    30.         if (pos < size) {  
    31.             retval = filemap_write_and_wait_range(mapping, pos,  
    32.                     pos + iov_length(iov, nr_segs) - 1);  
    33.             if (!retval) {  
    34.                 retval = mapping->a_ops->direct_IO(READ, iocb,  
    35.                             iov, pos, nr_segs);  
    36.             }  
    37.             if (retval > 0) {  
    38.                 *ppos = pos + retval;  
    39.                 count -= retval;  
    40.             }  
    41.   
    42.             /* 
    43.              * Btrfs can have a short DIO read if we encounter 
    44.              * compressed extents, so if there was an error, or if 
    45.              * we've already read everything we wanted to, or if 
    46.              * there was a short read because we hit EOF, go ahead 
    47.              * and return.  Otherwise fallthrough to buffered io for 
    48.              * the rest of the read. 
    49.              */  
    50.             if (retval < 0 || !count || *ppos >= size) {  
    51.                 file_accessed(filp);  
    52.                 goto out;  
    53.             }  
    54.         }  
    55.     }  
    56.   
    57.     count = retval;  
    58.     for (seg = 0; seg < nr_segs; seg++) {  
    59.         read_descriptor_t desc;  
    60.         loff_t offset = 0;  
    61.   
    62.         /* 
    63.          * If we did a short DIO read we need to skip the section of the 
    64.          * iov that we've already read data into. 
    65.          */  
    66.         if (count) {  
    67.             if (count > iov[seg].iov_len) {  
    68.                 count -= iov[seg].iov_len;  
    69.                 continue;  
    70.             }  
    71.             offset = count;  
    72.             count = 0;  
    73.         }  
    74.   
    75.         desc.written = 0;  
    76.         desc.arg.buf = iov[seg].iov_base + offset;  
    77.         desc.count = iov[seg].iov_len - offset;  
    78.         if (desc.count == 0)  
    79.             continue;  
    80.         desc.error = 0;  
    81.         do_generic_file_read(filp, ppos, &desc, file_read_actor);  
    82.         retval += desc.written;  
    83.         if (desc.error) {  
    84.             retval = retval ?: desc.error;  
    85.             break;  
    86.         }  
    87.         if (desc.count > 0)  
    88.             break;  
    89.     }  
    90. out:  
    91.     return retval;  
    92. }  
        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.   
    8.     map_bh.b_state = 0;  
    9.     map_bh.b_size = 0;  
    10.     bio = do_mpage_readpage(bio, page, 1, &last_block_in_bio,  
    11.             &map_bh, &first_logical_block, get_block);  
    12.     if (bio)  
    13.         mpage_bio_submit(READ, bio);  
    14.     return 0;  
    15. }  
     
        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
  • 相关阅读:
    POJ 2251 Dungeon Master
    HDU 3085 Nightmare Ⅱ
    CodeForces 1060 B Maximum Sum of Digits
    HDU 1166 敌兵布阵(树状数组)
    HDOJ 2050 折线分割平面
    HDU 5879 Cure
    HDU 1878 欧拉回路
    HDU 6225 Little Boxes
    ZOJ 2971 Give Me the Number
    HDU 2680 Choose the best route
  • 原文地址:https://www.cnblogs.com/wuchanming/p/4689701.html
Copyright © 2011-2022 走看看