文件描述符:
- 文件 = 文件内容 + 文件属性。 (文件属性也是数据-->即便你创建一个空文件,也要占据磁盘空间)
- 文件操作 = 文件内容的操作 + 文件属性的操作。(有可能在操作的过程中,即改变内容,有改变属性)
- 所谓的"打开"文件,究竟在干什么?将文件的属性或内容加载到内存汇总! -- 冯诺依曼体系决定
- 是不是所有的文件,都会处于被打开的状态?绝对不是!没有被打开的文件,在哪儿里?只在磁盘上存储着!
- 打开的文件(内存文件)和磁盘文件
- 通常我们打开文件、访问文件、关闭文件。是谁在进行相关操作?fopen,fclose,fread,fwrite....... ---》 代码 --》程序 --》 当我们的文件程序运行起来的时候,才会执行对应的代码,然后才是真正的对文件进行相关的操作。
- 进程和打开文件的关系
0.复习文件操作(C语言)
0.1由一段C语言文件操作产出几个问题
#include <stdio.h>
int main()
{
FILE *fp = fopen("log.txt","w");//写入
if(fp == NULL)
{
perror("fopen");
return 1;
}
const char* msg = "hello file";
int cnt = 1;
while(cnt < 20)
{
fprintf(fp,"%s:%d\n",msg,cnt++);
}
fclose(fp);
return 0;
}
运行结果:
这是我们写的一段最简单的C语言代码,这段代码可以产出几个问题:
0.1.1 log.txt生成时没有带路径,默认这个文件会在哪里形成呢?
我们都知道是在当前路径。那么什么是当前路径呢?当前路径是源代码所在的路径吗?其实这种说法是一种感性的认识,其实当前路径是进程所在的路径。为了验证这一结论。我们使用命令来查看一下。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
FILE *fp = fopen("log.txt","w");//写入
if(fp == NULL)
{
perror("fopen");
return 1;
}
printf("pid:%d\n",getpid());//获取当前进程的pid
while(1)
{
sleep(1);
}
const char* msg = "hello file";
int cnt = 1;
while(cnt < 20)
{
fprintf(fp,"%s:%d\n",msg,cnt++);
}
fclose(fp);
return 0;
}
使用指令查看
ls /proc/pid值 -l
我们发现有一个cwd(current working directory)--当前工作路径
因此我们验证了:当前路径是当前进程所处的工作路径。
此时我们如果对当前工作路径进行修改,我们仍然可以得到想要的结果(此时我们更改当前的工作路径到/home/Lxy)
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
chdir("/home/Lxy");//更改当前进程的工作路径
FILE *fp = fopen("log.txt","w");//写入
if(fp == NULL)
{
perror("fopen");
return 1;
}
printf("pid:%d\n",getpid());
while(1)
{
sleep(1);
}
const char* msg = "hello file";
int cnt = 1;
while(cnt < 20)
{
fprintf(fp,"%s:%d\n",msg,cnt++);
}
fclose(fp);
return 0;
}
我们继续使用指令来进行查看
ls /proc/pid值 -l
0.1.2 复习a选项
a 选项是一个写入操作,写入到文件的结尾。也就是追加操作。我们赶紧有C语言来看看效果
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
FILE *fp = fopen("log.txt","a");//写入
if(fp == NULL)
{
perror("fopen");
return 1;
}
const char* msg = "hello file";
int cnt = 1;
while(cnt <= 5)
{
fprintf(fp,"%s:%d\n",msg,cnt++);
}
fclose(fp);
return 0;
}
0.1.3 复习w选项
我们看到w操作解释的第一句话是Truncate file to zero length or create text file for writing. (将文件截断为零长度或创建文本文件进行写入).意思就是如果文件不存在则创建之;如果文件已经存在则从头开始写入。我们首先验证一下存在不写会有什么结果。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
FILE *fp = fopen("log.txt","w");//写入
if(fp == NULL)
{
perror("fopen");
return 1;
}
fclose(fp);
return 0;
}
我们看到了文件被清空了。因此我们得到当我们以'w'方式打开文件,准备写入时,其实文件已经被清空了。这是w和a的一个最大的区别!
0.1.4 复习读操作
fgets
fgets是从特定的文件流(FILE * stream)中读取特定的数据(char *s),大小是size.返回值成功了就是读取的起始地址。我们也用C语言来实现一下
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
//chdir("/home/Lxy");//更改当前进程的工作路径
FILE *fp = fopen("log.txt","r");//写入
if(fp == NULL)
{
perror("fopen");
return 1;
}
char buffer[64];
while(fgets(buffer,sizeof(buffer),fp) != NULL)
{
printf("echo:%s",buffer);
}
fclose(fp);
return 0;
}
我们根据读操作写一个小程序玩一玩,我们的想法是./myfile filename 可以打印出文件的内容
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc,char* argv[])
{
if(argc != 2)
{
printf("Usage:%s filename\n",argv[0]);
return 1;
}
FILE * fp = fopen(argv[1],"r");
if(fp == NULL)
{
perror("fopen");
return 1;
}
char buffer[64];
while(fgets(buffer,sizeof(buffer),fp) != NULL)
{
printf("%s",buffer);
}
fclose(fp);
return 0;
}
我们来一起看看吧~我们发现我们所写的和系统提供的cat指令大差不大啦~(感兴趣的小伙伴自己也尝试一下吧)
至此,我们文件基本操作(C语言)就复习到这里~
1.认识文件相关系统调用接口
我们回归理论:当我们像文件写入的时候,最终是不是像磁盘写入?肯定是,那么操作系统是硬件,只有操作系统有资格像硬件写入。那么能绕开操作系统吗?答案是不能。所有的上层访问文件的操作都必须贯穿操作系统。操作系统是如何被上层使用的呢?答案肯定是使用操作系统提供的相关系统调用!
那么这里有两个问题?
- 如何理解printf? ---- 封装了系统调用接口
- 我们怎么从来没见过系统调用接口呢? ---- 所有的语言都对系统接口做了封装
那么为什么要做封装?
- 原生系统接口,使用成本比价高(后期我们会看并且使用系统接口)
- 直接使用原生系统接口,语言不具备跨平台性。因此操作系统不同,操作系统所提供的原生接口不同。那么封装是如何解决跨平台问题的呢?--- 穷举所有的底层接口+条件编译 (根据具体对象调用对应的系统调用接口) 比如你用的是Linux系统,C语言就会把win,MacOs等接口关闭,只露出Linux的接口。上层用户用的都是一个函数,但是其实底层做了封装。
1.1 见一见系统接口
1.1.1 open
open是文件系统接口最重要的接口,没有之一。因此我们先来看看这个接口。
第一个参数(const char* pathname):带路径的文件名
第二个参数(flags):打开文件传递的选项(下面会重点介绍)
第三个参数(mode):设置权限
返回值:int , -1表示出现错误 (C语言FILE*)
第一个参数介绍:
带路径的文件名,如果只写文件名默认在当前路径下创建。
第二个参数介绍:
flags标志位有很多选项,其中这些选项都是宏。其中系统传递标记位时,是用位图结构来进行传递的。因此每一个宏标记只需要有一个比特位是1,并且有其他宏对应的值不能重叠。在这里我们讲自己写一个接口来验证一下
#include <stdio.h>
#define PRINT_A 0x1 // 0000 00001
#define PRINT_B 0x2 // 0000 00010
#define PRINT_C 0x4 // 0000 00100
#define PRINT_D 0x8 // 0000 01000
#define PRINT_DFL 0x0
void Show(int flags)
{
if(flags & PRINT_A) printf("Hello A\n");
if(flags & PRINT_B) printf("Hello B\n");
if(flags & PRINT_C) printf("Hello C\n");
if(flags & PRINT_D) printf("Hello D\n");
if(flags == PRINT_DFL) printf("hello Default\n");
}
int main()
{
printf("hello Default\n");
Show(PRINT_DFL);
printf("Hello A\n");
Show(PRINT_A);
printf("Hello B\n");
Show(PRINT_B);
printf("PRINT_A和PRINT_B\n");
Show(PRINT_A | PRINT_B);
printf("PRINT_C和PRINT_D\n");
Show(PRINT_C | PRINT_D);
printf("PRINT_A和PRINT_B和PRINT_C和PRINT_D\n");
Show(PRINT_A | PRINT_B | PRINT_C | PRINT_D);
return 0;
}
通过这个例子我们就更好的理解了open的第二个参数,介绍这两个参数后,我们就可以使用open系统函数了,我们也用C语言来实现一下
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd = open("log.txt",O_WRONLY | O_CREAT);
if(fd < 0 )
{
perror("open");
return 1;
}
printf("fd: %d\n",fd);
return 0;
}
第三个参数:
通过打印值我们发现fd返回值确实是一个int型。而且我们发现log.txt文件的权限部分是一对乱码,原因是,当我们新建一个文件的时候,我们要设置文件的权限。因此我们得出一个结论,如果我们要创建一个不存在的文件,不能使用两个参数的open接口,而是要使用带有权限参数的open接口。我们重新创建一下log.txt,并设置权限位0666
int main()
{
int fd = open("log.txt",O_WRONLY | O_CREAT,0666);
if(fd < 0 )
{
perror("open");
return 1;
}
printf("fd: %d\n",fd);
return 0;
}
此时我们发现权限比刚才的正常多了,确实不在是乱码现象了。但是0664,而不是0666。这是因为权限掩码是umask。默认的umask是0002.所以如果我们就想要权限是0666.我们手动设置umask为0即可。我们再来看看
1.1.2 close
我们把文件打开了,总得关闭文件吧。因此我们来看看关闭文件的系统接口close.因此这个接口非常简单好用。
1.1.3 write
我们把文件打开和关闭了解了之后,接下来我们要对文件进行操作了。第一个我们要了解的是write。像文件内写多西。
第一个参数(int fd):
fd,特定的文件描述符。也就是向那个文件写。
第二个参数(const void* buf):
写入缓冲区的起始地址
第三个参数(count):
写入缓冲区的大小
我们也用C语言来练一练
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
umask(0);
//打开文件
int fd = open("log.txt",O_WRONLY | O_CREAT,0666);
if(fd < 0 )
{
perror("open");
return 1;
}
printf("fd: %d\n",fd);
//对文件操作
int cnt = 0;
const char *str = "hello file\n";
while(cnt<5)
{
write(fd,str,strlen(str));
cnt++;
}
//关闭文件
close(fd);
return 0;
}
我们看到成功的向log.txt文件写入了5条hello file
至此我们已经见过了最基本的系统接口,后续我们还要对系统调用接口详细介绍使用
(本篇完)