欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

open/read/write/close等文件系统调用接口以及fd与FILE的比较

程序员文章站 2022-07-14 17:35:10
...

一、open/read/write/close等文件相关系统调用

  • open、read、write、close等系统函数称为无缓冲I/O(Unbuffered I/O)函数,用户程序在读写文件时既可以调用C标准I/O库函数,也可以直接调用底层的Unbuffered I/O函数.

那么在讨论Linux环境下基础IO函数前,我们先看看之间接触过的C标准I/O库函数

C标准I/O库函数

fopen(3)

调用open(2)打开制定的文件,返回一个文件描述符(一个int类型的编号),分配一个FILE结构体,其中包含该文件的描述符、I/O缓冲区和当前读写位置等信息,返回这个FILE结构体的地址。

fgetc(3)

通过传入的FILE 参数找到该文件的描述符、I/O缓冲区和当前读写位置,判断能否从I/O缓冲区读到下一个字符,如果能就直接返回该字符,否则调用read(2)把文件描述符传进去,让内核读取该文件的数据到I/O缓冲区,然后返回下一个字符。(对于C标准I/O    来说打开的文件由FILE 指针表示,对于内核来说,打开的文件由文件描述符标示,文件描述符从open系统调用获得,在使用read、write、close系统调用时都需要传文件描述符。)
- fputc(3)

判断该文件的I/O缓冲区是否有空间再存放一个字符,如果有则直接保存在I/O缓冲区中并返回,如果I/O缓冲区中已满就调用write(2),让内核把I/O缓冲区的内容写回文件。

fclose(3)

如果I/O缓冲区中还有数据没写回文件,就调用write(2)写回文件然后再调用close(2)关闭文件,释放FILE结构体和I/O缓冲区。

而open、read、write、close等系统函数称为无缓冲I/O(Unbuffered I/O)函数,用户程序在读写文件时既可以调用C标准I/O库函数,也可以直接调用底层的Unbuffered I/O函数,那个各自使用场景是什么呢?

  • 用Unbuffered I/O函数每次读写都要进内核,调一个系统调用比调一个用户控件的函数要慢很多,所以在用户程序开辟I/O缓冲区还是必要的,用C标准I/O库函数比较方便。
  • 用C标准I/O库函数要时刻注意I/O缓冲区和实际文件有可能不一致,在必要时调用fflush(3)。
  • UNIX的传统是Everything is a file(一切皆文件),I/O函数不仅可以读写文件还可以读写设备。在读写设备时通常是不希望有缓冲的。比如网络设置的读写就希望是实时读写。
  • C标准库函数是C标准的一部分,而Unbuffered I/O函数是UNIX标准的一部分。只有在UNXI平台上才能用Unbuffered I/O函数,windows上不行。

Unbuffered I/O函数open/read/write/close

- open函数

open/read/write/close等文件系统调用接口以及fd与FILE的比较
注释:
(1)函数说明:用来打开一个已经存在的文件或者创建一个普通文件
(2)参数解释:
pathname:要打开或创建的目标文件
flags:打开文件时,可以传入多个参数选项,用下面的一个或多个进行“或”运算,构成flags;
参数:O_RDONLY—只读打开、 Q_WRONLY—只写打开、O_RDWR—读、写打开 ;这三个变量只能指定一个
O_CREAT—若文件不存在,则创建它。需要使用mode(文件权限标志)选项,来指明新文件的访问权限
O_APPEND—追加写
(3)返回值:成功返回新打开文件的描述符,失败则返回-1

文件权限标志
open/read/write/close等文件系统调用接口以及fd与FILE的比较
文件权限标志也可以使用加权数字表示,这组数字被称为umask变量,它的类型是mode_t,是一个无符号八进制数。umask变量的定义方法如表:
open/read/write/close等文件系统调用接口以及fd与FILE的比较

2、read函数

open/read/write/close等文件系统调用接口以及fd与FILE的比较

注释:
(1)函数说明:是从 fd所描述的打开文件中读取 buf所指缓冲区中的 n个字节。
(2)参数说明
fd:文件描述符,用来指向要操作的文件的文件结构体
buf:一块内存空间
count:请求读取的字节数,读上来的数据保存在缓冲区buf中,同时文件的当前读写位置向后移。
ssize_t:有符号整型
(3)返回值:成功返回读取的字节数,出错返回-1并设置errno,如果在调用read之前已到达文件末尾,则这次read返回0。
注意:读操作从文件的当前位移量处开始,在成功返回之前,该位移量增加实际读的字数

