LINUX(UNIX)文件I/O学习
一、文件描述符
文件描述符是当打开或创建一个文件时,由内核向相应进程返回的标识(非负数,为int类型)。相应进程可以引用该标识对打开或创建的文件进行操作。
在unistd.h中定义了3个文件描述符:
标准输入 STDIN_FILENO 0
标准输出 STDOUT_FILENO 1
标准错误 STDERR_FILENO 3
二、打开/创建文件
通常用open、creat函数来打开和创建一个文件
1 #include <sys/types.h> 2 #include <sys/stat.h> 3 #include <fcntl.h> 4 5 //打开一个文件 6 int open(const char *pathname, int flags); 7 返回:若成功为文件描述符,若出错为- 1 8 //创建一个文件 9 int open(const char *pathname, int flags, mode_t mode); 10 返回:若成功为文件描述符,若出错为- 1 11 //以只写的方式创建一个文件 12 int creat(const char *pathname, mode_t mode); 13 返回:若成功为文件描述符,若出错为- 1
open函数可以用来打开一个文件,同时也可以用来创建一个文件,返回文件描述符,并且该文件描述符一定为当前未用的最小的。
pathname:将要打开或将要创建文件的名字
flags:可以是一下的值或运算后的结果(注:前三个只能选一个)
O_RDONLY --以只读的方式打开文件
O_WRONLY --以只写的方式打开文件
O_RDWR --以可读可写的方式打开文件
O_APPEND --以在文件尾追加的方式打开文件
O_CREAR --若文件不存在则创建它,并且指明第3个参数mode
O_EXCL --若同时指明了O_CREAT时,若文件已存在则报错,若不存在则创建文件(检测与创建绑定在一起为原子操作)
O_TRUNC --如果此文件存在,而且为自读或只写打开,则将其长度截断为0
O_NOCTTY --如果p a t h n a m e指的是终端设备,则不将此设备分配作为此进程的控制终端。
O_NONBLOCK -- 如果p a t h n a m e指的是一个F I F O、一个块特殊文件或一个字符特殊文件,则此选择项为此文件的本次打开操作和后续的 I / O操作设置非阻塞方式。
O_SYNC --使每次w r i t e都等到物理I / O操作完成。
O_SYNC --选择项不是P O S I X . 1的组成部分,但S V 保证在一个给定的描述符
mode:
对于creat函数,由于creat函数是已只写方式创建一个文件所以
creat(pathname, mode)等于open(pathname, O_WRONLY | O_CREAT | O_TRUNC, mode)
三、关闭文件
用close函数关闭文件
1 #include <unistd.h> 2 3 int close(int fd);
指定相应的文件描述符就可以关闭打开的文件
四、当前文件位移量(针对打开过的文件)
打开的文件都有一个与该文件相关联的当前文件位置,当进行I/O操作时,就从该位置开始。lseek用来重新定位文件中的当前位置(也可以返回当前文件的位置)。
由于lseek的以前的返回类型以及offset都是long类型,因此为lseek
1 #include <sys/types.h> 2 #include <unistd.h> 3 4 off_t lseek(int fd, off_t offset, int whence);
fd:相应文件描述符
offset:在文件中相对与whence的位移量
whence:标识在文件中的特定位置,有3个值
SEEK_SET:指文件开头
SEEK_CUR:指当前文件的位置
SEEK_END:指文件的尾
lseek(fd, 0, SEEK_CUR)用来返回文件的当前位置。
对与刚刚打开的文件一般是返回0,当以O_APPEND打开时,返回的为非零,为文件尾。
如果用lseek函数修改的当前位移量超过文件的当前长度时,该文件会形成文件空洞,文件空洞是不占用磁盘空间的,但是当读文件时,会将文件空洞读作0。
五、I/O操作
I/O操作包括对文件的读写操作,因此用到了read,write函数
1 #include <unistd.h> 2 3 ssize_t read(int fd, void *buf, size_t count); 4 5 ssize_t write(int fd, const void *buf, size_t count);
fd:相应文件的文件描述符
buf:读/写的缓冲区
count:对read,为要读的字节数,对write,为buf的大小
对read:
当读普通文件时,在读到要求字节数之前已到达了文件尾端。例如,若在到达文件尾端之前还有3 个字节,而要求读 10个字节,则read返回3,下一次再调用re ad时,它将返回0 (文件尾端)。也就是说当读到文件尾是,最后一次读一定是返回0。
当从终端设备读时,通常一次最多读一行。
当从网络读时,网络中的缓冲机构可能造成返回值小于所要求读的字节数。
某些面向记录的设备,例如磁带,一次最多返回一个记录。
对write:
write出错原因:
1.磁盘满
2.超出了文件长度的限制
write,read后都会修改文件的当前位移
六、内核中与文件相关的3个数据结构
在操作系统内核中维护这文件的相关信息,他们的关系如下:
操作系统为维护进程,设定了一个进程表,每一个进程在进程表中都单独占一个记录项
每个进程为维护打开的文件,设定一个文件描述符表(1),没一个文件描述符在该表中单独占一个记录,并且一个记录由两项组成:
1.文件描述符标志
2.指向文件表的指针
文件表中又包括了3项
1.文件状态标志(O_RDONLY | O_WRONLY...)
2.文件的当前位移(参见四、文件的当前位移量)
3.指向v节点的指针(v节点,文件的虚拟文件系统)
v节点包括的信息
1.v节点的信息
2.i节点的信息
3.文件的长度
当两个相互独立的进程打开同一个文件时,他们各自会分别获得一个文件描述符表,一个文件表,但是文件表中的v节点指针会指向同一个v节点。
此时当值进行文件读时,两个进程各不相干,都能正常进行,这是因为两个进程都有一个独立的文件表(包含一个文件当前的位移量,这个仅属于当前进程)。当进行文件写时,可能发生问题,例如,当打开文件时两个进程(A,B)的都将文件的当前位移量置为0,此时进程A先对该文件写如1000个字节,写过后,更改文件的当前位移量为1000,以及文件的长度为1000,此时进程B对该文件进行写(注意此时对B进程来说文件的当前位移量并没有变还是0),此时B进程写过1000个字节后恰好覆盖了A进程写入的,造成了写覆盖。
当然这个问题是可以解决的,只要在open文件时添加O_APPEND就能解决,因为这个标志规定将这两个操作(1.将文件表的文件当前位移量设为文件尾2.对文件写)作为原子操作,即每次写文件时,都会先修改文件的当前位移量为文件尾。这就是进程件文件共享的一个例子。
另外原子操作指的是将多个步骤的操作看成一个步骤,要么全做,要么不做。
七、改变打开文件的性质
要该变文的性质要用到fcntl,或者dup,dup2)(fcntl可以替代dup,dup2)
1 #include <unistd.h> 2 #include <fcntl.h> 3 4 int fcntl(int fd, int cmd, ... /* arg */ );
fd:相应文件描述符
cmd:用来确定fcntl的功能可以为下面的值
F_DUPFD:复制文件描述符
F_GETFD/F_SETFD:获得/设置文件描述符标志
F_GETFL/F_SETFL:获得/设置文件状态标志
还有其他,不过先学这几个
...:在ANSCI C中表示可以更多的参数
fcntl的功能:
正如cmd列出的,已个标志代表一个功能
1.复制文件描述符
复制描述符可以用dup,dup2,fcntl。
复制文件描述符后,使得新文件描述符和原有的文件描述符共享一个文件表,但是各自独占已个文件描述符表
1 #include <unistd.h> 2 3 int dup(int oldfd); 4 int dup2(int oldfd, int newfd);
newfd = dup(oldefd); 等同于 newfd = fcntl(oldfd, F_DUPFD);
dup2(oldfd, newfd); 等同于
close(newfd);
fcntl(oldfd, F_DUPFD, newfd);
只是dup2为原子操作
dup2操作是,制定newfd为新的文件描述符,如果newfd已经打开了则先关闭在复制。
2.获得/设置文件描述符标志(目前仅仅有已一个标志位close_on_exec)及FD_CLOEXEC
用dup,dup2,fcntl复制文件描述符时,该值会被清除。
设置F_GETFD会获得FD_CLOEXEC的值(以函数返回值返回)
设置F_SETFD会将...参数的值设置给FD_CLOEXEC位(用到第3个参数)
3.获得/设置文件状态标志
设置F_GERFL会以函数返回值的形式返回文件的状态标志
由于O_RDONLY,O_WRONLY,O_RDWR只能设置一个,所以要先将返回值与O_ACCMODE相与再来判断是3个值的哪一个
而其他标志这不需要
设置F_SETFL会将函数的第三个参数的值赋给文件的状态标志