read函数的底层调用。

很久之前对于虚拟文件部分的一些内容进行了整理。对于read的底层过程只是描述了一个大概,这次主要是对于每一部分的东向西进行详细的描述。go go go(方便对于面试时候的整理)。

参考 https://www.cnblogs.com/wangzahngjun/p/5553793.html

虚拟文件部分的整理参考原来博客的一片文章。具体的我就从open系统调用开始并且详细描述read的过程。

open系统调用:


对于sys_read 函数的调用过程如下

文件:
1.read()到sys_read();
2.通过task_struct->file_struct->fd[]->file*->f_op->read();
3通过目录项,找到该文件的inode;
4.在文件表中,通过文件内容偏移量计算出要读取的页;
5.通过inode找到文件对应的address_space;
6.在address_space中访问该文件的页缓存树,查找对应的页缓存结点:
(1)如果页缓存命中,那么直接返回文件内容;
(2)如果页缓存缺失,那么产生一个页缺失异常,创建一个页缓存页,同时通过inode找到文件该页的磁盘地址,读取

相应的页填充该缓存页;重新进行第6步查找页缓存;

大体过程如上面所述。

具体而言的,实际的一个文件系统也是如上面所说的过程,以ext2 问价系统来描述。


对于ext2而言过程如下 sys_read->file->fop->read->do_sync_read->filp->f_op->aio_read->generic_file_aio_read.

do_sync_read 函数