3、write函数

open/read/write/close等文件系统调用接口以及fd与FILE的比较

注释:
(1)函数说明:将 buf所指向的缓冲区的 n字节写入 fd 所描述的打开文件中
(2)参数说明:
fd:文件指针
buf:写入的数据保存在缓冲区buf中,同时文件的当前读写位置向后移
count:请求写入的字节数
(3)返回值:成功返回写入的字节数,出错返回-1并设置errno写常规文件时,write的返回值通常等于请求写的字节数count,而向终端或者网络写则不一定。

4、close函数

open/read/write/close等文件系统调用接口以及fd与FILE的比较

注释:
(1)函数说明:关闭指定文件
(2)参数解释:fd—文件描述符,用来指向要操作的文件的文件结构体
(3)返回值:若成功返回0,出错返回-1;
注意:关闭一个文件时也释放该进程加在该文件上的所有记录锁,当一个进程终止时,它所有的打开文件都由内核自动关闭。

二、对比fd与FILE结构体

1、文件描述符fd

每个进程在linux内核中都有一个task_struct结构体来维护进程相关的信息,称为进程描述符(Process Descriptor),它在操作系统理论中称为进程控制块(PCB, Process Control Block)。task_struct中有一个指针指向files_struct结构体,称为文件描述符表,其中每个表项包含一个指向已打开的文件的指针,如下图所示:open/read/write/close等文件系统调用接口以及fd与FILE的比较

  • 用户程序不能直接访问内核中的文件描述符表,而只能使用文件描述符表的索引(即0、1、2、3这些数字),这些索引就称为文件描述符,用int型变量保存。文件描述符在形式上是一个非负整数。实际上,就是这样一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当调用open打开一个文件或创建一个新文件时,内核分配一个文件描述符并返回给用户程序,该文件描述符边项中的指针指向新打开的文件。当读写文件时,用户程序把文件描述符传给read或write,内核根据文件描述符找到相应的表项,再通过表项中的指针找到相应的文件。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。

     程序启动时会自动打开三个文件:标准输入、标准输出和标准错误输出。在C标准中分别用FILE *指针stdin、stdout、stderr表示。这三个文件的描述符分别是0、1、2,保存在相应的FILE结构体中。头文件unistd.h中有如下的宏定义来表示这三个文件描述符:
     

#define STDIN_FILENO  0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2
  • (1)习惯上,标准输入(standard input)的文件描述符是 0,标准输出(standard output)是 1,标准错误(standard error)是 2。
  • (2)(2)0,1,2对应的物理设备一一般是:键盘,显示器,显示器。

查看当前最大打开文件描述符数
open/read/write/close等文件系统调用接口以及fd与FILE的比较

临时修改当前用户环境下的最大打开文件描述符数
open/read/write/close等文件系统调用接口以及fd与FILE的比较

2、FILE结构体

struct FILE
{
    char *_ptr;//文件输入的下一个位置
    int _cnt;//当前缓冲区的相对位置
    char *_base;//指基础位置(文件的起始位置)
    int _flag;//文件标志
    int _file;//文件的有效性验证
    int _charbuf;//检查缓冲区状况,如果缓冲区则不读取
    int _bufsiz;//文件的大小
    char *_tmpfname;//临时文件名
};

3、(FILE*)文件指针

文件指针指向进程用户区中一个被叫做FILE结构的结构数据。FILE结构包括一个缓冲区和一个文件描述符 。而文件描述符是文件描述符表的一个索引,因此从某种意义上说文件指针就是句柄的句柄(在Windows系统上,文件描述符被称作文件句柄)。

4、文件描述符与文件指针的区别

(1)文件描述符fd只是一个非负整数,在open时产生,起到一个索引的作用,进程通过PCB中的文件描述符表找到该fd所指向的文件指针file。
(2)open函数返回的是一个文件描述符,内核会在每个进程空间中维护一个文件描述符表,所有打开的文件都将通过此表中的文件描述符来引用;
(3)fopen函数返回的是一个文件指针(FILE*),FILE结构是包含有文件描述符的,fopen可以看做是open(fd直接操作的系统调用)的封装,它的优点是带有I/O缓存

open/read/write/close等文件系统调用接口以及fd与FILE的比较

可以看到,C库的fopen()函数就是返回打开文件指针;如果操作失败,返回空指针null。