当前位置: 首页 > news >正文

做的网站如何更换网站模板网站伪静态设置

做的网站如何更换网站模板,网站伪静态设置,page文件转换wordpress,php做网站答辩问题文件系统 前言一、C语言文件接口二、系统文件接口三、文件描述符四、struct file 对象五、stdin、stdout、stderr六、文件描述符的分配规则七、重定向1. 重定向的原理2. dup23. 重谈 stderr 八、缓冲区1. 缓冲区基础2. 深入理解缓冲区3. 用户缓冲区和内核缓冲区4. FILE 前言 首… 文件系统 前言一、C语言文件接口二、系统文件接口三、文件描述符四、struct file 对象五、stdin、stdout、stderr六、文件描述符的分配规则七、重定向1. 重定向的原理2. dup23. 重谈 stderr 八、缓冲区1. 缓冲区基础2. 深入理解缓冲区3. 用户缓冲区和内核缓冲区4. FILE 前言 首先我们在前面的学习中知道了 文件 内容 属性那么我们对文件的操作就是分别对内容和属性操作。 当我们要访问一个文件的时候都是先要把这个文件打开那么是谁把文件打开呢答案是进程打开文件在打开文件前文件是存放在磁盘上打开文件后文件被进程加载到内存中。 一个进程可以通过操作系统打开一个文件也可以打开多个文件所以操作系统一定会给进程提供系统调用接口去打开文件这些文件被加载到内存中可能会存在多个同时加载磁盘上的文件一定会涉及到访问磁盘设备这些操作由操作系统来完成所以在操作系统运行中可能会打开很多个文件那么此时操作系统就要将打开的文件进行管理怎样管理呢我们前面也学过先描述再组织 一个文件要被打开一定要现在内核中形成被打开的文件对象 一、C语言文件接口 我们简单复习一下C语言阶段使用的文件接口其中详细的内容链接 - 文件操作. 其中我们复习一下 fopen 和 fputs这个接口如下介绍 其中 fopen 中的 path 是我们需要打开的文件mode 是以什么样的方式打开。下面我们分别使用一下以 w 和 a 方式打开代码如下 1 #include stdio.h2 3 int main()4 {5 FILE* fp fopen(test.txt, w); 6 if(fp NULL)7 {8 perror(fopen);9 return 1;10 }11 12 const char* str aaaaaaaaaaaaaaaaaa\n;13 int cnt 10;14 while(cnt)15 {16 fputs(str, fp);17 cnt--;18 }19 20 fclose(fp);21 return 0;22 }其中以 “w” 方式打开是按照写方式打开如果文件不存在就创建它并且以 “w” 方式打开会先清空文件内容 例如下面场景我们先运行程序打开 test.txt 写入内容再重定向重新写入内容会发现原有内容已经被清空了 当以 “a” 方式打开代码如下 1 #include stdio.h2 3 int main()4 {5 FILE* fp fopen(test.txt, a); 6 if(fp NULL)7 {8 perror(fopen);9 return 1;10 }11 12 const char* str aaaaaaaaaaaaaaaaaa\n;13 int cnt 10;14 while(cnt)15 {16 fputs(str, fp);17 cnt--;18 }19 20 fclose(fp);21 return 0;22 }结果如下所以我们得出结论“a” 方式是从文件结尾出开始写入即追加不清空文件内原有内容 所以我们得出以 “w” 方式打开文件相当于重定向写入即 echo xxx filename以 “a” 方式打开文件相当于追加重定向写入即 echo xxx filename. 二、系统文件接口 接下来我们认识一下系统给我们提供的系统文件接口 open下面看一下文档介绍 其中 open 的返回值是 fd(文件描述符)如下图介绍 我们可以看到如果文件创建成功会返回新文件的文件描述符关于文件描述符我们下面再介绍。 其中 open 系统接口第一个参数 pathname 我们都知道就是需要打开文件的名字关于第二个参数我们需要介绍一下关于函数传入标志位的技巧是 Linux 中常用的传参方式例如我们想在函数传参的时候传入指定的宏它就会帮我们执行对应的宏的指令如下代码 1 #include stdio.h 2 3 #define Print1 1 // 0001 4 #define Print2 (11) // 0010 5 #define Print3 (12) // 01006 #define Print4 (13) // 10007 8 void Print(int flags)9 {10 if(flags Print1) printf(hello 1\n); 11 if(flags Print2) printf(hello 2\n);12 if(flags Print3) printf(hello 3\n);13 if(flags Print4) printf(hello 4\n);14 }15 16 17 int main()18 {19 Print(Print1);20 printf(\n);21 Print(Print1 | Print2);22 printf(\n);23 Print(Print1 | Print2 | Print3);24 printf(\n);25 Print(Print3 | Print4);26 printf(\n);27 Print(Print4); 28 return 0;29 }结果如下 所以 open 的第二个参数实际上是一些系统定义的宏定义在 open 的介绍文档中有介绍如下图 当我们想要以什么方式打开该文件时就传入对应的宏定义这就是 open 的第二个参数。我们先简单介绍几个宏定义 O_RDONLY: 只读打开O_WRONLY: 只写打开O_RDWR : 读写打开以上三个宏必须指定一个且只能指定一个O_CREAT : 若文件不存在则创建它。需要使用 mode 选项来指明新文件的访问权限O_APPEND: 追加写O_TRUNC: 打开后清空原内容接下来我们先介绍另一个系统接口 write我们先看看 man 手册如下 如上图write 的参数列表比较好理解第一个参数 fd 就是需要写入文件的文件描述符第二个参数 buf 就是需要写入的字符串第三个参数 count 就是需要写入的个数注意这里不需要把 \0 的个数加上去只需要将需要写入字符串的个数填上即可。 下面我们配对使用 open 和 write 如下代码 int main(){int fd open(test.txt, O_WRONLY | O_CREAT | O_TRUNC);if(fd 0){perror(open);return 1;}printf(fd: %d\n, fd);const char* str xxxxx\n;write(fd, str, strlen(str));close(fd);return 0;}当我们运行起程序再查看文件属性的时候会发现以下现象 此时我们创建的 test.txt 的权限是乱码的为什么呢这就和 open 的第三个参数 mode 有关了所以我们现在知道 mode 就是需要改写的权限以八进制形式传入下面我们对上面代码做修改 int main(){int fd open(test.txt, O_WRONLY | O_CREAT | O_TRUNC, 0666);if(fd 0){perror(open);return 1;}printf(fd: %d\n, fd);const char* str xxxxx\n;write(fd, str, strlen(str));close(fd);return 0;}再次运行程序 此时我们发现 test.txt 的权限就恢复正常了由于有权限掩码的原因权限为 0664. 此时我们查看 test.txt 中的内容 如果我们往文件中追加内容再重新打开文件的话原有的内容会被清掉因为我们传的是 O_TRUNC即打开后清空原内容 例如下图 当我们将代码中的 open(test.txt, O_WRONLY | O_CREAT | O_TRUNC, 0666) 改为 open(test.txt, O_WRONLY | O_CREAT | O_APPEND, 0666) 后重新再执行程序后就可以追加内容了如下图 三、文件描述符 通过上面的学习我们可以开始理解语言和系统的理解了我们在上面使用的 FILE* fp fopen(test.txt, w); 是C语言库函数的接口它对应的是系统接口 int fd open(test.txt, O_WRONLY | O_CREAT | O_TRUNC, 0666); 而 FILE* fp fopen(test.txt, a); 对应的是系统接口 int fd open(test.txt, O_WRONLY | O_CREAT | O_APPEND, 0666);那么我们得出结论因为文件只能通过操作系统去访问不能直接通过语言去访问所以 fopen 的底层一定是对 open 的封装 同时我们肯定会对 fd(文件描述符) 很感兴趣那么它到底是什么呢下面我们通过创建多个文件观察 open 的返回值 fd如下代码 int main(){int fd1 open(test1.txt, O_WRONLY | O_CREAT | O_APPEND, 0666);int fd2 open(test2.txt, O_WRONLY | O_CREAT | O_APPEND, 0666);int fd3 open(test3.txt, O_WRONLY | O_CREAT | O_APPEND, 0666);int fd4 open(test4.txt, O_WRONLY | O_CREAT | O_APPEND, 0666);printf(fd1: %d\n, fd1);printf(fd2: %d\n, fd2);printf(fd3: %d\n, fd3);printf(fd4: %d\n, fd4);close(fd1);close(fd2);close(fd3);close(fd4);return 0;}结果如下 我们可以看到连续创建文件时 fd 是一串连续的数字类似于数组的下标。 四、struct file 对象 下面我们开始理解文件在操作系统中的表现当一个进程需要打开一个在磁盘上的文件时此时操作系统内可能会存在多个被打开的文件那么这些文件需要被操作系统通过先描述再组织的形式管理起来那么当操作系统需要打开一个文件的时候需要为每个文件创建一个文件对象在 Linux 中被创建的结构体对象叫做 struct file即是被打开文件的描述结构体对象。 如果我们打开了很多个文件那么每个文件都有它自己的 struct file每个文件可能都由不同的进程打开的但是最终操作系统都要将这些 struct file 管理起来那么就可以通过双链表的形式将它们连接起来管理所以从此往后对打开文件的管理就转化成了对双链表的增删查改的管理。 那么操作系统如何知道哪个文件是由哪个进程打开的呢所以操作系统不得不面临一个问题如何维护进程和打开文件的对应关系所以操作系统内核中它为进程设计了一个结构体struct files_struct 该结构体也叫做进程文件描述符表进程文件描述符表里有一个非常重要的数组struct file* fd_array[]即一个数组指针。此时当一个进程打开一个文件时操作系统会为该文件创建一个 struct file 对象再把该对象的地址填入到 struct file* fd_array[] 中没有被使用的数组下标中最后把该数组的下标返回给上层其中这个数组的下标就是我们的 fd(文件描述符) 上面所说的理论过程可以通过下图进行理解 下面我们重新理解一下 struct file 对象那么 struct file 对象里面应该有什么呢答案是内容属性。首先我们需要知道 struct file 对象中有一些字段可以直接获得文件的所有属性直接或间接包含如下属性在磁盘的什么位置权限、大小、读写位置等。除此之外struct file 内还需要有自己对应的文件缓冲区也就是说这是一段内存空间。当我们的文件没有被打开的时候它是存放在磁盘当中的当我们打开这个文件时内核需要帮我们创建 struct file 对象因为在内核中struct file 是专门用来管理被打开文件的 当进程需要读取文件数据的时候必然是先要将数据加载到内存空间中即 struct file 的文件缓冲区中如果我们需要向这个文件中写入呢同样地我们也同样先需要将数据加载到内存中在内存中进行对文件数据的增删查改不能直接在磁盘中进行因为需要根据冯诺依曼体系这个过程是操作系统帮我们完成的因为操作系统是硬件的管理者 所以我们在应用层进行数据读写的时候本质是什么呢其实本质就是将内核缓冲区中的数据进行来回拷贝该过程如下图所示 首先如果我们需要写入数据我们根据进程找到相应的进程描述符表根据文件描述符找到对应的 struct_file 对象再将我们写入数据的 buffer 缓冲区拷贝到对应 struct_file 对象中的文件缓冲区中然后操作系统会帮我们刷新缓冲区中的内容这就完成了文件数据的写入。 同样地如果需要读取文件数据操作系统帮我们将文件的数据加载到文件缓冲区中我们根据文件描述符找到对应的 struct_file 对象中的文件缓冲区再将文件缓冲区中的内容拷贝到我们的 buffer 缓冲区中即可完成读取数据。 五、stdin、stdout、stderr 在该数组指针中为什么默认填入的 struct file 对象的地址是从下标 3 开始的呢0、1、2 去哪里了呢其实在进程运行的时候默认把标准输入(键盘stdin)、标准输出显示器stdout、标准错误显示器stderr 打开了它们分别代表 0、1、2那么它们也是文件吗没错因为 Linux 下一切皆文件这个我们下面再解释。 首先我们要知道操作系统访问一个文件时只认文件描述符即只能通过文件描述符访问其次我们回顾C语言的文件接口中返回值是 FILE*那么FILE又是什么呢其实它是C语言提供的结构体类型而操作系统访问文件只认文件描述符所以我们肯定FILE结构体中必定封装了文件描述符下面我们可以通过代码验证一下首先 stdin、stdout、stderr 的返回值都是 FILE*如下图所以我们可以通过它们的返回值观察 代码如下 int main(){printf(stdin-fd: %d\n, stdin-_fileno);printf(stdout-fd: %d\n, stdout-_fileno);printf(stderr-fd: %d\n, stderr-_fileno);}结果如下 如上图就验证了我们是对的。那么操作系统为什么要默认把 stdin、stdout、stderr 打开呢答案是为了让程序员默认进行输入输出代码编写 那么我们现在就要回答上面的问题了如何理解Linux下一切皆文件呢首先我们的外设设备键盘、显示器、磁盘、网卡…它们都有自己对应的读写方法而且方法肯定不一样的。 当操作系统打开键盘文件时创建了对应的 struct file 对象那么键盘的结构体对象中会有键盘对应的读写的函数指针该函数指针指向的是键盘底层对应的读写方法同理当系统打开显示器、磁盘、网卡等文件也是如此。所以这时候我们就可以忽略底层硬件读写方法的差异只需要关注软件层 struct file 对象中的有关读写的函数指针即可在操作系统看来它们的读写方法都是一样的因为每个 struct file 对象中都会有这样的指针而该软件层可以称为VFS(虚拟文件系统 )所以从这一层往上面看就要可以看作一切皆文件这种情况我们可以看作使用C语言实现了继承和多态 六、文件描述符的分配规则 由于系统默认把 fd 中的 0、1、2 打开了所以默认地我们打开一个文件的时候它的 fd 会是从 3 开始这毫无疑问。 接下来我们再认识一个系统接口read我们先看一下手册 如上图read 就是根据 fd 来进行读取文件缓冲区的数据将数据读到 buf 中count 就是 buf 的大小也就是我们期望读到多少数据返回值就是实际读到多少的数据。 假设我们现在需要在键盘里面读取数据读到什么就打印什么如下代码 #include stdio.h#include sys/types.h#include sys/stat.h#include fcntl.h#include unistd.hint main(){char buffer[1024];ssize_t s read(0, buffer, 1024);if(s 0){buffer[s-1] 0;printf(echo# %s\n, buffer);}return 0;}所以我们也侧面印证了系统是默认帮我们打开了0号文件描述符标准输入的。 接下来我们再认识一个系统接口write就是从对应的 fd 写入数据我们先看手册 接下来我们尝试往标准输出写入数据如下代码 int main(){char buffer[1024];ssize_t s read(0, buffer, 1024);if(s 0){buffer[s-1] 0;write(1, buffer, strlen(buffer));//printf(echo# %s\n, buffer);}return 0;}如上结果我们可是从来没有打开过 fd 为0或者1的文件所以就证明了系统是默认打开了标准输入、标准输出和标准错误。 所以我们得出第一个结论进程默认已经打开了0、1、2我们可以直接使用0、1、2进行数据的访问 接下来我们验证另一个问题当我们关闭0号 fd 时再打开一个文件时会给该文件分配哪一个 fd 呢下面我们验证一下 int main(){close(0);int fd open(log.txt, O_CREAT | O_WRONLY | O_TRUNC, 0666);if(fd 0){perror(open faild);return 1;}printf(fd:%d\n, fd);close(fd);return 0;}结果如下 同样地如果我们将 2号 fd 关闭时再打开一个文件它的 fd 也是 2这说明了什么呢 我们得出第二个结论文件描述符的分配规则是寻找最小的没有被使用的数据的位置分配给指定的打开文件 如果我们关闭 1号 fd 呢如果我们关闭 1号 fd再重新打开一个文件并把它的 fd 打印出来我们会发现什么都没有打印出来这是为什么呢很简单因为我们把 1号 fd 关闭了把标准输出关闭了 七、重定向 1. 重定向的原理 上面我们尝试过将 1号 fd 关闭后重新打开一个文件再打印数据会发现什么都没有打印出来但是我们将代码做一下修改如下 int main(){close(1);int fd open(log.txt, O_CREAT | O_WRONLY | O_TRUNC, 0666);if(fd 0){perror(open faild);return 1;}printf(fd: %d\n, fd);printf(stdout-fd: %d\n, stdout-_fileno);fflush(stdout);close(fd);return 0;}我们打印多了一个语句并且强制刷新了缓冲区下面我们观察现象 如上图我们直接执行程序没有打印任何信息但是我们读取 log.txt 的时候发现怎么信息都往 log.txt 里面打印了呢下面我们画一个图理解一下 如上图我们首先关闭了 1号 fd然后打开 log.txt根据文件描述符的分配规则给 log.txt 分配的应该是 1号 fd所以当前 1号 fd 是指向了 log.txt 的。而 printf 这样的接口只认 stdout而 stdout 的 fileno 为1即 printf 只认文件描述符 1所以在上层 printf 打印的时候只会往 1号 fd 里面打印的而具体地这个 1号 fd 如果指向显示器它就往显示器里打印如果指向 log.txt就往 log.txt 里打印 其实这就是重定向的原理如果我们需要完成重定向功能其实只需要将文件描述符下标不变将数组中的内容发生改变即可也就是说 1号 fd 以前指向的是显示器我们只需要将新文件的地址填入 1号 fd 中就会直接向新文件中写入了 所以重定向的本质其实就是修改特定文件描述符中的下标内容 上面我们所实现的是输出重定向的功能如果我们需要实现追加重定向呢很简单只需要修改 open 接口中的参数即可将 O_TRUNC 改成 O_APPEND 即可如下 int main(){close(1);int fd open(log.txt, O_CREAT | O_WRONLY | O_APPEND, 0666);if(fd 0){perror(open faild);return 1;}printf(fd: %d\n, fd);printf(stdout-fd: %d\n, stdout-_fileno);fflush(stdout);close(fd);return 0;}结果如下图所示 接下来我们尝试一下关闭0号 fd 后再打开 log.txt从 stdin 中读取数据即实现输入重定向如下代码 int main(){close(0);int fd open(log.txt, O_RDONLY);if(fd 0){perror(open faild);return 1;}char buffer[1024];fread(buffer, 1, sizeof(buffer), stdin);printf(%s\n, buffer);close(fd);return 0;}如上我们使用了 fread 接口我们可以看一下手册 结果如下 如上图本应该从标准输入键盘中读取数据的但是由于我们关闭了 0号 fd再打开 log.txt所以最后从 log.txt 中读取了数据。 2. dup2 但是以上方式实现的重定向太麻烦了每次都要关闭文件再重新打开文件有没有简洁一点的方式呢答案是有的我们知道重定向的本质就是将文件描述符表中的下标不变改变数组中的内容即可那么我们是不是就可以正常打开一个文件再将该文件中的文件描述符表中的内容拷贝到 0 号、1 号、2 号 fd 中呢例如下图 那么我们是否有一些接口可以帮助我们完成文件描述符级别的数组中的内容拷贝呢答案是有的以输出重定向为例我们只需要将新打开的文件的数组中的地址拷贝到 1号 fd 中的数组内容中即可。 这样的接口叫做 dup2 下面我们认识一下这个接口先看一下手册 现在我们以输出重定向为例我们观察 dup2 的参数我们应该如何传入参数呢我们观察该函数的描述得知最后保留下来的应该是 oldfd所以我们以输出重定向为例3号 fd 就是保留下来的1号 fd 就是被覆盖的所以 fd 就是 oldfd1就是 newfd即 dup(fd, 1); 那么会有一些问题例如我们将 fd 的内容拷贝到 1号 fd 后是不是有两个文件指针指向 log.txt 呢是的那么 log.txt 怎么知道有几个文件指针指向自己呢那么其中一个文件指针把 log.txt 关了会不会影响另外一个正在使用 log.txt 的文件指针呢答案是不会的因为在内核当中存在引用计数该引用计数记录的是有多少个文件指针指向自己当有一个文件指针把它关闭之后引用计数会减一并不会直接关闭文件当引用计数为 0 时说明已经没有文件指针指向自己这时候才可以关闭文件。 然后我们使用 dup2() 实现一下输出重定向 int main(){int fd open(log.txt, O_WRONLY | O_CREAT | O_TRUNC, 0666);if(fd 0){perror(open faild);return 1;}dup2(fd, 1);printf(hello printf\n);fprintf(stdout, hello fprintf\n);close(fd);return 0;}结果如下 但是这跟我们以前用的重定向有什么区别呢我们以前用的重定向如下 我们在命令行是这样使用的但是这跟我们上面学的又有什么关系呢首先我们执行的命令行都是进程在执行首先要对命令行进行解析如果识别到命令行中有 , , 这些符号说明该指令是要具备重定向功能的一旦识别到该指令是要具备重定向功能的那么前半部分就是需要执行的指令后半部分是我们需要重定向的目标文件。 所以我们可以修改以前模拟实现的 shell使其具备重定向功能其中简单实现重定向的 shell 的源码地址为myshell. 3. 重谈 stderr 那么对于标准输入和标准输出相信我们已经很熟悉了但是为什么要存在标准错误呢尤其它指向的也是显示器我们先来看看它们都是往显示器上打印信息如下 int main(){fprintf(stdout, hello, stdout\n);fprintf(stderr, hello, stderr\n);return 0;}如果我们将可执行程序进行输出重定向呢如下所示 如图为什么 hello, stderr 还会打印出来呢很简单我们的输出重定向只是将 hello, stdout 的信息重定向进 log.txt 里面去了不关 stderr 的事所以它还是会打印到显示器上。 如果我们需要将 stdout、stderr 打印到一个文件里应该怎么做呢此时我们需要在后面加上 21即如下 这又是什么意思呢21 我们可以理解成将 1号 fd 数组中的内容放到 2号 fd 中即现在 2号 fd 中数组的文件指针也指向了 log.txt. 那么为什么要有 stderr 呢其实我们现在写的重定向如./test log.txt 是一种简略的写法它的正常的写法应该是./test 1 log.txt如下 我们理解了 21 之后再看上面的写法应该就不难理解了。那么我们还可以像下面这样写注意 21 中间不能有空格 所以我们还可以写成下面这样./test 1 log.txt 2log.txt.error 此时我们就形成了两个文件 这样写我们就能理解了因为我们在进行重定向的时候我们把 1号 fd 重定向到 log.txt 文件把 2号 fd 重定向到 log.txt.error 文件里这样我们以后写程序时printf 打印的是常规信息perror 打印的是错误信息这样我们就可以将正确的信息和错误的信息分别保存到两个不同的文件中 八、缓冲区 1. 缓冲区基础 首先我们需要知道缓冲区是什么呢我们在前面也有所了解过如下图 当我们需要进行文件写入或者读取文件时实际上就是将我们自定义的 buffer 缓冲区写入到C库为我们提供的缓冲区然后再由C库的缓冲区根据 fd 找到文件对应的文件缓冲区进行写入或读取。本质上缓冲区就是一部分内存 我们可以举一个例子来比喻缓冲区我们可以将缓冲区比喻成我们平时的“菜鸟驿站”“菜鸟驿站”是帮我们将快递运输到另一个地方而缓冲区则是将数据运输至操作系统中的文件缓冲区因为有缓冲区的存在我们可以积累一部分信息再统一发送这也就提高了发送的效率所以缓冲区的主要作用是提高效率提高使用者的效率 缓冲区因为能够暂存数据所以必定有一定的刷新方式其中有以下几种 无缓冲(立即刷新)行缓冲(行刷新)全缓冲(缓冲区满了再刷新) 以上是一般的策略当然也有特殊情况例如 强制刷新如 \n 和 fflush()进程在退出的时候一般要进行刷新缓冲区。 而一般对于显示器文件默认是进行行刷新对于磁盘上的文件默认是全缓冲即缓冲写满再刷新。 2. 深入理解缓冲区 下面我们看一个例子如下代码 int main(){fprintf(stdout, C: hello fprintf\n);printf(C: hello printf\n);fputs(C: hello fputs\n, stdout);const char* str system call: hello write\n;write(1, str, strlen(str));return 0;}我们分别调用了三个C语言的接口fprintf、printf、fputs和一个系统接口write我们使用这几个接口往显示器上打印信息结果如下 如上图可以打印到正确的结果。接下来我们对上面的代码进行一些修改如下 int main(){fprintf(stdout, C: hello fprintf\n);printf(C: hello printf\n);fputs(C: hello fputs\n, stdout);const char* str system call: hello write\n;write(1, str, strlen(str));fork();return 0;}我们只在最后加上了 fork() 创建子进程我们观察运行的结果 结果也是没有问题的但是我们将执行结果输出重定向到一个文件中的时候呢如下 如上图为什么重定向到 log.txt 就会发生上面的现象呢这里涉及到的问题是有点多的我们一个一个来。 首先当我们直接向显示器打印的时候显示器文件的刷新方式是行刷新而且我们的代码中输出的所有字符串都有 \n所以 fork() 之前数据全部已经被刷新包括 system call所以 fork() 之后缓冲区已经被清空了即使进程退出需要刷新缓冲区也没有数据可刷新了当我们重定向到 log.txt 的时候本质是向磁盘文件中写入系统对于数据的刷新方式已经由行刷新变成了全缓冲全缓冲意味着缓冲区变大实际写入的简单数据不足以把缓冲区写满即使有 \n 强制进行行刷新但是由于此时系统默认的刷新方式是全缓冲所以当 fork() 执行的时候此时缓冲区的数据依旧在缓冲区中我们可以看到分别向显示器和文件中打印的时候系统接口 system call 始终都是只打印一次反而是C语言的接口在变化所以我们得出结论我们目前所谈的“缓冲区”和操作系统和系统接口是没有关系的由于fprintf printf 和 fputs 底层都是对 write 的封装所以引起这些变化的只能和C语言本身有关所以我们平时用得最多的其实是 C/C 提供的语言级别的缓冲区也就是用户缓冲区C/C 提供的缓冲区里面一定保存的是用户的数据属于当前进程在运行时自己的数据如果我们把数据交给了操作系统即通过语言级别的缓冲区刷新到文件缓冲区中后这个数据就属于操作系统了不属于进程了当进程退出的时候一般要进行刷新缓冲区即便数据没有满足刷新条件那么刷新缓冲区属不属于清空或者“写入”操作呢属于当我们的 fork() 之后总有一个进程需要先退出子进程或者父进程取决于OS的调度那么当任意一个进程退出的时候就要对缓冲区进行刷新即发生了写入操作就要发生写时拷贝但是我们发现发生写时拷贝的都是C语言的接口系统接口write并没有发生写时拷贝即没有使用C语言的缓冲区这是为什么呢很简单系统调用接口是在C语言之下的它看不到语言级别的缓冲区而是将数据直接写入到操作系统的缓冲区了所以该数据就不属于进程了也就不会发生写时拷贝了 3. 用户缓冲区和内核缓冲区 我们通过上面得知日常我们用得最多的其实是C/C提供的语言级别的缓冲区下面我们画一个图理解一下这两者的关系 上图就是我们的代码写入到文件中的过程以及缓冲区刷新的理解但是为什么要有这个过程呢我们直接使用 printf 写入到文件里不行吗我们上面说过缓冲区的存在就是提高使用者的效率如果 printf 直接往操作系统里面写会涉及到用户直接拷贝到操作系统里这个过程的成本可远比拷贝到C语言缓冲区中再拷贝到操作系统的成本要大因为缓冲区积累一定量后再往操作系统写入和用户一次一次往操作系统里写入明显前者效率更高提高了 printf 的调用效率所以 printf 只需要将我们用户定义的缓冲区写入到C语言的缓冲区由于大家都是同一个级别的拷贝所以过程也很简单很快。至于C语言缓冲区它可以积累上一段时间再一次性写入到操作系统中只跑一次就能大大提高效率 那么我们为什么要提高 printf 的调用效率呢其实 printf 只是众多 IO效率的一个代表还有许多接口例如fprintf fputs 和 scanf 等等都需要提高所以C语言上所有IO级别的接口都需要提供缓冲区都需要提高它们的效率这样我们写C语言的时候调用接口的效率就会非常快从而使我们代码的效率就非常快了。 4. FILE 通过以前的学习我们知道任何情况下我们输入输出的时候都要有一个 FILE 结构体。 因为 IO 相关函数与系统调用接口对应并且库函数封装系统调用所以本质上访问文件都是通过 fd 访问的。所以C库当中的 FILE 结构体内部必定封装了 fd。既然 FILE 结构体内部已经封装了 fd 了那么 FILE 里面也就一定为我们提供了一段缓冲区所以缓冲区是在 FILE 结构体里所以当我们任意打开一个文件都会有一个 FILE 结构体也就是任意一个文件都要在C标准库里通过 FILE 来为我们创建一个属于它自己的文件级别的用户级缓冲区所以有几个文件被打开就有几个 FILE就有几个缓冲区 例如我们可以在 /usr/include/stdio.h 路径下的头文件中找到 FILE 结构体的 typedef 我们还可以在 /usr/include/libio.h 路径下的头文件中找到 FILE 结构体的定义 基于上面的认识我们可以简单实现一个自己的C库函数当然我们只是为了更好地了解底层只是认识级别的实现源码链接my_Clib.
http://www.pierceye.com/news/697796/

