二,数据读取
数据读取分为同步读do_sync_read和异步读generic_file_aio_read。
它们的区别是同步读,要等数据读取结束函数才返回,异步读是在函数返回时数据读取可能还没结束。同步读是通过调用异步读取函数加等待函数实现的。
ssize_t
generic_file_aio_read(struct kiocb *iocb, const struct iovec *iov,
unsigned long nr_segs, loff_t pos)
{
struct file *filp = iocb->ki_filp;
ssize_t retval;
unsigned long seg;
size_t count;
loff_t *ppos = &iocb->ki_pos;
count = 0;
//检测用于存放将要读取数据的存储空间是否有效,
//此处是VERIFY_WRITE是因为读取的数据将要写入这些内存中。
retval = generic_segment_checks(iov, &nr_segs, &count, VERIFY_WRITE);
if (retval)
return retval;
/* coalesce the iovecs and go direct-to-BIO for O_DIRECT */
/*
将磁盘上的数据缓存在内存中,加速文件的读写。实际上,在一般情况下,read/write是只跟缓存打交道的。(当然,存在特殊情况。下面会说到。)read就直接从缓存读数据。如果要读的数据还不在缓存中,则触发一次读盘操作,然后等待磁盘上的数据被更新到磁盘高速缓存中;write也是直接写到缓存里去,然后就不用管了。后续内核会负责将数据写回磁盘。
如果定义了O_DIRECT:直接传送数据`绕过了页高速缓存
*/
if (filp->f_flags & O_DIRECT) {
。
。
。
}
for (seg = 0; seg
read_descriptor_t desc;
//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前有必要先看看函数file_read_actor。
int file_read_actor(read_descriptor_t *desc, struct page *page,
unsigned long offset, unsigned long size)
{
。
。
。
//大部分时间读写操作即是对缓存的读写,如果在缓存中找到了要读写的数据块则直接拷贝
//到用户空间。
left = __copy_to_user(desc->arg.buf, kaddr + offset, size);
。
。
。
}
/*****************************************************************************/
static 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;
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;
//找到页面的偏移量。即确定是存储在那个存面中
index = *ppos >> PAGE_CACHE_SHIFT;
prev_index = ra->prev_pos >> PAGE_CACHE_SHIFT;
prev_offset = ra->prev_pos & (PAGE_CACHE_SIZE-1);//获取页内偏移
//计算要读取数据在存储空间中的最后页面索引号
last_index = (*ppos + desc->count + PAGE_CACHE_SIZE-1) >> PAGE_CACHE_SHIFT;
offset = *ppos & ~PAGE_CACHE_MASK;
for (;;) {
struct page *page;
pgoff_t end_index;
loff_t isize;
unsigned long nr, ret;
//检查当前进程是否设置了重新调度标志`如果有`调用schdule()重新调度一次
cond_resched();
find_page:
//寻找当前位置对应的缓存页
page = find_get_page(mapping, index);
if (!page) {
//没有找到对应的缓存页,说明在页缓存区中不存在此页面对应的缓存页page_cache_sync_readahead(mapping,
ra, filp,
index, last_index - index);
page = find_get_page(mapping, index);
if (unlikely(page == NULL))
goto no_cached_page;
}
if (PageReadahead(page)) {
//文件预读
page_cache_async_readahead(mapping,
ra, filp,