在打开了文件以后 ,或者说建立起进程与文件之间的连接之后,才能对文件进行读/写;为了提高效率,linux对文件的读写是带缓冲的;所谓缓冲,是指系统为最近刚读/写过的文件内容在内核中保留的一份副本,以便当再次需要已经在缓冲存储副本中的内容时,就不必再临时从设备里读了,需要写的时候可以先写到副本中,待系统较为空闲时再从副本写入;在对进程的系统中,由于同一个文件可能为多个进程所共享,缓冲的作用就更显著了;再综合考虑了各层的缓冲的特点,选择了在文件系统层来进行缓冲,主要有file结构,dentry结构,inode结构;
(1)首先是file结构,一个file结构代表目标文件的一个上下文,不同的进程可以在同一个文件上建立不同的上下文,就是同一个进程也可以通过打开同一个文件多次建立起多个上下文;如果在file结构设置一个缓冲队列,那么缓冲区的虽然贴近特定上下文的使用者,却不便于多个进程共享,甚至不便于同一个进程打开的不同上下文共享;
(2)dentry结构,它不属于一个上下文,也不属于某一个进程,可以为所有的进程和上下文共享,可是,dnetry结构与目标文件并不是一对一的关系;多个dentry代表着一个inode结构;
(3)在inode建立起缓冲区队列是在合适不过的了;首先,inode结构与文件是一对一的关系,即使一个文件有多个路径名,最后也是对应到一个inode结构;再说一个文件的内容是不能共享的,在同一时间内,设备上的每一个记录块只能属于至多一个文件,将载有同一个文件内容的缓冲区放在inode是很自然的事;在inode有一个i_mapping指针,它指向address_space结构,缓冲区队列就在这个数据结构中;
挂在缓冲区队列中是内存页面,也就是说文件的内容并不是以记录块为单位缓冲的,而是以页面为单位进行缓冲的;如果一个记录块1K字节,那么一个页面相当于4个记录块;这是为了将文件内容的缓冲和文件的内存映射相结合起来的一种设计;但是对于设备层来说,最自然的是想以记录块为单位的缓冲,因为设备的读写都是以记录块为单位的;预读机制可以解决这个问题;但是另一方面,如果无论是页面缓冲还是记录块缓冲,控制信息和附加信息(比如本身的page结构,缓冲区头部buffer_head)都是游离在这些页面之外的;在一个缓冲区页面中,在文件层通过一个page结构将它挂入缓冲区队列,并且同时又可以通过各个进程的页面映射表映射这些内存空间;而在设备层则又通过若干(通常是4个)buffer_head挂入其所在设备的缓冲区队列;
由于预读机制,file结构实际上是要维持两个上下文的,一个当前位置的f_pos代表真正的读写上下文,而另一个是预读的上下文,即f_reada,f_ramax等;预读机制,读分为同步的,异步的;而写由于kflushd,一定异步的;
首先来看写;
(1)在sys_write()中,在file是一个上下文,有文件的写的位置;首先根据fd找到该已打开文件的file结构;然后,要通过file中f_mode的模式是否是FMODE_WRITE,然后使用locks_verify_area()对f_pos和接下来的count字节对写操作加上强制锁(由内核控制的),然后对inode结构中i_flock一一比对;
(2)然后就是针对普通文件的写操作了,对于ext2,那就是ext2_file_operations,具体的就是generic_file_write();在inode中有个指针i_mapping,指向一个address_space结构(结构中的pages用来维持缓冲区页面队列,如果文件映射到某些进程的用户空间,指针i_mmap指向一串虚存区间,a_pos指向一个address_space_operations数据结构,这个结