相关文章:

  • 英雄联盟网站源码开发设计公司
  • 企业形象网站开发名师工作室网站建设
  • o2o网站建设最好公司排名做竹鼠网站
  • 免费做网站软件2003商丘网络营销服务
  • 杭州网站建设加q479185700如何网上外贸接单
  • 针对茅台酒企业网站建设方案鸿基建设工程有限公司网站
  • 有创意营销型网站建设wordpress 慢集市
  • 注册网站多少钱永康电子商务网站建设
  • 江西省网站建设庆阳在线网
  • wordpress建站微信联系智慧政务网站怎么做
  • 邯郸购物网站建设电子商务是干什么的工作
  • 网站开发竞聘报告wordpress彩色标签云设置方法
  • 深圳高端网站建设收费宿州百度seo排名软件
  • 彩妆网站建设报告公司网页是什么
  • 站长之家综合查询工具广州网站网站建设
  • 网站开发如何实现数据库的链接同性性做视频网站
  • 网站建设uuiop网站建设盐城最便宜
  • iss怎么做网站一个旅游网站建设
  • 润州网站建设网址生成
  • 备案网站服务内容域名网站打开慢
  • 做网站域名备案需要多久企业年金查询官网
  • 制作企业网站的公司如何制作广告
  • 兰州网站优化excel做网站链接
  • 代做单片机毕业设计网站广州网站建设小程序开发
  • 深圳网站官网建设方案安阳做网站电话
  • 批量扫dedecms做的网站哪个网站可以兼职做效果图
  • 建网站与建网页的区别wordpress 七牛云 cdn
  • 专门做养老院的网站城市文化网站开发背景
  • html5写的网站有什么好处淄博网站制作制作
  • 服装设计素材网站大全做旅游的网站的要素