文章目录
前言
一、常用函数
1. open函数
2. close函数
3. write函数
4. read函数
5. dup函数
6. dup2函数
二、文件读写细节
1. 换行符
2. 文件描述符
3. errno和perror
前言
在Linux系统中,一切皆文件。因此,掌握Linux下文件IO常用的函数、理解读写文件背后的原理至关重要。在【嵌入式Linux笔记】第三篇:Linux应用开发基础(上)中,本人已经记载了相关的文件IO的知识,但那篇只是用于快速入门,记录的知识并不全面。本篇更加细致地记录文件IO中的重要知识点,若涉及版权问题,请联系本人删除。
一、常用函数
文件IO操作流程:首先调用open函数打开文件,其次采用read/write函数进行读写操作,最后调用close函数关闭文件。
值得注意的是:调用open函数之后,内核就会在进程中建立一个打开文件的数据结构,记录打开的文件;然后内核去磁盘(块设备)中找到文件,将其加载到内存中,我们读写都是作用于内存中的文件;最后调用close函数才会将内存中的文件写入到块设备中。
这样设计的原因:块设备本身有读写限制,读写操作起来不灵活;内存以字节为单位,操作灵活方便。
1. open函数
【1】头文件:#include <sys/types.h>、#include <sys/stat.h>、#include <fcntl.h>
【2】函数原型:
- int open(const char *pathname, int flags);
- int open(const char *pathname, int flags, mode_t mode);
【3】功能:打开或创建一个文件
【4】相关描述:
①pathname:指明了文件打开的地址。
②flags:必须包含以下之一:O_RDONLY, O_WRONLY, O_RDWR。当有多个参数时,用|来分隔。其余如下的flags是可选的:
- O_APPEND:追加内容
- O_TRUNC:截断,如果文件存在并且以只写、读写方式打开,则将其长度截断为0
- O_CREAT:若文件不存在,则创建
- O_EXCL:若要创建的文件已存在,则出错并返回-1,同时修改errno的值
- O_NONBLOCK:以非阻塞方式打开设备文件
- O_SYNC:write函数阻塞等待内容全部写入块设备中才返回
- 注意:如果 O_APPEND | O_TRUNC 则为 O_TRUNC 的效果
③mode:只有当创建新文件时(使用了O_CREAT时)才使用,用于指定文件的访问权限,能用权限的数字表示法。
- umask能够设置文件在创建时mode的掩码。例如,使用umask命令得到"0002"的结果,那么文件创建时模式掩码为"000 000 000 010"。其实就是other用户权限总数字-2。
- 使用open创建文件时最终的权限结果为:"mode & ~umask"。因此,若指定权限为0777但创建文件的权限结果为"000 111 111 101"即0775.
④返回值:
- 打开成功:是一个文件描述符,其是一个小的、非负整数。成功调用返回的文件描述符是当前未为进程打开的编号最低的文件描述符。
- 打开失败:-1
⑤机制:调用open函数会创建一个新的打开文件描述,打开文件描述记录了文件偏移量和文件状态标志。文件描述符就是对该打开文件描述的参考。当路径名被删除或修改为其它文件,则此参考并不受影响。
【5】open函数打开文件:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
int main(int argc, char **argv)
{
//命令检测
if (argc != 2) {
printf("请输入命令:%s <filename>\n", argv[0]);
return -1;
}
//打开文件
int fd = open(argv[1], O_RDWR);
if (fd < 0) {
perror("错误信息");
return -1;
}
//执行相关操作
printf("打开文件成功,fd=%d\n", fd);
//关闭文件
close(fd);
return 0;
}
【6】open函数创建文件:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
int main(int argc, char **argv)
{
//命令检测
if (argc != 2) {
printf("请输入命令:%s <filename>\n", argv[0]);
return -1;
}
//打开文件,若不存在则创建权限为777
//但是系统保护,最终创建权限为775
int fd = open(argv[1], O_RDWR | O_CREAT, 0777);
if (fd < 0) {
perror("错误信息");
return -1;
}
//执行相关操作
printf("打开文件成功,fd=%d\n", fd);
//关闭文件
close(fd);
return 0;
}
2. close函数
【1】头文件:#include <unistd.h>
【2】函数原型:int close(int fd);
【3】功能:关闭文件描述符为fd的文件。
【4】返回值:关闭成功为0,关闭失败为-1
3. write函数
【1】头文件:#include <unistd.h>
【2】函数原型:ssize_t write(int fd, const void *buf, size_t count);
【3】功能:将buf中指定字节数count的内容写入文件描述符为fd的文件中。
【4】相关描述:
- ①返回值:
- 写入成功:返回写入的字节数
- 写入失败:返回小于写入字节数
- 发生错误:返回-1
- ②使用lseek函数给指定位置写入内容,是对原有位置内容进行覆盖。
【5】示例:命令行中用户输入运行命令时,给出多条字符串,将字符串写入指定文件中。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
int main(int argc, char **argv)
{
/* 1.命令格式判定 */
if (argc <= 2) {
printf("请输入命令:%s <文件名> 字符串1 ...\n", argv[0]);
return -1;
}
/* 2.打开文件 */
int fd = open(argv[1], O_RDWR | O_CREAT, 0664);
if (fd < 0) {
perror("打开文件失败");
return -1;
}
/* 3.写入字符串 */
for (int i = 2; i < argc; ++i) {
int writeLen = write(fd, argv[i], strlen(argv[i]));
if (writeLen < strlen(argv[i])) {
perror("写入失败");
break;
}
write(fd, "\n", 1);//每个字符串后再换行
}
/* 4.关闭文件 */
close(fd);
return 0;
}
4. read函数
【1】头文件:#include <unistd.h>
【2】函数原型:ssize_t read(int fd, void *buf, size_t count);
【3】功能:将文件描述符为fd的文件中指定字节数count的内容读取到buf中。
【4】返回值:读取成功返回字节数,发生错误返回-1。在判定读取是否成功时,最好不要拿返回的字节数与指定的读取字节数作比较,因为有时指定的字节数会比返回的字节数大。
【5】示例:
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char **argv)
{
/* 1.命令格式判定 */
if (argc != 2) {
printf("请输入如下格式:%s <文件名>\n", argv[0]);
return -1;
}
/* 2.打开文件:只读 */
int fd = open(argv[1], O_RDONLY);
if (fd < 0) {
perror("打开失败");
return -1;
}
/* 3.读取文件中所有内容 */
char readBuf[100];
int readLen = read(fd, readBuf, sizeof(readBuf));
if (readLen == -1) {
perror("读取失败");
close(fd);
return -1;
}
printf("读取的文本长度:%d\n", readLen);
printf("读取的文本内容:%s", readBuf);
/* 4.关闭文件 */
close(fd);
return 0;
}
5. dup函数
【1】头文件:#include <unistd.h>
【2】函数原型:int dup(int oldfd);
【3】功能:将生成的文件描述符指向传入的oldfd指向的结构体,而不会新建一个结构体。
【4】返回值:新生成的文件描述符。
【5】示例:假设本地已有一个1.txt文件,当我们通过下列程序去读1.txt文件时,执行结果为:①fd = 3, fd2 = 4, fd3 = 5 ②fd: 1, fd2: 1, fd3 : 2
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char **argv)
{
//命令行判定
if (argc != 2) {
printf("请输入下列格式:%s <文件名>\n", argv[0]);
return -1;
}
//dup功能检验
int fd = open(argv[1], O_RDONLY); //会新建一个结构体
int fd2 = open(argv[1], O_RDONLY); //会新建一个结构体
int fd3 = dup(fd); //不新建,而是指向fd的结构体
printf("fd = %d, fd2 = %d, fd3 = %d\n", fd, fd2, fd3);
//读取同一个文件
char c1, c2, c3;
read(fd, &c1, 1); //fd中的pos往后移动
read(fd2, &c2, 1); //fd2中的pos往后移动
read(fd3, &c3, 1); //由于fd3指向fd结构体,fd中的pos往后移动
printf("fd: %c, fd2: %c, fd3 : %c\n", c1, c2, c3);
//关闭文件
close(fd);
close(fd2);
return 0;
}
6. dup2函数
【1】头文件:#include <unistd.h>
【2】函数原型:int dup2(int oldfd, int newfd);
【3】功能:重定向。以dup2(fd, 1)为例说明:①关闭文件句柄1的文件;②将文件句柄1指向fd对应的file结构体。因此,之后涉及文件句柄1的操作都作用于fd所指的文件。如下图所示:
【4】返回值:newfd。 一般不关注这个返回值。
【5】示例:假设我们有个1.txt文件,执行下列程序后,命令行中并未出现字符串,而字符串"fd = 3, fd = 1"出现在了1.txt文件中。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
int fd = open(argv[1], O_RDWR);
int fd2 = dup2(fd, 1);
printf("fd = %d, fd2 = %d\n", fd, fd2);
close(fd);
return 0;
}
二、文件读写细节
1. 换行符
- Windows中:换行为"\r\n"
- Linux中:换行为"\n"
- Mac中:换行为"\r"
2. 文件描述符
- 文件描述符是一种文件的"ID"。通过文件描述符,我们可以对其指向的文件进行读写操作。
- 在一个进程中,每次调用open函数打开一个文件,都会生成一个新的文件描述符(不会与已有的重复)。
- 不同进程的文件描述符是相互独立的。
- 一个进程中默认会有三个文件描述符:0指向标准输入(键盘),1指向标准输出(命令行),2指向错误信息的输出(命令行)。