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) / |