几个概念:
1.fopen、fclose、fread等都是库函数,而库函数需要被调用系统调用接口调用(下文会介绍的open,close,read等),所以我们可以认为f#系列的函数都是对系统调用的封装,方便二次开发
2.文件存放在磁盘里,磁盘是硬件,想访问硬件只能通过操作系统,所以所有人想访问磁盘,只能使用OS提供的接口,类似于python,c++都有不同的文件操作接口,但是其本质都是封装了调用系统调用
3.文件的操作可以看成是进程和被打开文件的关系(下面会介绍)
我们先复习一下常用的几个文件打开方式
“r” 只读打开一个文本文件,只允许读数据
“w” 只写打开或建立一个文本文件,只允许写数据
“a” 追加打开一个文本文件,并在文件末尾写数据
“rb” 只读打开一个二进制文件,只允许读数据
“wb” 只写打开或建立一个二进制文件,只允许写数据
“ab” 追加打开一个二进制文件,并在文件末尾写数据
“r+” 读写打开一个文本文件,允许读和写
“w+” 读写打开或建立一个文本文件,允许读写
“a+” 读写打开一个文本文件,允许读,或在文件末追加数据
“rb+” 读写打开一个二进制文件,允许读和写
“wb+” 读写打开或建立一个二进制文件,允许读和写
“ab+” 读写打开一个二进制文件,允许读,或在文件末追加数
接口介绍
一、open
man 2 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: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。
参数:
O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR : 读,写打开
这三个常量,必须指定一个且只能指定一个
O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
O_APPEND: 追加写返回值:
成功:新打开的文件描述符
失败:-1
这个flag就是位图,所以在我们使用的时候只需要|上各种条件我们就可以实现各种效果
fclose对应close->man 3 close查看
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#define FILE_NAME "log.txt"
int main()
{
//这个才对应于C语言的写
int fd=open(FILE_NAME,O_WRONLY|O_CREAT);
//失败就返回-1
if(fd<0)
{
perror("open");
return 1;
}
close(fd);
return 0;
}
我们正常创建会遇到如下情况:log.txt权限是乱码
主要原因在于我们创建文件默认权限是666目录权限是777,然后&~umask,创建文件默认权限为什么是666呢?原因就在于我们需要自己设置mode,也就是int open(const char *pathname, int flags, mode_t mode);
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#define FILE_NAME "log.txt"
int main()
{
//我们可以自己设置umask,这改的是子进程的umask,不影响父进程
umask(0);
//这个才对应于C语言的写
int fd=open(FILE_NAME,O_WRONLY|O_CREAT,0666);
//失败就返回-1
if(fd<0)
{
perror("open");
return 1;
}
close(fd);
return 0;
}
追加需要写成
int fd=open(FILE_NAME,O_WRONLY|O_APPEND);
二、write
man 2 write查看
注意这个buf,在C语言中我们文件的写分为二进制和文本文件,但是系统调用中只有二进制,所以也证明了C语言中的写是封装了write
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
#define FILE_NAME "log.txt"
int main()
{
//我们可以自己设置umask,这并不会影响shell的umask,因为这个是子进程创建的
umask(0);
//O_TRUNC对应于每次打开文件情况文件
int fd=open(FILE_NAME,O_WRONLY|O_CREAT|O_TRUNC,0666);
//失败就返回-1
if(fd<0)
{
perror("open");
return 1;
}
char outBuffer[64];
int cnt=5;
while(cnt)
{
sprintf(outBuffer,"%s:%d\n","hello Linux",cnt--);
//这里不能写+1
//虽然字符串以\0结束,但是文件不以\0结束
write(fd,outBuffer,strlen(outBuffer));
}
close(fd);
return 0;
}
三、read
man 2 read查看
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
#define FILE_NAME "log.txt"
int main()
{
//我们可以自己设置umask,这并不会影响shell的umask,因为这个是子进程创建的
umask(0);
//O_TRUNC对应于每次打开文件情况文件
int fd=open(FILE_NAME,O_RDONLY,0666);
//失败就返回-1
if(fd<0)
{
perror("open");
return 1;
}
char buffer[1024];
ssize_t num=read(fd,buffer,sizeof(buffer)-1);
//buffer这里是void*类型,所以可以读到任意类型,可以是图片,这里我们认为读到的是字符串
//read的返回值是实际读到的大小
if(num>0)
//在C语言中fgets是会自动加上\0的(如果文件中是字符串)
//其实就是C语言封装的系统调用自己帮我们做了(加上\0)
buffer[num]=0;
printf("%s",buffer);
close(fd);
return 0;
}
四、原理
我们先输出一下open返回值
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
#define FILE_NAME(number) "log.txt" #number
int main()
{
int fd0=open(FILE_NAME(1),O_WRONLY|O_CREAT|O_APPEND,0666);
int fd1=open(FILE_NAME(1),O_WRONLY|O_CREAT|O_APPEND,0666);
int fd2=open(FILE_NAME(1),O_WRONLY|O_CREAT|O_APPEND,0666);
int fd3=open(FILE_NAME(1),O_WRONLY|O_CREAT|O_APPEND,0666);
int fd4=open(FILE_NAME(1),O_WRONLY|O_CREAT|O_APPEND,0666);
printf("%d\n",fd0);
printf("%d\n",fd1);
printf("%d\n",fd2);
printf("%d\n",fd3);
printf("%d\n",fd4);
close(fd0);
close(fd1);
close(fd2);
close(fd3);
close(fd4);
return 0;
}
我们可以同时打开多个文件,所以被打开的文件需要被OS管理起来,OS需要先描述在组织,OS为了管理对应打开的文件,必定要为文件创建对应的内核数据结构标识文件->struct file{} 包含了文件的大部分属性
fd为什么从3开始呢?
要知道这一点我们先要知道三个标准输入输出流
stdin->键盘
stdout->显示器
stderr->显示器
所以我们可以得知C语言的FILE虽然是个结构体,但是里面必定有个字段有文件描述符
printf("stdin->fd:%d\n",stdin->_fileno);
printf("stdout->fd:%d\n",stdout->_fileno);
printf("stderr->fd:%d\n",stderr->_fileno);
所以证明C语言的FILE*也封装了文件描述符
回到一开始的概念:.文件的操作可以看成是进程和被打开文件的关系,这张图清楚的展示了这样的关系