linux下文件

来源:互联网 发布:百度关键词查询软件 编辑:程序博客网 时间:2024/06/06 03:33

任何Ext2分区中的第一个块从不受Ext2文件系统的管理,因为这一块是为分区的引导扇区所保留的。Ext2分区的其余部分分成块组。

超级块与组描述符被复制到每个块组中。只有块组0中所包含的超级块和组描述符才由内核使用,而其余的超级块和组描述符保持不变。

块位图必须存放在一个单独的块中。块位图用来标识一个组中块的占用和空闲情况。

设备文件,管道和套接字不需要数据块。所有必要的信息都存放在索引节点中。

索引节点与块位图并不永久保存在内存里,而是需要时从磁盘读。有了页高速缓存,最近使用的磁盘块保存在内存里。

当内核安装Ext2文件系统时,它调用ext2_fill_super()函数来为数据结构分配空间,并写入从磁盘读取的数据。函数返回后,分配的所有数据结构都保存在内存里,只有当Ext2文件系统卸载时才会被释放。

在打开文件时,要执行路径名查找。对于不再目录项高速缓存内的路径名元素,会创建一个新的目录项对象和索引节点对象。当VFS访问一个Ext2磁盘索引节点时,会创建一个ext2_indoe_info类型的索引节点描述符。

在磁盘上创建一个文件系统通常有两个阶段:第一步格式化磁盘,以使磁盘驱动程序可以读和写磁盘上的块,第二步才涉及创建文件系统。

mke2fs程序执行下列操作:1 初始化超级块和组描述符 2 对每个块组,保留存放超级块,组描述符,索引节点表以及两个位图所需要的所有磁盘块 3 把索引节点位图和每个块组都初始化为0 4 初始化每个块组的索引节点表 5 创建/root目录 6 创建lost+found目录 6 把有缺陷的块组织起来放在lost+found目录中。

文件的洞时普通文件的一部分,它是一些空字符但没有存放在磁盘的任何数据块中。

当内核分配一个数据块来保存Ext2普通文件的数据,就调用ext2_get_block()函数。如果块不存在,该函数就自动为文件分配块。每当内核在Ext2普通文件上执行读或写操作时就调用这个函数,显然这个函数只在页高速缓存内没有相应的块时才被调用。

Ext3日志所隐含的思想就是对文件系统进行的任何高级修改都分两步进行。首先,把待写块的一个副本存放在日志中,当发往日志的I/O数据传送完成时,块就被写入文件系统。当发往文件系统的I/O数据传送终止时,日志的块副本就被丢弃。

在Ext2和Ext3的文件系统中有6种元数据:超级块,块组描述符,索引节点,用于间接寻址的块,数据位图块和索引节点位图块。其他的文件系统可能使用不同的元数据。

日志记录本质上时文件系统将要发出的一个低级操作的描述。

修改文件系统的任一系统调用通畅都被划分为操纵磁盘数据结构的一系列低级操作。




系统调用read()将阻塞调用进程,直到数据被拷贝进用户态地址空间。但系统调用write不同,它在数据被拷贝到页高速缓存(延迟写)后马上结束。

O_SYNC标志只影响写操作,它将阻塞调用进程,直到数据被有效地写入磁盘。

对磁盘文件来说,read,write方法能够确定正被访问的数据所在物理块的位置,并激活块设备驱动程序开始传送。

对于大部分文件系统来说,从文件中读取一个数据页就等于在磁盘上查找所请求的数据存放在哪些块上。只要这个过程完成了,内核就可以通过向通用块层提交适当的I/O操作来填充这些页,事实上,大多数磁盘文件系统的read方法是由generic_file_aio_read()的通用函数实现的。

对磁盘的文件来说,写操作的处理相当复杂,因为文件大小可以改变,因此内核可能会分配磁盘上的一些物理块,当然,这个过程到底如何实现取决于文件系统的类型。




一个线性区可以和磁盘文件系统的普通文件的某一部分或者块设备文件相关联。

有两种类型的共享内存:共享型和私有型。私有映射的效率比共享内存的效率更高。但是私有映射页的任何写操作都会使内核停止映射该文件中的页。因此,写操作既不会改变磁盘上的文件,对访问相同文件的其他进程也不可见。

每个索引节点对象的i_mapping字段指向文件的address_space对象。每个address_space对象的page_tree字段又指向该地址空间的页的基树,i_mmap字段指向第二棵树,叫做radix优先级搜索树。

对同一个文件对象和索引节点之间的链接的建立是通过f_mapping的字段达到的。

每个线性区描述符都有一个vm_file字段,与所映射文件的文件对象链接。第一个映射单元的位置存放在线性区描述符的vm_pgoff字段,他表示以页大小为单位的偏移量。所映射的文件那部分的长度就是线性区的大小。