ssize_t do_sync_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos)
{
    struct iovec iov = { .iov_base = buf, .iov_len = len };//获取用户读字符保存buff空间的地址和所传进来的长度
    struct kiocb kiocb;//用来跟踪当前同步和异步的io
    ssize_t ret;

    init_sync_kiocb(&kiocb, filp);
    kiocb.ki_pos = *ppos;
    kiocb.ki_left = len;

    for (;;) {
        ret = filp->f_op->aio_read(&kiocb, &iov, 1, kiocb.ki_pos); //读操作开始 
        if (ret != -EIOCBRETRY)
            break;
        wait_on_retry_sync_kiocb(&kiocb);
    }

    if (-EIOCBQUEUED == ret)
        ret = wait_on_sync_kiocb(&kiocb);
    *ppos = kiocb.ki_pos;
    return ret;

首先所有的参数被一层一层的传递下来到达文件系统提供的read函数。

调用的是filp->op->aio_read 同步调用这个立即返回读到的的数据。

他的底层调用的是generic_file_aio_read函数功能分析

if (filp->f_flags & O_DIRECT) {//设置直接读取不从页缓存中读。
        loff_t size;
        struct address_space *mapping;
        struct inode *inode;

        mapping = filp->f_mapping;
        inode = mapping->host;
        if (!count)
            goto out; /* skip atime */
        size = i_size_read(inode);
        if (pos < size) {
            retval = filemap_write_and_wait_range(mapping, pos,
                    pos + iov_length(iov, nr_segs) - 1);
            if (!retval) {
                retval = mapping->a_ops->direct_IO(READ, iocb,
                            iov, pos, nr_segs);
            }
            if (retval > 0)
                *ppos = pos + retval;
            if (retval) {
                file_accessed(filp);
                goto out;
            }
        }

这个参数是open函数的一个参数具体查看man 2 open 对于这个标志进行介绍具体可以总结未以下几点

O_DIRECT,绕过缓冲区高速缓存,直接IO
直接IO:Linux允许应用程序在执行磁盘IO时绕过缓冲区高速缓存,从用户空间直接将数据传递到文件或磁盘设备,称为直接IO(direct IO)或者裸IO(raw IO)。
应用场景:数据库系统,其高速缓存和IO优化机制均自成一体,无需内核消耗CPU时间和内存去完成相同的任务。
使用直接IO的弊端:可能会大大降低性能,内核对缓冲区告诉缓存做了不少优化,包括:按顺序预读取,在成簇磁盘块上执行IO,允许访问同一文件的多个进程共享高速缓存的缓冲区。
使用方法:在调用open函数打开文件或设备时指定O_DIRECT标志。
注意可能发生的不一致性:若一进程以O_DIRECT标志打开某文件,而另一进程以普通(即使用了高速缓存缓冲区)打开同一文件,则由直接IO所读写的数据与缓冲区高速缓存中内容之间不存在一致性,应尽量避免这一场景。
使用直接IO需要遵守的一些限制:
用于传递数据的缓冲区,其内存边界必须对齐为块大小的整数倍
数据传输的开始点,即文件和设备的偏移量,必须是块大小的整数倍
待传递数据的长度必须是块大小的整数倍。

不遵守上述任一限制均将导致EINVAL错误。

如果没有设置上面标志那么它会从页缓存中读取

for (seg = 0; seg < nr_segs; seg++) {
        read_descriptor_t desc;
//将iovec结构转换成read_descriptor_t的结构
        desc.written = 0;
        desc.arg.buf = iov[seg].iov_base;//上面的参数重新打包一下啊
        desc.count = iov[seg].iov_len;
        if (desc.count == 0)
            continue;
        desc.error = 0;
        do_generic_file_read(filp, ppos, &desc, file_read_actor);//调用当前的函数进行读取。
        retval += desc.written;
        if (desc.error) {
            retval = retval ?: desc.error;
            break;
        }
        if (desc.count > 0)
            break;
    }
out:
    return retval;

do_generic_file_read()函数的主要功能是 //查找对应文件的页缓存 address_space 是根据文件的index所查找的对应的缓存页好。 find_get_page()使用地址空间的基树查找索引为index的页。尝试去找到第一个被请求的页。如果这个页不在页缓存中,就跳转到标号no_cached_page处,如果该页不是最新的,就跳转到标号page_not_up_to_date_locked处,如果在尝试去获取这个页面的独占权,即加锁的时候,没有获取成功,则跳转到page_not_up_to_date处。;

atic void do_generic_file_read(struct file *filp, loff_t *ppos,
        read_descriptor_t *desc, read_actor_t actor)
{
    struct address_space *mapping = filp->f_mapping;  //获取页高速缓存
    struct inode *inode = mapping->host;//获取inode
    struct file_ra_state *ra = &filp->f_ra;
    pgoff_t index;
    pgoff_t last_index;
    pgoff_t prev_index;
    unsigned long offset;      /* offset into pagecache page */
    unsigned int prev_offset;
    int error
    . ............................
    .............................

如果查到了但可能加锁的过程中出现的一下问题。

page_not_up_to_date_locked:
        /* Did it get truncated before we got the lock? */
  //有可能在锁页面的时候`有其它的进程将页面移除了页缓存区
         //在这种情况下:将page解锁`并减少它的使用计数,重新循环```
         //重新进入循环后,在页缓存区找不到对应的page.就会重新分配一个新的page
        if (!page->mapping) {
            unlock_page(page);
            page_cache_release(page);
            continue;
        }

        /* Did somebody else fill it already? */
         //在加锁的时候,有其它的进程完成了从文件系统到具体页面的映射?
         //在这种情况下,返回到page_ok.直接将页面上的内容copy到用户空间即可
        if (PageUptodate(page)) {
            unlock_page(page);
            goto page_ok;
        }

如果不是最新的页面则需要更新。更新的时候可能的问题。在这里调用实际的读page的操作mpping->a_ops->readpage()对该页进行读取。如果成功读取了一个页,检查其是否是最新的,如果是最新的,则跳转到标号page_ok处。如果发生了同步读错误,就设置其error,并跳转到readpage_error处。

readpage:
        /* Start the actual read. The read will unlock the page. */
        error = mapping->a_ops->readpage(filp, page);  //调用具体的readpage函数,在后面会分析。

        if (unlikely(error)) {   //如果发生了AOP_TRUNCATED_PAGE错误,则回到find_page重新进行获取
            if (error == AOP_TRUNCATED_PAGE) {
                page_cache_release(page);
                goto find_page;
            }
            goto readpage_error;
        }
//如果PG_uptodata标志仍然末设置.就一直等待,一直到page不处于锁定状态
         //  在将文件系统的内容读入page之前,page一直是处理Lock状态的。一直到
         //读取完成后,才会将页面解锁
        if (!PageUptodate(page)) {
            error = lock_page_killable(page);
            if (unlikely(error))
                goto readpage_error;
            if (!PageUptodate(page)) {
                if (page->mapping == NULL) {
                    /*
                     * invalidate_inode_pages got it
                     */
                    unlock_page(page);
                    page_cache_release(page);
                    goto find_page;
                }
                unlock_page(page);
                shrink_readahead_size_eio(filp, ra);
                error = -EIO;
                goto readpage_error;
            }
            unlock_page(page);
        }

        goto page_ok;  //读取成功

当该页不存在的时候:

这里主要讲述了当没有改页时时如何处理的。最后我们来看下do_generic_file_read函数的out

out:
    ra->prev_pos = prev_index;
    ra->prev_pos <<= PAGE_CACHE_SHIFT;
    ra->prev_pos |= prev_offset;

    *ppos = ((loff_t)index << PAGE_CACHE_SHIFT) + offset;  //计算实际的偏移量
    file_accessed(filp);//更新文件的最后一次访问时间。
}
具体上来说就这莫多东西。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值