文件在Linux乃至整个计算机系统中都是一个非常重要的概念。在Linux的系统编程中文件也是作为基础出现的,理解文件和文件系统有助于我们写出更安全、更高效、可读性更强的代码。学完Linux文件操作也一年了,上周因为要给小组大一的同学总结文件操作就又过了一遍感觉收获颇丰。在此做个记录。
一、文件系统简介
1、硬盘的组成
文件是一堆有序数据的集合,文件的最终形态是存储在硬盘上,为了更好的理解文件系统,这里简单先介绍一下数据在硬盘上的存储。硬盘由盘片、磁头、主轴等组成(平面结构如下图)。盘面上排列着许多纳米级的磁粉,这些磁粉通过磁头的磁化可以排列成不同的形态。通过 磁粉的N、S两极来记录数字信息里边的0、1。
平面结构 立体结构
所有的盘片都固定在主轴上,由主轴的转动带动所有的盘片在同一时刻高速旋转。每个盘面上都有一个读写磁头,在盘面旋转的时候在盘面上读写数据。每个盘面被划分为许多同心圆,每个同心圆轨迹被称为磁道。在多个盘面的相同磁道被称为柱面,硬盘数据的是按照柱面进行的。一个柱面写完之后踩进行下一个柱面,而不是一个盘面写完再进行下一个盘面。这样做的好处是减少磁头选取柱面所带来的时间消耗,因为选取柱面是一个物理过程,需要物理传动,而选取同一柱面上的磁头只需要通过电子切换即可。
操作系统在组织文件的时候是通过扇区来组织的,扇区就是在盘面上划分出来的扇形区域,一个扇区是4k的大小。而文件系统的作用就是将磁盘上的这些区域通过底层提供的驱动等一系列接口将文件数据以一定的格式存储在磁盘上,使得我们在使用磁盘时不必先了解磁盘是如何工作的,也不必关心我们存储的文件是放在磁盘的哪个扇区、哪个盘面上。总之一句话,文件系统将用户的数据按照自己的一套规定按序写在硬盘上,减轻了硬盘使用者的负担。常见的文件系统有NTFS、FAT、EXT等文件系统,它们在文件数据的组织上各不相同,下面举两个例子来说明文件系统的不同进而引出Linux在文件系统方面的优势。
2、FAT文件系统
FAT文件系统主要应用于DOS系统和U盘中,它是一种链式的组织结构,前一个文件数据点保存了后一个数据节点的地址,通过结点找节点的方式一步步找到所有的文件数据,如下图。
、
3、EXT2文件系统简介
EXT2文件系统是Linux广泛使用的一种文件系统,它是一种索引式的文件系统,它将磁盘划分为inode和block,每个文件分配一个inode通过这个inode节点索引到真正存储文件数据的block,它的存储形式可以用下图表示。具体的EXT2文件系统细节请参考下一篇博客。
4、虚拟文件系统(VFS)
FAT文件系统和EXT2文件系统的组织形式不一样为什么还可以进行文件交换操作呢?因为Linux下有一个非常强大的东西----虚拟文件系统。虚拟文件系统的存在使得一切都变得那么简单和方便,举一个例子:Linux下可以查看Windows分区里的内容,而Windows无法查看Linuxo系统分区下的文件内容。也正是因为有了虚拟文件系统才有了Linux下“一切皆文件”的重要思想。那么VFS到底是一个什么呢?其实就是在具体的文件系统和系统调用之间进行了一层封装,使得可以利用通用的接口来操作不同的文件系统(如下图)。
二、文件操作相关系统调用
基础调用不在本文介绍范围内,比如:open、read等
1、大文件I/O操作
在32位系统下进行文件操作时有时候会出现如下错误:
出现此错误的原因还要从open函数的执行过程说起,一个open系统调用的步骤如下:
问题就出在了构建一个file结构体那一步,一个file结构大致包含如下内容
划线部分就是症结所在,off_t在系统中是由long经过typedef得到的一个类型,而在32位系统下long的长度为4字节,而它所能表示的最大范围是2G。因此超过2G的文件它就用不了了。为了解决这个问题Linux提供了几个大I/O操作调用,分别是:
int open64(const char *pathname, int flags);
或使用O_LARGEFILE参数
off64_t lseek64(int fd, off64_t offset, int whence);
int truncate64(const char *path, off_t length);
int stat64(const char *path, struct stat *buf);
相应的也提供了相对应的几个结构体:
struct stat64 //类似于stat,支持大文件
off64_t //64位类型,用于表示文件偏移量
2、pread和pwrite操作
在多线程编程中通常需要让多个进程在同一个文件中的不同地方写入数据,比如多线程下载软件就需要同时在不同地方写入内容,因为线程数据共享进程打开的文件描述符,所以不能真正移动文件偏移量,这里就可以使用pread和pwrite函数从不同偏移量开始写入文件内容,因为这两个函数并不真正移动文件的偏移量。函数声明如下:
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset); //返回成功写入的字节数,失败返回-1