1.2.5 Ext2层读文件入口函数
好了,我们知道了Ext2文件系统的磁盘布局,以及始终缓存的磁盘超级拷贝块结构ext2_super_block和动态缓存的已分配磁盘索引节点结构ext2_inode这些预备知识。接下来就假设一个文件的inode已经分配好,并且包含该文件所有块号的对应宿主ext2_inode_info结构也在内存中初始化好了。那么如何读这个文件?
前面讲了,ext2层,也就是第二扩展文件系统的入口函数 generic_file_read,下面我们就从它开始,进入读文件操作的Ext2层:
ssize_t generic_file_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { struct iovec local_iov = { .iov_base = buf, .iov_len = count }; struct kiocb kiocb; ssize_t ret;
init_sync_kiocb(&kiocb, filp); ret = __generic_file_aio_read(&kiocb, &local_iov, 1, ppos); if (-EIOCBQUEUED == ret) ret = wait_on_sync_kiocb(&kiocb); return ret; } |
我们看到,generic_file_read调用函数__generic_file_aio_read,来自mm/filemap.c:
1134ssize_t 1135__generic_file_aio_read(struct kiocb *iocb, const struct iovec *iov, 1136 unsigned long nr_segs, loff_t *ppos) 1137{ 1138 struct file *filp = iocb->ki_filp; 1139 ssize_t retval; 1140 unsigned long seg; 1141 size_t count; 1142 1143 count = 0; 1144 for (seg = 0; seg < nr_segs; seg++) { 1145 const struct iovec *iv = &iov[seg]; 1146 1147 /* 1148 * If any segment has a negative length, or the cumulative 1149 * length ever wraps negative then return -EINVAL. 1150 */ 1151 count += iv->iov_len; 1152 if (unlikely((ssize_t)(count|iv->iov_len) < 0)) 1153 return -EINVAL; 1154 if (access_ok(VERIFY_WRITE, iv->iov_base, iv->iov_len)) 1155 continue; 1156 if (seg == 0) 1157 return -EFAULT; 1158 nr_segs = seg; 1159 count -= iv->iov_len; /* This segment is no good */ 1160 break; 1161 } 1162 1163 /* coalesce the iovecs and go direct-to-BIO for O_DIRECT */ 1164 if (filp->f_flags & O_DIRECT) { 1165 loff_t pos = *ppos, size; 1166 struct address_space *mapping; 1167 struct inode *inode; 1168 1169 mapping = filp->f_mapping; 1170 inode = mapping->host; 1171 retval = 0; 1172 if (!count) 1173 goto out; /* skip atime */ 1174 size = i_size_read(inode); 1175 if (pos < size) { 1176 retval = generic_file_direct_IO(READ, iocb, 1177 iov, pos, nr_segs); 1178 if (retval > 0 && !is_sync_kiocb(iocb)) 1179 retval = -EIOCBQUEUED; 1180 if (retval > 0) 1181 *ppos = pos + retval; 1182 } 1183 file_accessed(filp); 1184 goto out; 1185 } 1186 1187 retval = 0; 1188 if (count) { 1189 for (seg = 0; seg < nr_segs; seg++) { 1190 read_descriptor_t desc; 1191 1192 desc.written = 0; 1193 desc.arg.buf = iov[seg].iov_base; 1194 desc.count = iov[seg].iov_len; 1195 if (desc.count == 0) 1196 continue; 1197 desc.error = 0; 1198 do_generic_file_read(filp,ppos,&desc,file_read_actor); 1199 retval += desc.written; 1200 if (desc.error) { 1201 retval = retval ?: desc.error; 1202 break; 1203 } 1204 } 1205 } 1206out: 1207 return retval; 1208} |
函数__generic_file_aio_read()是所有文件系统实现同步和异步读操作所使用的通用例程。该函数接受四个参数:kiocb描述符的地址iocb、iovec描述符数组的地址iov、数组的长度和存放文件当前指针的一个变量的地址ppos。iovec描述符数组被函数generic_file_read()调用时只有一个元素,该元素描述待接收数据的用户态缓冲区。
为什么只有一个元素呢?read()系统调用的一个叫做readv()的变体允许应用程序定义多个用户态缓冲区,从文件读出的数据分散存放在其中;__generic_file_aio_read()函数也实现这种功能,只不过从文件读出的数据将只烤贝到一个用户态缓冲区,所以只有一个元素。不过,可以想象,使用多个缓冲区虽然简单,但需要执行更多的步骤。
我们现在来说明函数__generic_file_aio_read()的操作。为简单起见,我们只针对最常见的情形,即对页高速缓存文件的系统调用read()所引发的同步操作。我们不讨论如何对错误和异常的处理。
我们看到,1154行调用access_ok()来检查iovec描述符所描述的用户态缓冲区是否有效。因为起始地址和长度已经从sys_read()系统调用得到,因此在使用前需要对它们进行检查。如何检查呢?access_ok宏实际上是__range_not_ok宏:
#define access_ok(type, addr, size) (likely(__range_not_ok(addr, size) == 0)) #define __range_not_ok(addr, size) / ({ / unsigned long flag, roksum; / __chk_user_ptr(addr); / asm("add %3,%1 ; sbb %0,%0 ; cmp %1,%4 ; sbb $0,%0" / : "=&r" (flag), "=r" (roksum) / : "1" (addr), "g" ((long)(size)), / "rm" (current_thread_info()->addr_limit.seg)); / flag; / }) |
如果参数无效,也就是检查addr到addr+ size的地址区间大于current进程的thread_info结构的addr_limit.seg的值,则返回错误代码-EFAULT。
随后1189,其实传进来的参数nr_segs是1,所以1190行只建立一个读操作描述符,也就是一个read_descriptor_t类型的数据结构。该结构存放与单个用户态缓冲相关的文件读操作的当前状态。
typedef struct { size_t written; //已经拷贝到用户态缓冲区的字节数 size_t count; //待传送的字节数 union { char __user *buf; void *data; } arg; //在用户态缓冲区中的当前位置 int error; //读操作的错误码(0表示无错误) } read_descriptor_t; |
__generic_file_aio_read函数判断本次读请求的访问方式,如果是直接I/O模式(filp->f_flags 被设置了 O_DIRECT 标志,即不经过 cache)的方式,则调用generic_file_direct_IO 函数;不过我们最常用的是 page cache 的方式,则调用1198行的do_generic_file_read 函数,传送给它文件对象指针filp、文件偏移量指针ppos,刚分配的读操作描述符的地址和函数file_read_actor()的地址:
static inline void do_generic_file_read(struct file * filp, loff_t *ppos, read_descriptor_t * desc, read_actor_t actor) { do_generic_mapping_read(filp->f_mapping, &filp->f_ra, filp, ppos, desc, actor); } |
函数 do_generic_file_read 仅仅是一个包装函数,把该文件的file结构的address_space字段传给 do_generic_mapping_read 函数:
872void do_generic_mapping_read(struct address_space *mapping, 873 struct file_ra_state *_ra, 874 struct file *filp, 875 loff_t *ppos, 876 read_descriptor_t *desc, 877 read_actor_t actor) 878{ 879 struct inode *inode = mapping->host; 880 unsigned long index; 881 unsigned long end_index; 882 unsigned long offset; 883 unsigned long last_index; 884 unsigned long next_index; 885 unsigned long prev_index; 886 loff_t isize; 887 struct page *cached_page; 888 int error; 889 struct file_ra_state ra = *_ra; 890 891 cached_page = NULL; 892 index = *ppos >> PAGE_CACHE_SHIFT; 893 next_index = index; 894 prev_index = ra.prev_page; 895 last_index = (*ppos + desc->count + PAGE_CACHE_SIZE-1) >> PAGE_CACHE_SHIFT; 896 offset = *ppos & ~PAGE_CACHE_MASK; 897 898 isize = i_size_read(inode); 899 if (!isize) 900 goto out; 901 902 end_index = (isize - 1) >> PAGE_CACHE_SHIFT; 903 for (;;) { 904 struct page *page; 905 unsigned long nr, ret; 906 < |