Linux文件I/O
# 前置知识
-
Linux文件I/O分为系统IO和标准IO,常用于系统编程
-
系统I/O通过文件描述符
fd
来操作文件 -
标准I/O通过文件流
FILE*
来操作文件 -
Linux下可以使用
man
命令来查看使用手册
学习和使用这些API最快的途径是利用系统自带的man
查看手册,查看系统IO可以用man 2 open
, 查看标准I/O可以用man 3 fopen
。
关于linux中man 1 2 3 … 的区别 :
1、Standard commands (标准命令)
2、System calls (系统调用)
3、Library functions (库函数)
4、Special devices (设备说明)
5、File formats (文件格式)
6、Games and toys (游戏和娱乐)
7、Miscellaneous (杂项)
8、Administrative Commands (管理员命令)
9、 其他(Linux特定的), 用来存放内核例行程序的文档。
# 系统I/O
常用的系统I/O接口有:
open()
read()
write()
close()
lseek()
1. open()
使用命令man 2 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);
函数参数:
参数 | 意义 |
---|---|
pathname | 打开/创建的文件名 |
flags | 文件的打开模式,常用O_RDONLY, O_WRONLY, O_RDWR |
mode | 创建文件时需要指定,用来设置创建文件的权限(rwx) |
返回值:
- 返回值类型:
int
- 调用成功时返回一个文件描述符
fd
- 调用失败时返回
-1
,并设置errno
关于函数的参数说明:
第二个参数flags
可以指定为以下宏:
- O_RDONLY : 只读打开
- O_WRONLY : 只写打开
- O_RDWR : 读写打开
- O_APPEND:以追加模式打开
- O_CREAT:若文件不存在,则创建该文件,同时需要指定第三个参数
- O_SYNC:使每次write都等到物理I/O操作完成
- O_TRUNC:若此文件存在,并以读写或只写打开,则文件长度为0(打开文件的同时将文件中的内容清除)
- O_EXCL:若同时设置O_CREAT标志且文件已存在,则会出错。可用于测试文件是否存在
- O_NOCTTY:若打开的文件是终端设备,则不将此设备设置为进程的控制终端
- O_NONBLOCK:若打开的文件是管道、块设备文件或字符设备文件,则后续的I/O操作均设置为非阻塞方式
以上关于flags
的参数只有前三个指定读写方式的参数必须唯一,其他都可以用或运算符|
同时指定多个宏。
第三个参数mode
可以指定为以下宏:
linux环境下使用命令man 2 open
打开的手册中关于flags
指定了O_CREAT
时的第三参数选项,学过Linux文件权限都能理解手册的内容。
2. read()
使用命令man 2 read
查看使用手册
头文件:
使用read()
函数需要包含头文件:#include <unistd.h>
函数原型:
ssize_t read(int fd, void *buf, size_t count);
函数参数:
参数 | 意义 |
---|---|
fd | 即将读取文件的文件描述符 |
buf | 存储读入数据的缓冲区 |
count | 将要读入的数据的个数 |
返回值:
- 返回值类型是:
ssize_t
,32位机上等同于int
- 成功时返回读取的字节数
- 出错时返回EOF,读到文件末返回0
3. write()
使用命令man 2 write
查看使用手册
头文件:
使用write()
函数需要包含头文件:#include <unistd.h>
函数原型:
ssize_t write(int fd, const void *buf, size_t count);
函数参数:
参数 | 意义 |
---|---|
fd | 即将读取文件的文件描述符 |
buf | 要写入的数据缓冲区 |
count | 写入数据的个数,大小不应该大于buf大小 |
返回值:
- 返回值类型为:
ssize_t
- 成功时返回写入的字节数
- 出错时返回EOF,读到文件末返回0
4. close()
使用命令man 2 close
查看使用手册
头文件:
使用close()
时需要包含头文件: #include <unistd.h>
函数原型:
int close(int fd);
函数参数:
- fd: 要关闭的文件描述符。
返回值:
- 关闭成功时返回0,出错时返回EOF.。
说明:
- 程序在结束时会自动关闭所有打开的文件。
- 文件被关闭后,再对文件进行任何操作都是无意义的。
5. lseek()
使用命令man 2 lseek
查看使用手册
头文件:
使用lseek()
函数需要包含头文件:
#include <sys/types.h>
#include <unistd.h>
函数原型:
off_t lseek(int fd, off_t offset, int whence);
函数参数:
参数 | 意义 |
---|---|
fd | 文件描述符 |
offset | 偏移量,可正可负 |
whence | 指定一个基准点,基准点+偏移量等于当前位置 |
返回值:
- 返回值类型为:
off_t
, 32位机等同于int - 成功时则返回目前的读写位置, 也就是距离文件开头多少个字节
- 出错时返回-1, errno 会存放错误代码.
关于函数的参数说明:
第三个参数whence
:
- SEEK_SET:基准点为文件开头
- SEEK_CUR:基准点为当前位置
- SEEK_END:基准点为文件长度(可以理解为文件末尾)
系统IO实例——文件复制
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
if(argc != 3) { //argv第一个参数为自身文件名,因此需要再指定两个文件
printf("error\n");
exit(1); //使用exit()需要包含头文件<stdlib.h>
}
//打开文件
int fd_from = open(argv[1], O_RDONLY); //第二个参数为copy的源文件
if(-1 == fd_from) {
perror("open1");
exit(2);
}
int fd_to = open(argv[2], O_WRONLY | O_CREAT, S_IRWXU); //第三个参数为copy的目标文件
if(-1 == fd_to) {
perror("open2");
exit(3);
}
char buff[128] = {0};
ssize_t ret;
while(1) { //每次读写128个字节,直到文件结束
ret = read(fd_from, buff, sizeof(buff) - 1);
if(-1 == ret) {
perror("read");
}
else if(0 == ret) { //read()返回0则文件结束了
printf("finish copy\n");
break;
}
ret = write(fd_to, buff, ret);
if(-1 == ret) {
perror("write");
}
}
//关闭文件
close(fd_from);
close(fd_to);
return 0;
}
# 标准I/O
- 与系统I/O不同的是,标准I/O带有缓冲区,可以减少系统调用,提高系统效率。
常用的标准I/O接口有:
fopen()
fread()
fwrite()
fclose()
使用标准I/O只需包含头文件:#inlcude <stdio.h>
1. fopen()
函数原型:
FILE *fopen(const char *path, const char *mode);
函数参数:
参数 | 意义 |
---|---|
path | 打开/创建的文件名 |
mode | 文件的打开模式 |
关于mode
的选项有:r只读,r+读写;w只写,w+读写,a追加写,a+追加读写。其中除了r和r+其他都会在文件不存在时创建文件。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ypi7jjAB-1650468046692)(https://cdn.jsdelivr.net/gh/Chen-Mxn/mx-picgo-image/20220420173628.png)]
返回值:
- 成功时返回一个文件流指针:
FILE*
- 失败时返回
NULL
2. fread()
函数原型:
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
函数参数:
参数 | 意义 |
---|---|
ptr | 接收数据的地址,最小尺寸size*nmemb 字节 |
size | 每次读取的一个单元的大小,单位字节 |
nmemb | 读取的单元个数 |
stream | 即将读取文件的文件流 |
返回值:
- 返回值类型为:
size_t
,在32位机等价于unsigned int
- 成功时返回实际读取的单元个数
- 文件结束或读取出错时返回0或一个小于nmemb的数
3. fwrite()
函数原型:
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
函数参数:
参数 | 意义 |
---|---|
ptr | 要写入文件的数据的地址 |
size | 每次写入的一个单元的大小 |
nmemb | 写入的单元个数 |
stream | 即将写入文件的文件流 |
返回值:
- 返回值类型为:
size_t
- 成功时返回实际写入的单元个数
- 出错时返回一个与
nmemb
不同的数
4. fclose()
函数原型:
int fclose(FILE *stream);
函数参数:
stream
: 要关闭的文件流指针
返回值:
- 成功时返回0
- 失败时返回
EOF
,并设置errno
标准IO实例——文件复制
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
if( argc != 3) {
printf("error! please input 2 file name");
exit(1);
}
//打开文件
FILE *fp_from = fopen(argv[1], "r"); //只读
if(NULL == fp_from) {
perror("fopen1");
exit(2);
}
FILE *fp_to = fopen(argv[2], "w"); //只写
if(NULL == fp_to) {
perror("fopen2");
exit(3);
}
size_t size;
char buff[128] = {0};
while(1) { //每次128字节不断读写
size = fread(buff, 1, sizeof(buff) - 1, fp_from);
if(0 == size) { //fread()返回0 文件结束即复制完成
printf("finish copy\n");
break;
}
size = fwrite(buff, 1, size, fp_to);
if(0 == size) {
perror("fwrite");
}
//每次读写完要把buff清空防止出错
memset(buff, 0, sizeof(buff));
}
//关闭文件
fclose(fp_from);
fclose(fp_to);
return 0;
}
# 系统IO与标准IO的区别
最大的区别在于:
- 标准IO引用了缓冲机制,在每次执行标准IO操作时,所有的操作都在缓存区中执行,在系统空闲时或者缓冲区满的时候再写入物理磁盘,从而提高效率。
- 系统IO没有缓冲机制,每次执行系统IO操作时都会进行硬盘的读写。
2022.04.20