Linux系统通过 文件描述符表,文件表项,inode节点 这三大要素来管理文件操作。
文件描述符表:
文件描述符表是进程级别的,每个进程都有自己的文件描述符表,除了 0 ,1 , 2 这三个特殊的文件描述符外,不同进程文件描述符表中的节点代表不同的文件。比如有文件 /home/file ,在进程1中与之对应的描述符是20,但是在进程2中与之对应的描述符可能是50。但是不论是进程1的20还是进程2的50都是指向文件 /home/file的。
每个进程都有一个文件描述符表。
文件表项:
上面说到了不同进程的不同文件描述符可以指向同一个物理文件,那么不同进程是怎么互相协调的呢,如果进程1对文件只有读权限,进程2对文件有读写权限,那么这个权限信息如果写在同一个为位置,那么不就乱套了。所以需要一个中间隔离地带,这就是文件表项,每个进程的文件描述符表的每个节点都包含一个文件表项信息(实际上是有一个指向文件表项信息的指针)。
每个进程都有一个文件描述符表。每个文件描述符表的节点都指向一个文件表项。
文件表项中放什么信息呢? 其中有 文件操作权限, “当前进程” 对此文件的操作偏移量,注意引号中的内容,也就是说每个进程对某个物理文件都有自己的偏移量信息。比如进程1写到了距离文件开始100字节的位置,进程2写到了距离文件开始200字节的位置,那么这两个进程是互相不干扰的。进程1再写50字节,那么数据在文件的表现就是从 100~150字节,进程2写100字节,那么在文件中的表现就是 200~300字节被写入。
那么问题来了,每个进程对于物理文件都有自己偏移量,那么如果想让多进程写同一个文件,且都追加到尾部怎么办???最常见的例子就是日志系统,如果处理的不好,多进程写同一个文件最容易出现日志交叉。
这里要提到1个前提条件:所有的系统调用都是原子的,这个原子性即针对线程,也针对进程。
多进程场景下:
我们可以先通过lseek先把偏移量调整到 “当前时间点” 的文件尾部,然后在write写入数据。乍一看好像没问题,但是lseek 和 write是两个系统调用,因此无法保证 进程级和线程级 原子性。
这里就需要提到 O_APPEND 选项,我们在打开文件的时候可以声明此属性,这样每次 write 操作都会先 把偏移量调整到 当前尾部,然后写入数据。
因此,如果指定了O_APPEND,数据会完全有序地写入文件。
如果不指定而使用lseek+write的方法,那么可能会出现数据踩踏覆盖,而且会出现顺序颠倒,因为不同进程对于同一个文件会有不同的文件表项,也就是说这个时候会有两个偏移量值,每个进程管理自己的偏移量值。且这个偏移量值无法手工同步。
多线程场景下:
如果多线程操作同一个文件会怎样???
如果指定了O_APPEND,同样不需要手动管理。
如果不指定,因为对于同一个文件描述符在进程内部多线程是共享的,所有这些线程共享偏移量,因此不会出现数据踩踏覆盖,但是会出现数据颠倒,因为无法保证线程的调度顺序。
inode节点:
inode节点可以理解为内核文件系统维护的实际的物理文件内容,不太需要我们关心,内核会维护他的操作原子性,这也就是上面提到的 “系统调用总是原子的” 。
参考:https://blog.csdn.net/qq_28114615/article/details/94590598