文章目录
文件IO(系统调用)
文件描述符
以文件描述符指代打开的文件,非负整数。一个进程启动之后,默认打开三个文件描述符:
- 0标准输入 #define STDIN_FILENO
- 1标准输出 #define STDOUT_FILENO
- 2标准错误 #define STDERR_FILENO
操作系统为每个进程维护一个文件描述符表,新打开文件返回文件描述符表中未使用的最小文件描述符,调用open函数可以打开或创建一个文件,得到一个文件描述符。
文件的打开与关闭
打开文件
open函数:打开或者新建一个文件函数原型
- int open(const char *pathname, int flags);
- int open(const char *pathname, int flags, mode_t mode);
函数参数:
-
pathname:要打开或创建的文件路径
-
flags:文件访问模式,有一系列常数值可供选择,可同时选择多个,用按位或(|)连接起来,所以这些常数的宏定义都以O_开头,表示 or。
-
必选项:以下三个常数中必须指定一个,且仅允许指定一个。
- O_RDONLY:只读打开
- O_WRONLY:只写打开
- O_RDWR:可读可写打开
-
常用可选项:可以同时指定0个或多个,和必选项按位或起来使用。
-
O_APPEND:追加,如果文件已有内容,这次打开文件所写的数据附加到文
件的末尾而不覆盖原来的内容。 -
O_CREAT:若此文件不存在则创建它。使用此选项时需要提供第三个参数mode,表示该文件的访问权限。注:文件最终权限:mode & ~umask.
int fd=open("./a.txt",O_RDONLY | O_CREAT,0666); //文件掩码为0002,其他人不会有写权限,0664
-
O_ExCL:如果同时指定了O_CREAT,并且文件已存在,则出错返回。
-
O_TRUNC:如果文件已存在,将其长度截断为为0字节。
-
O_NONBLOCK:设置非阻塞模式
- 普通文件默认是非阻塞的,内核缓冲区保证了普通文件I/O不会阻塞,
打开普通文件一般会忽略该参数 - 设备、管道和套接字默认是阻塞的,以O_NONBLOCK方式打开可以做
非阻塞IO (Nonblockl/O)
- 普通文件默认是非阻塞的,内核缓冲区保证了普通文件I/O不会阻塞,
-
-
函数返回值:
- 成功:返回一个最小且未被占用的文件描述符
- 失败:返回-1,并设置errno值
关闭文件
int close(int fd);
文件的写入与读取
读文件
read函数:
从打开的设备或文件中读取数据
函数原型:
ssize_t read(int fd, void *buf, size_t count);
函数参数:
- fd:文件描述符
- buf:读取的数据保存在缓冲区 buf中
- count: buf 缓冲区存放的最大字节数
函数返回值:
-
>0:读取到的字节数
-
=0:文件为空/文件读取完毕
-
=-1:出错,并设置errno
写文件
函数描述:
向打开的设备或文件中写数据函数原型:
ssize_t write(int fd, const void *buf, size_t count);
函数参数:
- fd:文件描述符
- buf:缓冲区,要写入文件或设备的数据count: buf中数据的长度
函数返回值:
- 成功:返回写入的字节数
- 错误:返回-1并设置errno
偏移量
所有打开的文件都有一个当前文件偏移量(current file offset),也叫读写偏移量和指针。文件偏移量是文件描述符的属性。文件偏移量通常是一个非负整数,用于表明文件开始处到文件当前位置的字节数(下一个read()或write()操作的文件起始位置)。文件的第一个字节的偏移量为0。文件打开时,会将文件偏移量设置为指向文件开始(使用O_APPEND除外),以后每次read()和write()会自动对其调整,以指向已读或已写数据的下一字节。因此连续的read()和write()将按顺序递进,对文件进行操作。
在不显示地修改文件偏移量的前提下:
- 对同一文件进行读写会产生追加效果,原因为在读取一段数据后,文件偏移量到读取数据后的下一位置,这时对此文件进行写操作,不会覆盖文件前面的内容。
- 而对不同文件进行读写会产生覆盖效果,虽然在读取文件一段数据后,文件偏移量到读取数据后的下一位置,但在另一文件中,偏移量仍在文件的开头(offset=0),将一文件读取到的数据从另一文件的开头开始写入,自然而然会进行覆盖。
对同一文件读写产生追加效果
对不同文件读写产生覆盖效果
对不同文件写入,追加
打开文件时访问模式添加O_APPEND
![image-20230728184119604](https://i-blog.csdnimg.cn/blog_migrate/0fdaa7ac525e86dd77a0557ab3bd4c67.png)
![image-20230728184114384](https://i-blog.csdnimg.cn/blog_migrate/4ac395592867218585866470c87ad91f.png)
对同一个文件进行覆盖
读取文件内容后,文件偏移量不在开头位置,要进行覆盖,需要设置文件描述符的偏移量为0,使用lseek()函数。
偏移量的操作
使用lseek 函数可以改变文件的偏移量。
函数描述:移动文件指针头文件
#include <sys/types.h>
#include <unistd.h>
函数原型:
off_t lseek(int fd, off_t offset, int whence);
函数参数:
- fd:文件描述符
- offset:字节数,以whence参数为基点解释offset
- whence:解释offset参数的基点
- SEEK_SET:文件偏移量设置为offset
- SEEK_CUR:文件偏移量设置为当前文件偏移量加上offset,offset可以为负数
- SEEK_END:文件偏移量设置为文件长度加上offset,offset可以为负数
函数返回值:
- 若lseek成功执行,则返回新的偏移量
- 失败返回-1,并设置error
lseek()常用操作
- 文件指针移动到头部
lseek(fd,0,SEEK_SET);
- 获取文件指针当前位置
lseek(fd,0,SEEK_CUR);
- 获取文件长度
lseek(fd,0,SEEK_END);
-
实现文件扩展
扩展后需要在扩展区执行一次写操作才能扩展成功
-
使用SEEK_SET
使用SEEK_SET扩展时,设置的字节数大小要比当前文件长度大,才会扩展
-
- 使用SEEK_END
lseek(fd,100,SEEK_END);
练习
实现cp指令的功能
cp ./a.txt …/b.txt
int main(int argc, char* argv[])
{
if(argc!=3){
printf("输入正确的参数!");
return 0;
}
char* path1=argv[1];
char* path2=argv[2];
int fd1=open(path1,O_RDWR);
int fd2=open(path2,O_RDWR|O_CREAT|O_TRUNC,0666);//若文件存在,长度截断为0
int read_count=-1;
char buff[64];
while(read_count!=0){
read_count=read(fd1,buff,sizeof(buff));
write(fd2,buff,read_count);
}
close(fd1);<