共享内存映射的页通常都包含在页高速缓存中,私有内存映射的页只要还没有被修改,也都包含在页高速缓存中。

内存映射机制依赖于请求调页机制。实际上,一个新建立的内存映射就是一个不包含任何页的线性区。

mmap()系统调用返回新线性区中第一个单元位置的线性地址。

<span style="font-family:SimSun;font-size:14px;">SYSCALL_DEFINE6(mmap_pgoff, unsigned long, addr, unsigned long, len,         unsigned long, prot, unsigned long, flags,         unsigned long, fd, unsigned long, pgoff) {     struct file *file = NULL;     unsigned long retval = -EBADF;      if (!(flags & MAP_ANONYMOUS)) {         audit_mmap_fd(fd, flags);         if (unlikely(flags & MAP_HUGETLB))             return -EINVAL;         file = fget(fd);     } else if (flags & MAP_HUGETLB) {         struct user_struct *user = NULL;         /*          * VM_NORESERVE is used because the reservations will be          * taken when vm_ops->mmap() is called          * A dummy user value is used because we are not locking          * memory so no accounting is necessary          */         len = ALIGN(len, huge_page_size(&default_hstate));         file = hugetlb_file_setup(HUGETLB_ANON_FILE, len, VM_NORESERVE,                         &user, HUGETLB_ANONHUGE_INODE);     }      flags &= ~(MAP_EXECUTABLE | MAP_DENYWRITE);      down_write(¤t->mm->mmap_sem);     retval = do_mmap_pgoff(file, addr, len, prot, flags, pgoff);     up_write(¤t->mm->mmap_sem); }</span>

<span style="font-family:SimSun;font-size:14px;">SYSCALL_DEFINE1(old_mmap, struct mmap_arg_struct __user *, arg) {     struct mmap_arg_struct a;      if (copy_from_user(&a, arg, sizeof(a)))         return -EFAULT;     if (a.offset & ~PAGE_MASK)         return -EINVAL;      return sys_mmap_pgoff(a.addr, a.len, a.prot, a.flags, a.fd,                   a.offset >> PAGE_SHIFT); }</span>


不需要将待撤销可写共享内存映射中的页刷新到磁盘。实际上,因为这些页仍然在页高速缓存中,因此继续起磁盘高速缓存的作用。

进程可以通过msync()调用把属于共享内存的脏数据刷新到磁盘。

为创建一个非线性内存映射,用户态进程首先以mmap()系统调用创建一个常规的共享内存映射。应用然后用remap_file_pages()来重新映射内存映射中的一些页。

因为非线性内存映射的内存页是按照相对于文件开始处的页索引存放在页高速缓存汇总,而不是按照相对于线性区开始处的索引存放的,所以非线性内存映射刷新到磁盘的方式与线性内存映射是一样的。

read()和write()系统调用不是在磁盘和用户存储器之间直接传送数据,而是分两次传送:在磁盘和内核缓冲区之间和内核缓冲区与用户存储器之间。

在每次I/O直接送中,内核对磁盘控制器进行编程,以便在自缓存的应用程序中的用户态地址空间中的页和磁盘之间直接传送数据。

磁盘高速缓存由内核拥有,不能被换出去,并且对内核态的所有进程都是可见的。

直接I/O传送应当在给定进程的用户态地址空间的页内移动数据。内核必须当心这些页是由内核态的任一进程访问的,当数据传送正在进行时不能把他们交换出去。

当自缓存的应用程序要直接访问文件时,它以O_DIRECT标志置位的方式打开文件。

大多数情况下,direct_IO方法都是__blockdev_direct_IO()函数的封装函数。这个函数相当复杂,它调用大量的辅助数据结构和函数。但是实际上它所执行的操作:对存放在相应块中要读或写的数据进行拆分,确定数据在磁盘上的位置,并添加一个或多个用于描述要进行的I/O操作的bio描述符。

通常情况下,__blockdev_direct_IO()函数并不立即返回,而是等所有的直接I/O传送都已完成才返回。因此,一旦read()和write()系统调用返回,自缓存应用程序就可以安全地访问含有文件数据的缓冲区。

"异步"就是当用户态进程调用库函数读写文件时,一旦读写操作进入队列函数就结束,甚至有可能真正的I/O数据传输还没有开始。

异步I/O可以由系统库实现而完全不需要内核支持。实际上aio_read()或aio_write()库函数克隆当前进程,让子进程调用同步的read()或write()系统调用,然后父进程结束aio_read()或aio_write()函数并继续程序的执行。

0 0
原创粉丝点击