个人主页:点我进入主页
专栏分类:C语言初阶 C语言进阶 数据结构初阶 Linux C++初阶 C++进阶 算法
欢迎大家点赞,评论,收藏。
一起努力,一起奔赴大厂
目录
一.文件内容的回顾
二.提炼一下对文件的理解
三.理解文件
3.1 open
3.2 close
3.3write
四.文件描述符fd
4.1fd
4.2理解一切皆文件
4.3大致了解一下打开文件和写文件的过程
4.4 文件描述符0,1,2的证明
五.封装
5.1在Linux层面上c语言对底层的封装
5.2c语言为什么这样做
六 再次理解一下一切接文件
一.文件内容的回顾
在c语言中我们学过文件的操作,在这里我先回顾一下文件的操作,代码如下:
#include<stdio.h>
#include<string.h>
int main()
{
FILE *fp=fopen("log.txt","w");
if(fp==NULL)
{
perror("fopen");
return 1;
}
const char*str="hello file";
fprintf(fp,"%s",str);
fclose(fp);
return 0;
}
我们查看log.txt的内容
我们打开的方式为’w‘,当文件不存在时创建一个文件然后写入,当文件存在时清空文件的内容然后写入,当然还存在另一种写入方式'a',追加。我们在linux中可以使用重定向来创建文件
> a.txt
可以创建一个空文件
我们可以输入
echo hello> a.txt
将hello输入到文件a.txt
每一次输入都是会清空文件的内容然后输入。
当然也存在一种和a方式一样的,
echo file>>a.txt
可以追加到a.txt文件中
二.提炼一下对文件的理解
文件=内容+属性。打开文件本质就是进程打开文件,文件没有打开的时候存在哪里?文件没有打开的时候存在磁盘中;一个进程可以打开多个文件吗? 可以。系统中可以存在多个进程吗?可以。很多情况下的,操作系统中一定存在的大量被打开的文件,操作系统需要对这些打开的文件进行管理,先描述在组织,所以操作系统中一定存在一个类似于PCB的结构。
三.理解文件
文件在磁盘中,磁盘时外设,对文件的操作就是对外设进行操作,用户不可以直接对外设进行操作,操作系统可以对外设进行操作,我们想要写文件就需要操作系统进行操作,所以一定存在一个接口(例如我们c/c++中fprintf/fopen等)可以让操作系统进行文件的操作。
3.1 open
我们查看守则
man 2 open
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
const char *pathname是指文件的文件名或者文件路径,int flags这个参数是使用位图的形式传入的,mode_t mode是指文件的权限。我们看代码:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fp=open("log.txt",O_CREAT|O_WRONLY,0666);
return 0;
}
我们可以看到可以出现一个log.txt文件:
对于flags这个参数它类似于
#define A 1
#define B 1<<1
#define C 1<<2
int main()
{
int a=A|B|C;
}
对于这个文件的权限为什么是644而不是666呢?这个和掩码有关,具体不做解释。
3.2 close
我们查看守则
man 2 close
关闭需要文件描述符fd进行关闭。
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fp=open("log.txt",O_CREAT|O_WRONLY,0666);
if(fp<0)
{
perror("open");
return 1;
}
close(fp);
return 0;
}
3.3write
我们看手则
man 2 write
第一个参数int fd是指文件描述符, 第二个参数const void *buf是输入的内容,第二个参数 size_t count是输入内容的长度。我们看代码:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fp=open("log.txt",O_CREAT|O_WRONLY,0666);
if(fp<0)
{
perror("open");
return 1;
}
const char*str="hello file";
write(fp,str,strlen(str));
close(fp);
return 0;
}
我们查看log.txt文件的内容
四.文件描述符fd
4.1fd
我们先看一下库中open的返回值
我们看下面代码:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fpa=open("loga.txt",O_CREAT|O_WRONLY|O_TRUNC,0666);
printf("fpa : %d\n",fpa);
int fpb=open("logb.txt",O_CREAT|O_WRONLY|O_TRUNC,0666);
printf("fpb : %d\n",fpb);
int fpc=open("logc.txt",O_CREAT|O_WRONLY|O_TRUNC,0666);
printf("fpc : %d\n",fpc);
int fpd=open("logd.txt",O_CREAT|O_WRONLY|O_TRUNC,0666);
printf("fpd : %d\n",fpd);
return 0;
}
代码运行的结果为:
fpa为3,fpb为4,fpc为5,fpd为6 ,文件描述符没有0,1,2,其中0是标准输入流,1是标准输出流,2是标准错误流
这三个的返回类型为FILE。
一个进程可以对应多个文件,操作系统需要知道文件的状态,需要对打开的文件进行管理,先描述在组织,我们可以看下面的图片:
文件描述符的fd的本质是什么?内核进程:文件映射关系的数组下标。open在干什么?创建file,开辟文件缓冲区的空间,加载文件数据,查看文件的描述符表,file地址填入对应的表和下标,返回下标。
4.2理解一切皆文件
键盘,显示器,鼠标等都是硬件,但是一切皆文件,所以硬件在操作系统也是以文件存储,既然是文件,那么就有一个struct_file ,看下图:
在操作系统中每一个struct_file都有一些函数指针来进行硬件的访问,这是类的多态级的技术。
4.3大致了解一下打开文件和写文件的过程
我们在log.txt文件中进行写入 helloworld,我们看下面的图片:
先打开文件,返回一个文件描述符,通过这个文件描述符进行写文件,然后在进程strict_task中找到这个文件描述符表,然后通过文件描述符表下表为文件描述符,根据这个的地址找到文件的属性和文件的内容然后写入文件,操作系统再定时将内容刷新到磁盘中。
4.4 文件描述符0,1,2的证明
在stdin.stdout,stderr对应的结构体中有一个变量_fileno来存储文件描述符,我们看下面的代码代码:
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
printf("stdin : %d\n",stdin->_fileno);
printf("stdout : %d\n",stdout->_fileno);
printf("stderr : %d\n",stderr->_fileno);
return 0;
}
运行结果如下:
同样当我们创建一个文件时,它也会封装文件描述符,看下面代码:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
printf("stdin : %d\n",stdin->_fileno);
printf("stdout : %d\n",stdout->_fileno);
printf("stderr : %d\n",stderr->_fileno);
FILE* fp=fopen("log.txt","w");
if(fp<0)
{
perror("feopn");
return 1 ;
}
printf("fp : %d\n",fp->_fileno);
return 0;
}
结果如下:
五.封装
5.1在Linux层面上c语言对底层的封装
直接看代码:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fp1=open("log.txt",O_CREAT|O_WRONLY|O_TRUNC,0666);
if(fp1<0)
{
perror("fopen");
return 1;
}
return 0;
}
在这段代码中文件的打开和c语言中的下面代码效果一样:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
FILE* fp=fopen("log.txt","w");
if(fp==NULL)
{
perror("fopen");
return 1;
}
return 0;
}
所以这里是对底层的封装,,同样追加写也有对应的封装:
int fp=open("log.txt",O_CREAT|O_WRONLY|O_APPEND,0666);
5.2c语言为什么这样做
对底层进行封装的一个重要原因就是语言不具有跨平台性,当我们在windos平台写一段代码,在Linux中就有可能不能运行,这就是因为语言奴具有跨平台性,所以它就会出一个接口,例如我们的fopen/fwrite等都是对底层进行封装,看下面图片可以大致了解一下其中的实现
六 再次理解一下一切接文件
Linux中一切皆文件,那么显示器也是文件,既然是文件那我们就可以通过代码在显示器上写一些内容,我们输入指令:
ls /dev/pts
当我们再开一个终端,再次输入这个指令可以看到:
我们在第一个终端上运行代码:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fd=open("/dev/pts/1",O_WRONLY|O_APPEND);
if(fd<0)
{
perror("open");
return 1;
}
const char * str="hello linux\n";
while(1)
{
write(fd,str,strlen(str));
sleep(1);
}
close(fd);
return 0;
}
我们在另一个终端上可以看到: