文章目录
一、文件IO概述
1、POSIX规范
POSIX(Portable Operating System Interface,可移植操作系统接口规范,目的是提高UNIX环境下程序的可移植性。
2、虚拟文件系统VFS
Linux的文件系统由两层结构搭建:上面的虚拟文件系统VFS(Virtual File System),和下面的各种不同的具体文件系统(例如Ext、FAT32、NFS等)。
VFS将各种具体的文件系统的公共部分抽取出来形成一个抽象层,位于用户的程序与具体需要使用的系统中间,并提供系统调用接口。这样我们只需针对VFS提供的系统调用进行文件操作而无需具体考虑底层细节。VFS屏蔽了用户对底层细节的描述使得编程简化。
可以使用指令:
cat /proc/filesystems
查看当前操作系统支持哪些具体文件系统。
3、文件与文件描述符
- Linux操作系统是基于文件概念搭建起来的操作系统(“万物皆文件”)。
- 对于内核而言,内核使用文件描述符来索引打开文件。 基于文件描述符的I/O操作虽然不能直接移植到诸如Windows系统等之外的操作系统上,
但对于某些底层的I/O操作(例如驱动程序、网络连接等)是唯一的操作途径。 - 通常一个进程启动时,都会打开三个流:标准输入、标准输出、标准错误输出
流的名称 文件描述符 宏定义
标准输入 0 STDIN_FILENO
标准输出 1 STDOUT_FILENO
标准错误输出 2 STDERR_FILENO
- 在一个程序中默认可以打开的文件个数为1024个,所以文件描述符的大小范围为[0,1023]
输入 ulimit -a 可以查看限制信息 看-n大小
输入 ulimit -n 2048 可以改为2048范围
二、文件IO函数
1、打开
需要头文件:#include<sys/stat.h>
#include<fcntl.h>
函数原型:int open(const char *pathname,int flags,int perms);
函数参数:pathname:打开文件名(可以包含具体路径名)
flags:打开文件的方式,具体见下
perms:新建文件的权限,可以使用宏定义或者八进制文件权限码,具体见下
函数返回值:成功:文件描述符
失败:-1
1.1、参数2flags具体可用参数(若使用多个flags参数可以使用‘|’符号来组合):
O_RDONLY:以只读方式打开文件
O_WRONLY:以只写方式打开文件
O_RDWR:以可读可写方式打开文件
注意:O_RDONLY与O_WRONLY与O_RDWR三个参数互斥,不可同时使用
/*其他可选*/
O_CREAT:如果文件不存在,就创建这个文件,并使用参数3为其设置权限
O_EXCL:如果使用O_CREAT创建文件时文件已存在则返回错误信息。使用这个参数可以测试文件是否已存在
O_NOCTTY:若打开的是一个终端文件,则该终端不会成为当前进程的控制终端
O_TRUNC:若文件存在,则删除文件中全部原有数据并设置文件大小为0
O_APPEND:以添加形式打开文件,在对文件进行写数据操作时数据添加到文件末尾
若在参数2的位置有多个参数进行组合,注意使用按位或(|)运算符。
/可查看/usr/include/i386-linux-gnu/bits/fcntl.h文件看到具体的宏定义 /
1.2、参数3perms表示新建文件的权限,
可以使用宏定义或八进制文件权限码。其中宏定义的格式是:S_I(R/W/X)(USR/GRP/OTH),
其中R/W/X代表可读/可写/可执行,USR/GRP/OTH代表文件所有者/文件组/其他用户。
例如:S_IRUSR|S_IWUSR表示设置文件所有者具有可读可写权限,即0600。
(一般情况下该参数都直接使用八进制文件权限码因为使用宏定义的形式太复杂)。
2、关闭
函数close()
需要头文件:#include<unistd.h>
函数原型:int close(int fd);
函数参数:fd:文件描述符
函数返回值:成功:0
失败:-1
示例:使用open()与close()打开文件和关闭文件
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
int fd;
if((fd=open("hello.txt",O_RDWR|O_CREAT|O_TRUNC,0666))<0)
{
perror("fail to open file");
exit(0);
}
close(fd);
return 0;
}
3、读写数据
函数read()
需要头文件:#include<unistd.h>
函数原型:int read(int fd,void *buf,size_t count);
函数参数:fd:文件描述符
buf:读取出的数据存放的缓冲区(内存地址)
count:指定读取的字节数
函数返回值:成功:读到的字节数 或 0(表示文件已结尾)
失败:-1
函数write()
需要头文件:#include<unistd.h>
函数原型:ssize_t write(int fd,void *buf,size_t count);
函数参数:fd:文件描述符
buf:待写入的数据存放的缓冲区
count:指定写入的字节数
函数返回值:成功:已写的字节数
失败:-1
示例:使用read()和write()函数,先向文件中写入一些数据,之后读取出来
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/stat.h>
#include<fcntl.h>
#define MAX 128
int main(int argc,char *argv[])
{
int fdread,fdwrite;
char buffer[MAX]={0};
int n=0,sum=0;
if(argc<3)
{
printf("arguments are too few, Usage:%s <src_file> <dst_file>\n",argv[0]);
exit(0);
}
if((fdread=open(argv[1],O_RDONLY))<0)
{
perror("fail to open file");
exit(0);
}
if((fdwrite=open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0666))<0)
{
perror("fail to open file");
exit(0);
}
while((n=read(fdread,buffer,MAX))>0)
{
sum += n;
write(fdwrite,buffer,n);
}
printf("复制文件成功,共操作%d字节\n",sum);
close(fdread);
close(fdwrite);
return 0;
}
4、文件定位
函数lseek()
需要头文件:#include<unistd.h>
#include<sys/types.h>
函数原型:off_t lseek(int fd,off_t offset,int whence);
函数参数:fd:文件描述符
offset:相对于基准点whence的偏移量,正数表示向前移动,负数表示向后移动,0表示不移动
whence:基准点(取值同标准I/O内fseek()函数第三个参数)
函数返回值:成功:当前读写位置
失败:-1
4.1、第三个参数whence的取值如下:
SEEK_SET:代表文件起始位置,数字表示为0
SEEK_CUR:代表文件当前的读写位置,数字表示为1
SEEK_END:代表文件结束位置,数字表示为2
lseek()仅将文件的偏移量记录在内核内而不进行任何I/O操作。
注意:lseek()函数仅能操作常规文件,
一些特殊的文件(例如socket文件、管道文件等)无法使用lseek()函数。