广告公司寮步网站建设,域名权重,建设企业官网模板,wordpress自动采集外链目录 1.先认识文件
2.c语言中常用文件接口
fopen#xff08;打开文件#xff09;
3.系统接口操作文件
open write
文件的返回值以及打开文件的本质
理解struct_file内核对象
了解文件描述符#xff08;fd#xff09;分配规则
重定向
dup接口
标准错误流
文件缓冲…目录 1.先认识文件
2.c语言中常用文件接口
fopen打开文件
3.系统接口操作文件
open write
文件的返回值以及打开文件的本质
理解struct_file内核对象
了解文件描述符fd分配规则
重定向
dup接口
标准错误流
文件缓冲区
举例
总结
简单接口的封装 1.先认识文件
首先了解文件之前我们必须清楚的了解文件的本质
1.文件内容属性对文件的操作就是对内容或者属性操作。
2.其实内容属于数据属性也是属于数据的。存储文件既要存储文件内容们也要存放文件属性。
3.我们进程在访问文件时首先要打开这个文件打开前他是存储在磁盘中的打开后需要把文件加载都内存当中。
4.一个进程可打开多个文件多个文件都会被加载到内存当中。
而打开文件这些工作也是操作系统干的事因此操作系统要管理这些文件就需要先描述在组织
首先描述出文件的各个属性及内容再将它组织成一个结构体即文件的内核数据结构通过链表的方式链接这些结构体之后对文件实现增删查改就是操作链表。
因此打开一个文件操作系统会在内核中形成一个被打开的文件对象来描述该文件。
5.因此一个打开的文件与一个未被打开的文件两者此时是不一样的没打开的文件就在磁盘里打开的文件需要被加载到内存当中并形成对应的文件对象结构表示该文件并用来管理该文件。
2.c语言中常用文件接口
fopen打开文件 先看第一个fopen参数第一个是文件路径第二个是文件句柄文件指针。 1 #includestdio.h2 3 int main()4 {5 FILE *pfopen(log.txt,w);//以下的方式在当前路径下打开一个log.txt文件6 if(pNULL)7 {8 perror(fopen);//打开失败打印错误信息9 return 1;10 }11 const char*strhello linux\n;12 int cnt10;13 while(cnt--)14 {15 fputs(str,p);//像文件里面写入十行字符串 16 }17 fclose(p);//关闭文件18 return 0;19 }基本了解一下常用接口。
对于文件的打开方式我们再看一下 r:以读的方式打开文件只能读。
r支持读文件也支持写入写入在文件末尾写。
对于w以写方式打开的时候时截断的即每次会先清空之前的内容文件未创建会创建。即覆盖式打开文件。
而我们的重定向文件就是以w方式打开文件当我们需要清空文件内容时就是用重定向方式打开文件。
w与w方式一样不过增加了读。
a:也是文件写入只不过写入是从文件结尾写入即追加写入。
我们也可以利用追加重定向的符号进行写入 文件。
a:与a效果一样增加了读。
3.系统接口操作文件
上述c语言操作文件的接口在底层其实都是操作系统打开文件的指令被封装成c接口。
因此我们来尝试用系统接口操作文件
open 首先对于open的返回值如果打开成功它会返回一个文件描述符简称fd,失败会返回-1和错误信息。
第一个参数文件名第二个参数flags表示打开文件的标记位。
对于标志位可能会右很多参数对于linux下传参方式是这样的
这里的一个标志位是可以传多个标志位的
如果文件存在
就是这里的O_RDONLY(以读方式打开),O_WRONLIY以写的方式打开,O_RDWR以读写的方式打开. 若果文件不存在
标志位是下面这些O_CREAT(创建文件),O_DIRCTORY(),O_NOCTTY()....... 实际上这些标志位就是一个宏整数这里的每一个宏都是唯一的。
通过宏传参我们可以实现对于满足宏条件的语句都可以执行因此可以实现多个一起传入参数且实现其中的多个功能。
如下一个简单的程序 #includestdio.h#includeunistd.h#includesys/types.h#includesys/stat.h#include fcntl.hint main(){int fdopen(log.txt,O_WRONLY|O_CREAT);//一些的方式打开不存在就创建if(fd0){perror(open failed);return 1;} close(fd);return 0;}
因为我们并没有提供文件属性设置的指令因此这里会是乱码的。 因此我们可以看到对于函数open第二个是有三个参数第三个参数就代表设置文件的权限
这里的权限就是对应的8进制数 当我们在打开时这样写 int fdopen(log.txt,O_WRONLY|O_CREAT,0666); 但是这里的权限表示的是664主要原因创建权限时系统中会有权限掩码帮我们过滤我们设置的权限可利用umask函数修改掩码。 //再open前加入
umask(0)//权限掩码设置为0 之后就可以看见都是6660了。 我们在一般使用的时候就使用系统的umask。 write
打开晚间后那么我们就可以操作文件了最常用的就是文件写入了 向一个指定的文件描述符写入。
参数也不难看出是 fd文件描述符 buf写的字符串 count个数
char*strhello,linux!;
write(fd,str,strlen(str));
其次我们再写入时如果之前有内容并不会清空而是从头开始覆盖式的写入因此我们再写入之前如果想写入新的数据需要添加新的标志位-O_TRUNC截断文件为0。
int fdopen(log.txt,O_WRONLY|O_CREAT|O_TRUNC,0666);
若我们想要在文件结尾处写入则式将这里的O_TRUNC换成0_QPPEND,追加写入。
int fdopen(log.txt,O_WRONLY|O_CREAT|O_APPEND,0666);
看到这里我们就会发现如何去将系统库函数封装成c语言的接口。
文件的返回值以及打开文件的本质 当我们在代码创建了多个文件将他们的文件描述符一一打印出来的时候我们会发现他是从3开始逐渐增加且是连续的逐渐增加我们明白因为文件标识符不可能一样通过的方式确保每一个文件的文件描述符是唯一的但是为什么是从3开始的呢
首先和管理进程一样在管理文件是操作系统要为每一个打开的文件创建对应的内核结构struct_fIle管理该文件结构体里面存放的就是描述文件的各种属性。 为了区分好文件结构体与进程的结构体确定那个进程有哪些文件文件被打开时系统创建对应的文件结构体struct_file同时会在该进程有一个数组里存放文件的地址并将此时的下标返回给上级通过此方式哪些进程的哪些文件就可以被知道且两者更好能区分。且这个下标就是我们这里的fd进程标识符。
知道了这些那么为什么不是从0开始的呢前三个数组下标呢 实际上这个我们之前在学c语言的时候就会知道文件在打开时会默认打开三个文件流
标准输入流 stdin 键盘 0
标准输出流 stdout 显示器 1
标准错误流 stderr 显示器 2
这其实也就是前三个文件描述符。
我们知道c语言里FILE*来表示文件的开的返回值那么是什么
FILE* 其实是一个结构体由c语言标准库提供作为一个结构体他其中必定封装有fd文件描述符其实对应的返回值与函数c语言库中都进行了封装。
int main() {printf(stdin-fd,stdin-_fileno);printf(stdout-fd,stdout-_fileno);printf(stderrer-fd,stderr-_fileno);FILE*pfopen(log.txt,w);printf(FILE-fd,p-_fileno);fclose(p);} 了解了以上那么为什么文件被打开时要默认打开这三个文件流
默认打开这三个就是为了让用户直接读写数据直接用函数接口操作文件。
其次在每一个文件架构体重都会有两个函数指针red,wirte。对于键盘显示器这样的书输入输出设备他们就会有对应的读写方法。 理解struct_file内核对象
我们可以大概猜到struct_file与进程的内个对象类似里面存放了各种关于文件的信息属性内容 对于结构体的创建在内存当中被如何创建我们都可以类比进程。对于文件我们想要读数据时需要将文件加载到内存当中其次写数据也是需要我们将数据先加载到内存当中而文件数据加载到内存当中并不是直接加载到内存当中而是先加载到文件缓冲区当中那我们我们读写数据的本质就是在缓冲区里来回拷贝。
了解文件描述符fd分配规则
我们已经知道了fd本质就是文件结构体指针数组的下标那么在实际使用时如何分配的呢
我们继续了解一下read接口向指定的文件标识符中读取数据。 既然默认打开三个流文件我们来试试这
先看看输入流文件我们知道它对应的文件标识符位0我们直接通过read读取输入缓冲区的字符
int main(){char buffer[100];//因为文件在打开时默认打开了三个流因此我们可以直接做读写操作read(0,buffer,100);//给输入流读取buffer数组大小的字符串及我们输入的字符串保存在bufferprintf(%s,buffer);return 0; } 通过打印侧面证明了我们可以访问输入这个文件。不用通过scanf来读字符。
int main(){char buffer[100];read(0,buffer,100);//给输入流读取字符串并保存在bufferwrite(1,buffer,strlen(buffer));//给输出流写入字符串buffer return 0; }
所以fd的分配规则就是默认先打开这三个文件流012.
且在分配的时候从下开始找最小的没有被使用的位置再分配给打开的指定的文件。
当然我们也可以打开文件再关闭这三个默认的文件流例如close(1),那么我们想要打印出的数据就打印不出了。
重定向
基本了解以上内容之后我们看看这个代码是为什么呢 int main(){close(1);//关闭输出流int fdopen(FILENAME,O_CREAT| O_WRONLY | O_TRUNC,0666);if(fd0) { perror(open failed); return 1; } printf(%d\n,fd); printf(stdout-%d\n,stdout-_fileno); fflush(stdout); close(fd); }
首首先我们关闭了输出流运行程序之后没有输出信息没问题关闭之后我们又打开了一个文件该文件的输入输出错误流是打开的此时我们进行打印之后就刷新缓冲区打印的信息没出来正常但是当我们打开文件后却发现写入显示器的内容到了文件里面而这就是输出重定向。
那么为什么会是这样呢 总结的来说就是刚开始关闭了输入流这里的结构体文件指针下标1对应没有指向原本只想显示器此时又打开了一个文件此时操作系统要看当前文件与此时打开的文件的关联即打开我们.c文件时下标02对应的指针又指向1被关闭了此时再次打开一个文件则该文件地址要放到结构体指针数组且要从最小的没被占用开始所以该文件指向了下标1即我们新打开的这个文件的描述符变成了1因此打印进文件当中了。
因此文件结构体数组下标没变我们改变下标对应的内容就可以实现重定向。
上述就是输入流stdout-显示器转变stdout-文件
例如我们这里可以重新让他输入而且这里我们使用fprintf接口与printf差不多只是多了我们需要的接口。 printf的stdout默认是向显示器里输出利用fprintf我们可以决定向哪个地方输出
将上述的代码的两个打印变成这样写 fprintf(stdout,%d\n,fd); fprintf(stdout,stdout-%d\n,stdout-_fileno);
取消关闭输出流在运行就可以看到会向显示器打入 此时新打开的文件标识符位3默认stdout的是1 。向显示器打印。此时数据1位置指向的是显示器。
再关闭在运行此时1位置对应的指向是文件我们就发现他就是相对应文件里打印数据。 根据上述结论可以验证我们的想法。
打开文件的文件标识符为1stdout也为1/
但是对于这里我们是通过刷新缓冲区测i可以去掉刷新缓冲区文件里也没写入这与我们的文件缓冲区有关。
通过上述方式我们很容易实现输出输入重定向。
我们可以从文件读数据从键盘读数据我们也可以向显示器打印数据也可以向文件打印数据。
上层fd不变指向的内容在改变。
dup接口 我们可以看到关于dup函数的功能它会改变旧文件的文件描述符即改变文件指针的指向。且dup会默认关闭旧的文件流这里就是显示器。
通过该种方式我们也可以实现文件描述符的重定向其中在文件结构体内部还定义了一个引用计数的fcount有几个文件指向结构体数组的某个位置对应fcount就为几当fcount为0时就会释放掉。当然我们也可以自己再重定向之前关闭这个文件流不关闭系统也会帮我们重新指向。 int main(){int fdopen(FILENAME,O_CREAT| O_WRONLY | O_TRUNC,0666);if(fd0){perror(open failed);return 1;}//利用dup接口实现重定向我们这里替换下标为1的输出流文件dup2(fd,1);//把原本文件的fd变成1此时下标为1的地址指向的是文件fprintf(stdout,file-%d,fd);fprintf(stdout,stdout-%d,stdout-_fileno); return 0;
} 了解到重定向的本质我们就可以通过命令行参数来自己实现输入重定向符号追加重定向。
其次进程替换与重定向两者之间是没有影响的。程序替换只是地址映射发上了变换并没有创建新的进程。因此不影响重定向。
标准错误流
标准输入输出存在我们很清楚每次都要先打开这两个流我们也能理解那么标准错误流为什么每一次也要打开错误流是干什么用的。
首先除了重定向输入流输出流我们也是可以重定向错误六例如我们可以将错误流重定向到输出流。
1log.txt 21,通过连续的重定向我们可以实现将多个文件的内容指向输出流1。通过该种方式我们就可以看到错误信息方便我们排查。
文件缓冲区
当一个文件被访问时无论读写都需要我们将它加载到文件缓冲区当中。即我们读写的数据都先事先拷贝到一个buffer里再通过write接口将内容写到文件当中且一个文件的缓冲刷新机制是全缓冲的。
那么什么是缓冲区如何去理解缓冲区
对于缓冲区就是一部份内存我们将数据拷贝其中无论是C语言还是c我们都里理解为是malloc开辟出来的一块空间。
那为什么要有缓冲区呢
缓冲区的作用是为了提高使用者的效率我们不再把数据直接交给对方而是先直接交给缓冲区对于用户我们就直接完成了工作缓冲区之后的工作让操作系统来搞。
因为有缓冲区我们可以积累一部分派发数据拥有不同的派发效果。当然积累数据的不同程度缓冲区有不同的派发策略-----无缓冲行缓冲全缓冲....。其次也有特殊情况下的策略-------如强制刷新进程退出强制刷新。
用过这种方式也提高了发送的效率。
举例
下来我们分析一下这段代码、 #includestdio.h2 #includeunistd.h3 #includestring.h4 int main()5 {6 7 fprintf(stdout,hello linux1\n);8 fputs(hello linux2\n,stdout);9 printf(hello linux3\n);10 const char *strhello write\n;11 write(1,str,strlen(str));12 13 fork();//注意在该位置创建的子进程 14 return 0;15 }运行之后的输出和重定向到log.txt的输出为什么是这样子的 对于write接口的数据先写到文件当中其次是c的接口的写入且写了两份 首先第一次直接输出到显示器上的没问题创建的子进程的位置也是在最后与他无关。
输入正常。
第二次重定向到文件之后再打开里面的内容为什么是这样子的呢
这里的fork是如何执行的呢
首先我们的缓冲区刷新是行刷新因为我们在每一句话后面都带\n了即在fork之前缓冲区都是刷新了的。
但是当我们通过输出重定向到log.txt此时我们依然知道这里的文件结构一直真的数组下标1里面不是指向显示器的而是指向log.txt的文件当中了此时的缓冲区刷新由行刷新变成了全缓冲
全缓冲意味着缓冲区变大更加意味着我们写入的简单数据不足以把缓冲区写满因此fork在执行的时候此时数据还在缓冲区中。
目前我们这里所说的缓冲区与操作系统无关而是c语言的封装因为我们的接口printf,fprintf底层都是write的封装但是这里取有缓冲区的概念系统接口本身是没有这的。因此这里的缓冲区就是与c语言本身自己有关。
而我们这里的c/c提供的缓冲区所保存的数据属不属于进程的数据呢实际上该缓冲区的数据属于进程本身的数据。
但是对于文件里面的数据他与进程是什么关系呢文件里的数据与进程的此时来说因该是没有啥关系的即文件里的数据不属于进程的数据当我们把缓冲区的数据交给到操作系统时此时的数据就与进程没什么关系而是属于文件。
因此当进程退出的时候一般要刷新缓冲区而刷新缓冲区就是要把这里的数据写到文件当中。
而父子进程数据共享当一方写入到文件中时对于这里来说就是任意一个进程退出强制刷新缓冲区像文件里写入两方都发生写时拷贝因此我们的数据写入了两份。
但是对于系统调用接口他与缓冲区没有任何关系他是直接写入的因此系统接口只写一次。
而且系统接口先进行写入之后缓冲区刷新再写入缓冲区的数据。因此我们看到先写的write的内容之后是两份拷贝。
总结
对于系统的文件读写接口直接接像文件写入的但是c/c等语言不仅对读写接口进行了封装还提供了缓冲区使得读写数据对缓冲区操作缓冲区的数据属于进程但当刷新写入文件当中时此时的数据不属于进程而属于操作系统文件。
对于c语言我们的缓冲区就封装在FILE中由许多指针构成的一块空间。 简单接口的封装
.h文件
pragma once
#define MAXSIZE 1024
//三种刷新策略
#define Flushline 1
#define Flushall 2
#define Flushnone 3typedef struct _myFILE
{int fileno;//文件标识符char buffer[MAXSIZE];//缓冲区int end;//占用空间int flag;//标志
}myFILE;myFILE* my_fopen(const char *path,const char* mode);
int my_fclose(myFILE*fp);
int my_fputs(const char*s,int num,myFILE* stream);
int my_fflush(myFILE*stream);.c文件
#includemystdio.h
#includestring.h
#includestdio.h
#includesys/types.h
#includesys/stat.h
#includefcntl.h
#includestdlib.h
#includeerrno.h
#includeunistd.h
#define permison 0666
//简单的封装io接口myFILE* my_fopen(const char *path,const char* mode)
{int fd0;//文件标识符int flag0;//标志位,模式//打开文件模式if(strcmp(mode,r)0){//以读的方式打开flag |O_RDONLY;//只读}else if(strcmp(mode,w)0){//以写的方式打开flag|(O_WRONLY | O_CREAT |O_TRUNC);}else if(strcmp(mode,a)0){//以追加的方式flag|(O_WRONLY|O_CREAT|O_APPEND);}//打开文件if(flag O_CREAT){//若文件不存在创建文件fdopen(path,flag,permison);}else{fdopen(path,flag);}if(fd0){errno2;return NULL;}//打开成功返回myFILE*p(myFILE*)malloc(sizeof(myFILE));if(!p){//申请失败errno3;return NULL;} p-flagFlushline;p-filenofd;p-end0;return p;
}
int my_fclose(myFILE*stream)
{my_fflush(stream);//把数据刷新到内核当中//fsync(stream-fileno);return close(stream-fileno);
}
int my_fputs(const char*s,int num,myFILE* stream)
{//把字符写到缓冲区当中memcpy(stream-bufferstream-end,s,num);//从end位置处写入stream-endnum;//判断\n ,遇到就要刷新if(stream-flag Flushline stream-end0 (stream-buffer[num-1]\n)){my_fflush(stream); //行刷新}return stream-end;}
int my_fflush(myFILE*stream)
{ //若缓冲区为空就直接关闭if(stream-end0){close(stream-fileno);}//刷新写到文件中write(stream-fileno,stream-buffer,stream-end); stream-end0;return 0;
}