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会将函数的第三个参数的值赋给文件的状态标志

原文地址:https://www.cnblogs.com/wmllz/p/5023782.html