文件IO相关参考链接
【Linux应用编程】文件I/O基础入门~详细剖析
【Linux应用编程】深入探究了解文件io相应函数与底层逻辑
【Linux应用编程】标准IO库~详细剖析,重学c语言底层实现逻辑
【Linux应用编程实战】Day1_高级IO:非阻塞 I/O、I/O 多路复用、异步 I/O、存储映射 I/O 以及文件锁
真言:对文件的输入/输出操作,Linux 下一切皆文件;
内容
-
Linux 系统下文件描述符的概念、构成通用 I/O 模型的系统调用;
-
如打开文件、关闭文件、从文件中读取数据和向文件中写入数据以及这些系统调用涉及的参数;
文件 IO 示例
文件 IO 操作相关系统调用,一个通用的 IO 模型通常包括的基本操作:
打开文件、读写文件、关闭文件【open()、read()、write()、close()】
/* 从源文件 src_file 中读取 1KB 数据,然后将其写入到目标文件 dest_file 中
在进行读写操作之前,首先调用 open 函数将源文件和目标文件打开;
成功打开之后再调用 read 函数从源文件中读取 1KB 数据;
然后再调用 write 函数将这 1KB 数据写入到目标文件中,至此,文件读写操作就完成; */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(void)
{
char buff[1024];
int fd1, fd2;
int ret;
/* 打开源文件 src_file(只读方式) */
fd1 = open("./src_file", O_RDONLY);
if (-1 == fd1)
return fd1;
/* 打开目标文件 dest_file(只写方式) */
fd2 = open("./dest_file", O_WRONLY);
if (-1 == fd2)
{
ret = fd2;
goto out1;
}
/* 读取源文件 1KB 数据到 buff 中 */
ret = read(fd1, buff, sizeof(buff));
if (-1 == ret)
goto out2;
/* 将 buff 中的数据写入目标文件 */
ret = write(fd2, buff, sizeof(buff));
if (-1 == ret)
goto out2;
ret = 0;
out2:
/* 关闭目标文件 */
close(fd2);
out1:
/* 关闭源文件 */
close(fd1);
return ret;
}
文件描述符
open 函数会有一个返回值(文件描述符);
- 对于 Linux 内核而言,所有打开的文件都会通过文件描述符进行索引;
- 一个进程可以打开多个文件,但是在 Linux 系统中,一个进程可以打开的文件数是有限制;
- 所有执行 IO 操作的系统调用都是通过文件描述符来索引到对应的文件;
譬如fd1 和 fd2,这是一个 int 类型的数据,在 open函数执行成功的情况下,会返回一个非负整数,该返回值就是一个/*文件描述符(file descriptor)*/;
调用 open 函数打开一个现有文件或创建一个新文件时,内核会向进程返回一个文件描述符,用于指代被打开的文件;
对于一个进程来说,文件描述符是一种有限资源,文件描述符是从 0 开始分配的,譬如说进程中第一个被打开的文件对应的文件描述符是 0、第二个文件是 1、第三个文件是 2、第 4 个文件是 3……以此类推,所以由此可知,文件描述符数字最大值为 1023(0~1023)。每一个被打开的文件在同一个进程中都有一个唯一的文件描述符,不会重复,如果文件被关闭后,它对应的文件描述符将会被释放,那么这个文件描述符将可以再次分配给其它打开的文件、与对应的文件绑定起来;
每次给打开的文件分配文件描述符都是从最小的没有被使用的文件描述符(0~1023)开始,当之前打开的文件被关闭之后,那么它对应的文件描述符会被释放,释放之后也就成为了一个没有被使用的文件描述符了;
调用 open 函数打开文件的时候,分配的文件描述符一般都是从 3 开始,这里大家可能要问了,上面不是说从 0 开始的吗,确实是如此,但是 0、1、2 这三个文件描述符已经默认被系统占用了,分别分配给了系统标准输入(0)、标准输出(1)以及标准错误(2);
ulimit 命令来查看进程可打开的最大文件数:
最大值默认情况下是 1024,也就意味着一个进程最多可以打开 1024 个文件,这个限制数可设置;
open 打开文件
tips:这段看似有点杂乱,但都是原理基础,搞懂open后面很简单!
-
操作一个文件,需要先打开该文件,得到文件描述符;
-
然后再对文件进行相应的读写操作(或其他操作),最后在关闭该文件;
-
open 函数用于打开文件,当然除了打开已经存在的文件之外,还可以创建一个新的文件;
函数原型与头文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
参数
open 函数即可传入 2 个参数(pathname、flags)、也可传入 3 个参数(pathname、flags、mode);
但是第三个参数 mode 需要在第二个参数 flags 满足条件时才会有效;
函数参数和返回值
pathname:字符串类型,用于标识需要打开或创建的文件,可以包含路径(绝对路径或相对路径)信
息,譬如:“./src_file”(当前目录下的 src_file 文件)、"/home/dengtao/hello.c"等;如果 pathname 是一个符号链接,会对其进行解引用;
flags:调用 open 函数时需要提供的标志,包括文件访问模式标志以及其它文件相关标志,这些标志使
用宏定义进行描述,都是常量,open 函数提供了非常多的标志,我们传入 flags 参数时既可以单独使用某一
个标志,也可以通过位或运算(|)将多个标志进行组合;
open 函数的 flags 标志并不止这些,还有很多标志譬如 O_APPEND、O_ASYNC、O_DSYNC、O_NOATIME、O_NONBLOCK、O_SYNC 以及 O_TRUNC......
mode:此参数用于指定新建文件的访问权限,只有当 flags 参数中包含 O_CREAT 或 O_TMPFILE 标志
时才有效(O_TMPFILE 标志用于创建一个临时文件);
通过 touch 命令可新建一个文件,chmod 命令对文件权限进行修改,ls -l"命令来查看到文件所对应的权限;
open 函数去新建一个文件时,也需要mode 参数便用于指定此文件的权限;
mode 参数的类型是 mode_t,这是一个 u32 无符号整形数据:
实际编程中,我们可以直接使用 Linux 中已经定义好的宏,不同的宏定义表示不同的权限:
**返回值:**成功将返回文件描述符,文件描述符是一个非负整数;失败将返回-1;
man 命令
( man 手册)查看某一个 Linux 系统调用的帮助信息,将该系统调用的详细信息显示出来;
包括函数功能介绍、函数原型、参数、返回值以及使用该函数所需包含的头文件等信息;
man 2 open #查看 open 函数的帮助信息
/* Tips:man 命令后面跟着两个参数,数字 2 表示系统调用,man 命令除了可以查看系统调用的帮助信息外,
还可以查看 Linux 命令(对应数字 1)以及标准 C 库函数(对应数字 3)所对应的帮助信息;
最后一个参数open 表示需要查看的系统调用函数名。 */
write 写文件
调用 write 函数可向打开的文件写入数据
函数原型与头文件
/*man 2 write*/
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
函数参数
fd:文件描述符。与 write 函数的 fd 参数意义相同。
buf:指定用于存储读取数据的缓冲区。
count:指定需要读取的字节数。
返回值
如果成功将返回写入的字节数(0 表示未写入任何字节),如果此数字小于 count 参数,这不是错误,譬如磁盘空间已满,可能会发生这种情况;如果写入出错,则返回-1。
对于普通文件(我们一般操作的大部分文件都是普通文件,譬如常见的文本文件、二进制文件等),不管是读操作还是写操作,一个很重要的问题是:从文件的哪个位置开始进行读写操作?也就是 IO 操作所对应的位置偏移量,读写操作都是从文件的当前位置偏移量处开始,当然当前位置偏移量可以通过 lseek 系统调用进行设置,关于此函数后面再讲;默认情况下当前位置偏移量一般是 0,也就是指向了文件起始位置,当调用 read、write 函数读写操作完成之后,当前位置偏移量也会向后移动对应字节数,譬如当前位置偏移量为 1000 个字节处,调用 write()写入或 read()读取 500 个字节之后,当前位置偏移量将会移动到 1500 个字节处。
read 读文件
调用 read 函数可从打开的文件中读取数据
函数原型与头文件
/*man 2 read*/
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
函数参数
fd:文件描述符。与 write 函数的 fd 参数意义相同。
buf:指定用于存储读取数据的缓冲区。
count:指定需要读取的字节数。
返回值
如果读取成功将返回读取到的字节数,实际读取到的字节数可能会小于 count 参数指定的字节数,也有可能会为 0,譬如进行读操作时,当前文件位置偏移量已经到了文件末尾。
实际读取到的字节数少于要求读取的字节数,譬如在到达文件末尾之前有 30 个字节数据,而要求读取 100 个字节,则 read 读取成功只能返回 30;而下一次调用 read 读,它将返回 0(文件末尾)。
close 关闭文件
调用 close 函数关闭一个已经打开的文件;
函数原型与头文件
/*man 2 close*/
#include <unistd.h>
int close(int fd);
函数参数
fd:文件描述符,需要关闭的文件所对应的文件描述符。
返回值
成功返回 0,如果失败则返回-1;
除了使用 close 函数显式关闭文件之外,在 Linux 系统中,当一个进程终止时,内核会自动关闭它打开的所有文件,也就是说在我们的程序中打开了文件,如果程序终止退出时没有关闭打开的文件,那么内核会自动将程序中打开的文件关闭。很多程序都利用了这一功能而不显式地用 close 关闭打开的文件;
lseek
设置当前读写位置
-
对于打开的文件,系统都会记录读写位置偏移量,记录文件当前的读写位置;
-
调用 read()或 write()函数对文件进行读写操作时,从当前读写位置(起始位置)偏移量开始进行数据读写
函数原型与头文件
/*man 2 lseek*/
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
函数参数
fd:文件描述符。
offset:偏移量,以字节为单位。
whence:用于定义参数 offset 偏移量对应的参考值,该参数为下列其中一种(宏定义):
SEEK_SET:读写偏移量将指向 offset 字节位置处(从文件头部开始算);
SEEK_CUR:读写偏移量将指向当前位置偏移量 + offset 字节位置处,offset 可以为正、也可以为负,如果是正数表示往后偏移,如果是负数则表示往前偏移;
SEEK_END:读写偏移量将指向文件末尾 + offset 字节位置处,同样 offset 可以为正、也可以为负,如果是正数表示往后偏移、如果是负数则表示往前偏移。
返回值
成功将返回从文件头部开始算起的位置偏移量(字节为单位),也就是当前的读写位置;发生错误将返回-1。
编程实战
/***************************************************************
实战(1)实例代码
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名 : testApp_1.c
作者 : 邓涛
版本 : V1.0
描述 :
论坛 : www.openedv.com
日志 : 初版 V1.0 2021/01/05 创建
***************************************************************/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main(void)
{
char buffer[1024];
int fd1, fd2;
int ret;
/* 打开 src_file 文件 */
fd1 = open("./src_file", O_RDONLY);
if (-1 == fd1) {
printf("Error: open src_file failed!\n");
return -1;
}
/* 新建 dest_file 文件并打开 */
fd2 = open("./dest_file", O_WRONLY | O_CREAT | O_EXCL,
S_IRWXU | S_IRGRP | S_IROTH);
if (-1 == fd2) {
printf("Error: open dest_file failed!\n");
ret = -1;
goto err1;
}
/* 将 src_file 文件读写位置移动到偏移文件头 500 个字节处 */
ret = lseek(fd1, 500, SEEK_SET);
if (-1 == ret)
goto err2;
/* 读取 src_file 文件数据,大小 1KByte */
ret = read(fd1, buffer, sizeof(buffer));
if (-1 == ret) {
printf("Error: read src_file filed!\n");
goto err2;
}
/* 将 dest_file 文件读写位置移动到文件头 */
ret = lseek(fd2, 0, SEEK_SET);
if (-1 == ret)
goto err2;
/* 将 buffer 中的数据写入 dest_file 文件,大小 1KByte */
ret = write(fd2, buffer, sizeof(buffer));
if (-1 == ret) {
printf("Error: write dest_file failed!\n");
goto err2;
}
printf("OK: test successful\n");
ret = 0;
err2:
close(fd2);
err1:
close(fd1);
return ret;
}