4.1.lseek函数介绍
(1)当我们要对1个文件进行读写时,首先需打开该文件,则我们读写的所有文件都是动态文件;动态文件在内存中就是以文件流的形式存在的。
(2)文件流很长,里面有很多个字符,我们需要确定当前正在操作的是哪个位置;GUI模式下的软件用光标来标识当前正在操作文件流的哪个位置;在动态文件中则通过文件指针(即Vnode结构体中的1个元素)来标识当前正在操作文件流哪个位置;文件指针不能被直接访问,linux系统使用lseek函数来访问该文件指针。
(3)当我们打开1个空文件时,默认情况下文件指针指向文件流的起始位置,则这时候去读写该文件时就是从文件流的起始位置开始的;write和read函数本身自带移动文件指针的功能,所以当我读写了n个字节后,文件指针会自动向后移动n位;如果需要人为的随意更改文件指针,那就只能通过lseek函数了。
(4)read和write函数都是从当前文件指针处开始操作的,所以当我们用lseek显式的将文件指针移动后,那么再去read/write时就是从移动过后的位置开始的;则当我们操作空文件时先write写了12字节,然后read时是空的,但是此时我们打开文件后发现12字节确实写进来了。
4.2.计算文件长度和构建空洞文件
(1)linux中并没有1个函数可以直接返回1个文件的长度,但是我们做项目时经常会需要知道1个文件的长度,我们自己利用lseek来写1个函数得到文件长度即可。
(2)空洞文件即该文件中有1段是空的;普通文件中间是不能有空的,因为我们write时文件指针是依次从前到后去移动的,我们不可能绕过前面直接到后面;我们打开某个文件后,用lseek往后跳过1段,再write写入1段,则会构成1个空洞文件。
(3)空洞文件方法对多线程共同操作文件是及其有用的,有时候我们创建1个很大的文件,如果从头开始依次构建时间很长,有1种思路就是将文件分为多段,然后多线程来操作,每个线程负责其中1段的写入。
4.3.重复打开同一文件读取
(1)1个进程中两次打开同一个文件,然后分别读取,1种情况是fd1和fd2分别读,1种情况是接续读;经过实验验证,证明了结果是fd1和fd2分别读。
(2)分别读说明我们使用open两次打开同一个文件时,fd1和fd2所对应的文件指针(fd->文件表指针->文件表->文件指针)是不同的2个独立的指针;文件指针是包含在文件表中的,所以可以看出linux系统的进程中不同fd对应的是不同的独立的文件表。
4.4.重复打开同一文件写入
(1)1个进程中两次打开同一个文件,然后分别写入,1种情况是fd1和fd2分别写,1种情况是接续写;经过实验验证,证明了结果是fd1和fd2分别写。
(2)正常情况下我们有时候需要分别写,有时候又需要接续写,所以这两种本身是没有好坏之分的,关键看用户需求;有时候我们希望接续写而不是分别写,办法就是在open时加O_APPEND标志即可。
4.5.O_APPEND实现原理及原子操作性说明
(1)分别写的内部原理就是2个fd拥有不同的文件指针,并且彼此只考虑自己的位移;但是O_APPEND标志可以让write和read函数内部多做1件事情,即移动自己的文件指针的同时也移动别人的文件指针(即fd1和fd2还是各自拥有1个独立的文件指针,但是这两个文件指针关联起来了,1个动了会通知另1个跟着动)。
(2)原子操作的含义即整个操作一旦开始是不会被打断的,必须直到操作结束其它代码才能得以调度运行,这就叫原子操作;每种操作系统中都有一些机制来实现原子操作,以保证那些需要原子操作的任务可以运行;O_APPEND对文件指针的影响,对文件的读写是原子的。
4.6.文件共享及实现方式
(1)文件共享即同一个文件(即同一个inode,同一个pathname)被多个独立的读写体(即多个文件描述符)同时(一个已打开尚未关闭的同时另一个去操作)操作;文件共享的意义有很多,譬如我们可以通过文件共享来实现多线程同时操作同1个大文件,以减少文件读写时间,提升效率。
(2)文件共享的核心即制造多个文件描述符指向同一个文件;常见的有3种文件共享的情况,第1种是同一个进程中多次使用open打开同一个文件;第2种是在不同进程中去分别使用open打开同一个文件(两个fd在不同的进程中,则两个fd的数字可以相同也可以不同);第3种情况是linux系统提供了dup和dup2两个API来让进程复制文件描述符;分析文件共享时的核心关注点在于确认是分别写/读还是接续写/读。
(3)当两个文件指针分别独立且互不关联->分别写/读;当两个文件指针分别独立且相互关联/两个文件指针相同->接续写/读(见图1)。
4.7.再论文件描述符
(1)文件描述符的本质是1个数字,该数字本质上是进程表中文件描述符表的1个表项,进程通过文件描述符作为index去索引查表得到文件表指针,再间接访问得到该文件对应的文件表。
(2)文件描述符是open系统调用内部由操作系统自动分配的,操作系统规定fd从0开始依次增加;fd也是有最大限制的,在linux的早期版本中(0.11)fd最大是20,所以当时1个进程最多允许打开20个文件;linux中文件描述符表是个指针数组(不是链表),其中fd是index,文件表指针是value。
(3)当我们去open时,内核会从文件描述符表中挑选1个最小的未被使用的数字给我们返回;即如果之前fd已经占满了0-9,那我们下次open得到的一定是10(但是如果上一个fd得到的是9,下一个不一定是10,这是因为可能前面更小的一个fd已经被close释放掉了)。
(4)fd中0、1、2已经默认被系统内核占用了,因此用户进程得到的最小的fd就是3了;当我们运行1个程序得到1个进程时,内部默认已打开3个文件,其对应的fd就是0、1、2;这3个文件分别叫stdin、stdout、stderr,即标准输入、标准输出、标准错误。
(5)标准输入一般对应的是键盘(0这个fd对应的是键盘的设备文件),标准输出一般是LCD显示器(1对应LCD的设备文件);printf函数其实就是默认输出到标准输出stdout上了,fpirntf函数可以指定输出到哪个文件描述符中。
4.lseek_example
/*
* 公司:XXXX
* 作者:Rston
* 博客:http://blog.csdn.net/rston
* GitHub:https://github.com/rston
* 项目:lseek函数及共享文件
* 功能:演示lseek函数的基本使用。
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
int fd = -1;