好了,我们知道了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
907 /* nr is the maximum number of bytes to copy from this page */
908 nr = PAGE_CACHE_SIZE;
909 if (index >= end_index) {
910 if (index > end_index)
911 goto out;
912 nr = ((isize - 1) & ~PAGE_CACHE_MASK) + 1;
913 if (nr <= offset) {
914 goto out;
915 }
916 }
917 nr = nr - offset;
918
919 cond_resched();
920 if (index == next_index)
921 next_index = page_cache_readahead(mapping, &ra, filp,
922 index, last_index - index);
923
924find_page:
925 page = find_get_page(mapping, index);
926 if (unlikely(page == NULL)) {
927 handle_ra_miss(mapping, &ra, index);
928 goto no_cached_page;
929 }
930 if (!PageUptodate(page))
931 goto page_not_up_to_date;
932page_ok:
933
934 /* If users can be writing to this page using arbitrary
935 * virtual addresses, take care about potential aliasing
936 * before reading the page on the kernel side.
937 */
938 if (mapping_writably_mapped(mapping))
939 flush_dcache_page(page);
940
941 /*
942 * When (part of) the same page is read multiple times
943 * in succession, only mark it as accessed the first time.
944 */
945 if (prev_index != index)
946 mark_page_accessed(page);
947 prev_index = index;
948
949 /*
950 * Ok, we have the page, and it's up-to-date, so
951 * now we can copy it to user space...
952 *
953 * The actor routine returns how many bytes were actually used..
954 * NOTE! This may not be the same as how much of a user buffer
955 * we filled up (we may be padding etc), so we can only update
956 * "pos" here (the actor routine has to update the user buffer
957 * pointers and the remaining count).
958 */
959 ret = actor(desc, page, offset, nr);
960 offset += ret;
961 index += offset >> PAGE_CACHE_SHIFT;
962 offset &= ~PAGE_CACHE_MASK;
963
964 page_cache_release(page);
965 if (ret == nr && desc->count)
966 continue;
967 goto out;
968
969page_not_up_to_date:
970 /* Get exclusive access to the page ... */
971 lock_page(page);
972
973 /* Did it get unhashed before we got the lock? */
974 if (!page->mapping) {
975 unlock_page(page);
976 page_cache_release(page);
977 continue;
978 }
979
980 /* Did somebody else fill it already? */
981 if (PageUptodate(page)) {
982 unlock_page(page);
983 goto page_ok;
984 }
985
986readpage:
987 /* Start the actual read. The read will unlock the page. */
988 error = mapping->a_ops->readpage(filp, page);
989
990 if (unlikely(error)) {
991 if (error == AOP_TRUNCATED_PAGE) {
992 page_cache_release(page);
993 goto find_page;
994 }
995 goto readpage_error;
996 }
997
998 if (!PageUptodate(page)) {
999 lock_page(page);
1000 if (!PageUptodate(page)) {
1001 if (page->mapping == NULL) {
1002 /*
1003 * invalidate_inode_pages got it
1004 */
1005 unlock_page(page);
1006 page_cache_release(page);
1007 goto find_page;
1008 }
1009 unlock_page(page);
1010 error = -EIO;
1011 shrink_readahead_size_eio(filp, &ra);
1012 goto readpage_error;
1013 }
1014 unlock_page(page);
1015 }
1016
1017 /*
1018 * i_size must be checked after we have done ->readpage.
1019 *
1020 * Checking i_size after the readpage allows us to calculate
1021 * the correct value for "nr", which means the zero-filled
1022 * part of the page is not copied back to userspace (unless
1023 * another truncate extends the file - this is desired though).
1024 */
1025 isize = i_size_read(inode);
1026 end_index = (isize - 1) >> PAGE_CACHE_SHIFT;
1027 if (unlikely(!isize || index > end_index)) {
1028 page_cache_release(page);
1029 goto out;
1030 }
1031
1032 /* nr is the maximum number of bytes to copy from this page */
1033 nr = PAGE_CACHE_SIZE;
1034 if (index == end_index) {
1035 nr = ((isize - 1) & ~PAGE_CACHE_MASK) + 1;
1036 if (nr <= offset) {
1037 page_cache_release(page);
1038 goto out;
1039 }
1040 }
1041 nr = nr - offset;
1042 goto page_ok;
1043
1044readpage_error:
1045 /* UHHUH! A synchronous read error occurred. Report it */
1046 desc->error = error;
1047 page_cache_release(page);
1048 goto out;
1049
1050no_cached_page:
1051 /*
1052 * Ok, it wasn't cached, so we need to create a new
1053 * page..
1054 */
1055 if (!cached_page) {
1056 cached_page = page_cache_alloc_cold(mapping);
1057 if (!cached_page) {
1058 desc->error = -ENOMEM;
1059 goto out;
1060 }
1061 }
1062 error = add_to_page_cache_lru(cached_page, mapping,
1063 index, GFP_KERNEL);
1064 if (error) {
1065 if (error == -EEXIST)
1066 goto find_page;
1067 desc->error = error;
1068 goto out;
1069 }
1070 page = cached_page;
1071 cached_page = NULL;
1072 goto readpage;
1073 }
1074
1075out:
1076 *_ra = ra;
1077
1078 *ppos = ((loff_t) index << PAGE_CACHE_SHIFT) + offset;
1079 if (cached_page)
1080 page_cache_release(cached_page);
1081 if (filp)
1082 file_accessed(filp);
1083}
要看懂这个函数的代码,首先要简单地介绍一下文件高速缓存的背景知识。前面我们在讲解Ext2超级块对象和索引节点对象的时候,涉及到了一点高速缓存的知识,其实页高速缓存的类型分为两种,磁盘页高速缓存和文件页高速缓存。
磁盘页高速缓存是,把缓冲区页的描述符插人基树,树根是与块设备相关的特殊bdev文件系统中索引节点的address_space对象。前面我们已经讲过了,这里再强调一下,这种缓冲区页必须满足很强的约束条件,就是所有的块缓冲区涉及的块必须是在块设备上相邻存放的。
而文件页高速缓存却不同,是把缓冲区页的描述符插入普通文件的inode对应address_space的基树。文件数据被分割为一个个以 4k字节,也就是一个页面大小为单元的数据块,这些数据块(页)被组织成一个基树( radix 树)。树中所有叶子节点为一个个页框结构(struct page),表示了用于缓存该文件的每一个页。在叶子层最左端的第一个页保存着该文件的前4096 个字节,接下来的页保存着文件第二个4096 个字节,严格地依次类推。
树中的所有中间节点为组织节点,指示某一地址上的数据所在的页。此树的层次可以从0 层到6 层,所支持的文件大小从 0 字节到16 T 个字节。树的根节点指针可以从和文件相关的 address_space 对象(该对象保存在和文件关联的inode 对象中)中取得(更多关于 page cache 的结构内容请参见博客“磁盘高速缓存”
http://blog.csdn.net/yunsongice/archive/2010/08/23/5833154.aspx)。
言归正传,函数 do_generic_mapping_read首先879行获得地址空间对象的所有者,即索引节点对象,它将拥有填充了文件数据的页面。它的地址存放在address_space对象的host字段中。千万要注意,如果所读文件是块设备文件,这也是一般情况下,我们读取SCSI磁盘上的文件,那么所有者就不是由filp->f_dentry->d_inode所指向的索引节点对象,而是bdev特殊文件系统中的索引节点对象。
892行,把文件看作细分的数据页(每页4096字节),并从文件指针*ppos导出第一个请求字节所在页的逻辑号,即地址空间中的页索引,并把它存放在index局部变量中。也把第一个请求字节在页内的偏移量存放在offset局部变量中。
904行开始一个循环来读入包含请求字节的所有页,要读数据的字节数存放在read_descriptor_t描述符的count字段中。在一次单独的循环期间,函数通过执行下列的子步骤来传送一个数据页:
909~916行,如果index*4096+offset超过存放在索引节点对象的i_size字段中的文件大小,则从循环退出。919行调用cond_resched()来检查当前进程的标志TIF_NEED_RESCHED。如果该标志置位,则调用函数schedule()。如果有预读的页,921行读入这些页面。
注意到函数中还有一个内部变量next_index,在函数内等于index,所以921行page_cache_readahead函数肯定会执行,用来预读一些页面,取决于传递进来的file参数的f_ra字段。后面章节我们会详细讲解文件的预读。这里就略过。
函数中还有一个内部变量last_index,这个last_index指向需要读的所有页面集合的最后一个页框号(我们需要读的页面范围是last_index - index)。那么接下来的工作就是从index到last_index一页一页的读入到用户空间。
925行调用find_get_page(),并传入指向address_space对象的指针及索引值作为参数;它将查找页高速缓存以找到包含所请求数据的页描述符(如果有的话)。我们看到,根据文件当前的读写位置,在页高速缓存中中找到缓存请求数据的 page。这个page的index的值是文件当前读写位置ppos右移PAGE_CACHE_SHIFT。这个宏等于PAGE_SHIFT,也就是12,正好反映了,ppos在磁盘中,距离文件开头所使用了页面个数。
如果find_get_page()返回NULL指针,则所请求的页不在页高速缓存中。如果这样,927行调用handle_ra_miss()来调整预读系统的参数。然后走到1050行的no_cached_page标号下,调用page_cache_alloc_cold使用伙伴系统alloc_pages分配一个新页;调用add_to_page_cache()插入该新页的页描述符到address_space的基树page_tree中,对应位置为局部变量index,即对应磁盘地址空间中的页索引。记住该函数将新页的PG_locked标志置位。调用add_to_page_cache_lru()将该新页描述符插入到LRU链表。然后跳到readpage开始读文件数据。
如果find_get_page()返回该页,说明页已经位于页高速缓存中。通过930行PageUptodate宏检查标志PG_uptodate:如果置位,则页所存数据是最新的,因此无需从磁盘读数据,就到page_ok标号。如果没有置位,说明页中的数据是无效的,因此必须从磁盘读取,则跳到page_not_up_to_date标号处,通过971行调用lock_page()函数获取对页的互斥访问。
现在页已由当前进程锁定。然而,另一个进程也许会在上一步之前已从页高速缓存中删除该页,那么,它就要检查页描述符的mapping字段是否为NULL。在这中情形下,它将调用975行的unlock_page()来解锁页,976行的page_cache_release宏减少它的引用计数(find_get_page()所增加的计数),并跳回924行的find_page标号处来重读同一页。
如果函数已运行至980行,说明页已被锁定且在页高速缓存中。981行再次检查标志PG_uptodate,因为另一个内核控制路径可能已经完成上面的必要读操作。如果标志置位,则调用unlock_page()并跳至page_ok标号处来跳过读操做。
现在真正的I/O操作可以开始了,988行调用文件的address_space对象之readpage方法。传递给它的是对应的待填充页描述符page和文件对象file,函数会负责激活磁盘到页之间的I/O数据传输。我们稍后再讨论该函数对普通文件与块设备文件都会做些什么。
来到998行,如果标志PG_uptodate还没有置位,则它会等待直到调用lock_page()函数后页被有效读入。该页在前面被锁定,一旦读操作完成就被解锁。因此当前进程在I/O数据传输完成时才停止睡眠。
1027行,如果index超出文件包含的页数(该数是通过将inode对象的i_size字段的值除以4096得到的),那么它将减少页的引用计数器,并跳出循环。这种情况发生在这个正被本进程读的文件同时有其他进程正在删减它的时候。
1033-1041行将应被拷入用户态缓冲区的页中的字节数存放在局部变量nr中。这个值应该等于页的大小(4096字节),除非offset非0(这只发生在读请求书的首尾页时)或请求数据不全在该文件中。
又回到page_ok标号,这回已经prev_index不等于index了,所以调用mark_page_accessed()将标志PG_referenced或PG_active置位,从而表示该页正被访问并且不应该被换出。如果同一文件(或它的一部分)在do_generic_file_read()的后续执行中要读几次,那么这个步骤只在第一次读时执行。
现在到了把页中的数据拷贝到用户态缓冲区的时候了。为了这么做,do_generic_file_read()的959行调用file_read_actor()函数,该函数的地址作为参数传递。file_read_actor()执行下列步骤:
i. 调用kmap(),该函数为处于高端内存中的页建立永久的内核映射。
ii. 调用__copy_to_user(),该函数把页中的数据拷贝到用户态地址空间。注意,这个操作在访问用户态地址空间时如果有缺页异常将会阻塞进程。
iii. 调用kunmap()来释放页的任一永久内核映射。
iv. 更新read_descriptor_t描述符的count、 written和buf字段。
960行,根据传入用户态缓冲区的有效字节数来更新局部变量index和count。一般情况下,如果页的最后一个字节已拷贝到用户态缓冲区,那么index的值加1而offset的值清0;否则,index的值不变而offset的值被设为已拷贝到用户态缓冲区的字节数。
964行减少页描述符的引用计数器。965行判断:如果read_descriptor_t描述符的count字段不为0,那么文件中还有其他数据要读,继续循环来读文件中的下一页数据。
至此,所有请求的或者说可以读到的数据已读完。到out;标号,1076行更新预读数据结构filp->f_ra来标记数据已被顺序从文件读入。1078行把index*4096+offset值赋给*ppos,从而保存以后调用read()和write()进行顺序访问的位置。
最后调用file_accessed(filp)把当前时间存放在文件的索引节点对象的i_atime字段中,并把它标记为脏后返回。
到此,我们知道:当页上的数据不是最新的时候,该函数调用mapping->a_ops->readpage 所指向的函数(变量mapping为inode 对象中的address_space对象),那么这个函数到底是什么呢?
前面我们学习了,ext2_fill_super函数初始化超级快后,ext2_read_inode就是ext2超级块的s_op->read_inode具体实现函数。当需要建立一个文件的inode时,该函数会调用ext2_get_inode函数,从一个页高速缓存中读入一个磁盘索引节点结构ext2_inode,然后初始化VFS的inode。其中,索引节点inode->i_mapping->a_ops 被初始化成了ext2_aops全局变量。
其实,address_space 对象是嵌入在 inode 对象之中的,那么不难想象,address_space 对象成员 a_ops 的初始化工作将会在初始化 inode 对象时进行。ext2_aops全局变量的readpage字段都是ext2_readpage,最终调用ext2_readpage 函数处理读数据请求。到此为止, ext2 文件系统层的工作结束。