在上一篇博客之中,我们对C语言中的文件基本操作进行了一个简答的回顾,并通过vim编写代码对库函数进行了简单实现。
那么这一篇博客我们我们将从系统调用接口出发,来对文件IO进行一个介绍。
目录
1.open
1.1参数介绍
1.2返回值
2.write
2.1参数介绍
2.2返回值
3.read
3.1参数介绍
3.2返回值
4.lseek
4.1参数介绍
4.2返回值
5.close
5.1参数介绍
6.实例
7.库函数和系统调用接口
int open(char *pathname, int flag, int mode);
其中,参数pathname则是需要打开文件的路径名;flag则是对应的文件打开方式,打开方式的选择代表着对文件的不同操作选项;
flag存在3个必选项(只能选择其一,不能同时使用):O_RDONLY、O_WRONLY、O_RDWR,分别对应文件操作中的:只读、只写和可读可写。
值得一提的是:这3个选项的本质是一个宏(底层实现中的定义),对应3个数字,分别为:00、01和02(16进制)。所以这3者只能选择其一,不能同时使用,因为加以“或”来使用,本质上其对应的数字并没有发生改变,还可能会变为未知值(接口无法识别)。
flag还存在可选项:O_CREAT--文件不存在则创建;O_TRUNC--截断文件,即丢弃文件原有数据;O_APPEND--追加写,总是将新加入数据写道文件末尾。
例子:当我们要实现库函数中提到的w+打开方式时,通过系统调用接口open来实现的话,其中打开方式选项的内容即为:O_RDWR | O_CREAT | O_TRUNC。
对于open中的第3个参数mode,当O_CREAT被使用的时候,就一定需要使用mode来设定被创建文件的权限。值得注意的时:当我们通过数字来对创建文件赋权时,一定要注意赋权数字首位的0不能省略,因为在常用权限位之前还存在一些特殊的权限位,不过我们很少加以关注。
接口执行成功则返回一个文件描述符(非负整数)作为文件的操作句柄;失败则返回-1。
ssize_t write(int fd, char *buf, size_t len);
fd--open接口打开文件时返回的文件操作句柄;buf--要写入文件数据所在的空间首地址;len--要写入文件数据的长度(以字节为单位)。
写入数据成功则返回写入的数据长度(写入数据长度为0时,返回0),失败则返回-1。
ssize_t read(int fd, char *buf, size_t len);
fd--open接口打开文件时返回的文件操作句柄,buf--一块空间的首地址,用于存放读取到的文件内容;len--需要读取的数据长度(len的长度不能大于buf的大小,防止越界)。
读取数据成功则返回实际读取到的数据长度(字节为单位),如果返回0,则说明光标位置(读写位置)位于文件末尾;失败则返回-1。
off_t lseek(int fd, off_t offset, int wherence);
fd--open接口打开文件时返回的文件操作句柄;offset--光标位置(读写位置)偏移量;wherence--偏移量的起始位置。
wherence参数对应3个关键字:SEEK_SET--文件起始位置;SEEK_CUR--光标(读写)所在位置;SEEK_END--文件末尾位置。
当前跳转后,读写位置相当于文件起始位置的偏移量,根据这个特性,我们可以使用该接口让读写位置跳转到文件末尾,如此lseek的返回值便为文件的大小。失败则返回-1。
int close(int fd);
关闭打开文件,释放资源。
fd--open接口打开文件时返回的文件操作句柄。
介绍完系统调用接口对文件的操作之后,还是老方法,让我们通过代码来对上述接口进行一个简单的实践。
最后所执行出的结果便是,创建出对应的main.txt文件,也成功将data写入到其中,最后也成功读取到了main.txt中的内容,通过可执行文件test很好的打印了出来。正如下图所示:
在大致了解完库函数和系统调用接口之后,我们来对二者再次进行一个简答的比较和讲述。
老生常谈的概念是,系统调用接口是底层提供给我们来对内核实现管理的一种方式,而库函数则是前人对系统调用接口的进行一部封装,提高了管理方式的功能性。
那么从对文件的操作就可以看出,库函数返回的文件操作句柄是一个FILE*的数据,而系统调用接口中的文件操作句柄是一个int类型的数据。显而易见,FILE结构体就是对文件描述符了一个封装,其内部肯定包含了文件描述符成员。
然后让我们来聊一聊缓冲区,其实从本质上而言,对于系统调用接口而言并不存在缓冲区,所以在之前的博客当中,我们演示通过系统调用接口_exit()来退出程序的时候会发现缓冲区没有被刷新,对应内容没有被打印显示。
其实当我们调用系统调用接口来对内容进行打印的时,会发现系统调用接口的打印内容会直接显示在终端上,而对于printf库函数则是会等进程结束之后才会将内容打印显示到终端上。
当我们查询库函数和系统调用接口的内容时,我们会发现对于系统调用接口而言并不存在缓冲区,所以当有内容需要被打印时,它会即刻打印结果显示在终端。而对于库函数,它们在封装时,前人定义了缓冲区的内容,需要打印的内容会不断的添加到缓冲区当中,直到缓冲区被刷新,其结果才会显示到终端上。
那么,我们为什么需要创建缓冲区,缓冲区的作用是什么。这个问题的答案其实很简单,是为了避免多次IO,提高文件IO效率。