C++缓冲区

什么是缓冲区?

就是一段内存空间(有的是语言级别的缓冲区,有的是os级别的缓冲区)

缓冲区的刷新策略是一般+特殊:

普通:

1.立即刷新

2.行刷新(行缓冲)\n

3.满刷新(全缓冲)

特殊:

1.用户强制刷新(fflush)

2.进程退出

缓冲区的目的:提高整机效率,为了提高用户的响应速度。

顺丰快递

写透模式:WF。成本高,速度慢。(即时刷新)

写回模式:WB。成本低,快速(缓冲区)

一般而言:行缓冲的设备文件--显示器

全缓冲的设备文件--磁盘文件(效率考量)

所有的设备,永远都倾向于全缓冲!--缓冲区满了,才刷新-->>需要更少次的IO操作-->>更少次的外设的访问------>>提高效率。

同时我们需要注意的是,和外部设备IO的时候,数据量的大小不是主要矛盾,你和外设预备IO的过程是最耗费时间的!

其他刷新策略是,结合具体情况做的妥协!

显示器:直接给用户看的,一方面要照顾效率,一方面要照顾用户体验

极端情况,你是可以自定义规则的

由缓冲区引发的问题

// C语言提供的 printf("hello printf\n"); fprintf(stdout, "hello fprintf\n"); const char *s = "hello fputs\n"; fputs(s, stdout); // OS提供的 const char *ss = "hello write\n"; write(1, ss, strlen(ss)); //fork(); //创建子进程

这段代码加上一个fork就会引发问题

[hang@VM-4-12-centos day10]$ ./a.out hello printf hello fprintf hello fputs hello write [hang@VM-4-12-centos day10]$ ./a.out >tmp.txt [hang@VM-4-12-centos day10]$ cat tmp.txt hello write hello printf hello fprintf hello fputs hello printf hello fprintf hello fputs [hang@VM-4-12-centos day10]$

问题是什么呢??

同样的一个程序,向显示器打印输出4行文本,向普通文件(磁盘上),打印的时候,变成了7行,其中:C语言IO接口是打印了2次的,系统接口,只打印一次和向显示器打印一样!

曾经"我们所谈的缓冲区",绝对不是由OS提供的!! 如果是OS统一提供,那么我们上面的代码,表现应该是一样的!

这里我们应该明白是由C标准库来实现的。

其中会有一个问题,刷新是不是写时拷贝呢???是的,因为在刷新的过程中,数据从有到无,不就相当于改变了数据内容吗????这时候会发生写时拷贝。

write是系统调用接口,write之后直接刷新进入到了系统内部,变成了内核数据,剩下的就是操作系统的事了。

而我们的C语言的函数,底层是封装了系统调用接口的(write),那么C语言的缓冲区在哪里呢??

其实是在C标准库内部,有一段空间(buffer)。

值得注意的是,我们是在最后调用fork函数,fork函数上面的函数已经被执行完了,但这并不代表数据已经刷新完了!!!

  1. 1如果向显示器打印,刷新策略是行刷新,那么最后执行fork的时候—-一定足出数执行完了&&数据已经被刷新了!---------->>>此时的fork无意义。
  2. 如果你对应的程序进行了重定向--要向磁盘文件打印-->>>隐形的刷新策略变成了全缓冲! ----->\n边没有意义了---------->>>>fork的时候一定是函数执行完了,但是数据还没有刷新!! ——在当前进程对应的C标准库中的缓冲区中!! —-这部分数据是父进程的数据。然后在fork的过程中,形成了两个执行流,那么在其中一个执行流将缓冲区的数据刷新到磁盘中的时候,就会写时拷贝,从而导致父子进程数据分离,进而会打印出两份。

C标准库给我们提供的用户级缓冲区!! 在哪里??

C语言中,打开文件:FILE*fopen(const char *path,const char *mode) ;

由FILE* ----->> struct FILE,结构体-内部封装了fd,还包含了改文件fd对应的语言层的缓冲区结构!

我们自己设计缓冲区代码

#include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <assert.h> #include <stdlib.h> #define NUM 1024 struct MyFILE_{ int fd; char buffer[1024]; int end; //当前缓冲区的结尾 }; typedef struct MyFILE_ MyFILE; MyFILE *fopen_(const char *pathname, const char *mode) { assert(pathname); assert(mode); MyFILE *fp = NULL; if(strcmp(mode, "r") == 0) { } else if(strcmp(mode, "r+") == 0) { } else if(strcmp(mode, "w") == 0) { int fd = open(pathname, O_WRONLY | O_TRUNC | O_CREAT, 0666); if(fd >= 0) { fp = (MyFILE*)malloc(sizeof(MyFILE)); memset(fp, 0, sizeof(MyFILE)); fp->fd = fd; } } else if(strcmp(mode, "w+") == 0) { } else if(strcmp(mode, "a") == 0) { } else if(strcmp(mode, "a+") == 0) { } else{ //什么都不做 } return fp; } //是不是应该是C标准库中的实现! void fputs_(const char *message, MyFILE *fp) { assert(message); assert(fp); strcpy(fp->buffer+fp->end, message); //abcde\0 fp->end += strlen(message); //for debug printf("%s\n", fp->buffer); //暂时没有刷新, 刷新策略是谁来执行的呢?用户通过执行C标准库中的代码逻辑,来完成刷新动作 //这里效率提高,体现在哪里呢??因为C提供了缓冲区,那么我们就通过策略,减少了IO的执行次数(不是数据量) if(fp->fd == 0) { //标准输入 } else if(fp->fd == 1) { //标准输出 if(fp->buffer[fp->end-1] =='\n' ) { //fprintf(stderr, "fflush: %s", fp->buffer); //2 write(fp->fd, fp->buffer, fp->end); fp->end = 0; } } else if(fp->fd == 2) { //标准错误 } else { //其他文件 } } void fflush_(MyFILE *fp) { assert(fp); if(fp->end != 0) { //暂且认为刷新了--其实是把数据写到了内核 write(fp->fd, fp->buffer, fp->end); syncfs(fp->fd); //将数据写入到磁盘 fp->end = 0; } } void fclose_(MyFILE *fp) { assert(fp); fflush_(fp); close(fp->fd); free(fp); } int main() { //close(1); MyFILE *fp = fopen_("./log.txt", "w"); if(fp == NULL) { printf("open file error"); return 1; } fputs_("one: hello world", fp); fork(); fclose_(fp); }

可以完成重定向的shell

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/wait.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <assert.h> #define NUM 1024 #define SIZE 32 #define SEP " " //保存完整的命令行字符串 char cmd_line[NUM]; //保存打散之后的命令行字符串 char *g_argv[SIZE]; // 写一个环境变量的buffer,用来测试 char g_myval[64]; #define INPUT_REDIR 1 #define OUTPUT_REDIR 2 #define APPEND_REDIR 3 #define NONE_REDIR 0 int redir_status = NONE_REDIR; char *CheckRedir(char *start) { assert(start); char *end = start + strlen(start) - 1; //ls -a -l\0 while(end >= start) { if(*end == '>') { if(*(end-1) == '>') { redir_status = APPEND_REDIR; *(end-1) = '\0'; end++; break; } redir_status = OUTPUT_REDIR; *end = '\0'; end++; break; //ls -a -l>myfile.txt //ls -a -l>>myfile.txt } else if(*end == '<') { //cat < myfile.txt,输入 redir_status = INPUT_REDIR; *end = '\0'; end++; break; } else{ end--; } } if(end >= start) { return end; //要打开的文件 } else{ return NULL; } } // shell 运行原理 : 通过让子进程执行命令,父进程等待&&解析命令 int main() { extern char**environ; //0. 命令行解释器,一定是一个常驻内存的进程,不退出 while(1) { //1. 打印出提示信息 [whb@localhost myshell]# printf("[root@我的主机 myshell]# "); fflush(stdout); memset(cmd_line, '\0', sizeof cmd_line); //2. 获取用户的键盘输入[输入的是各种指令和选项: "ls -a -l -i"] // "ls -a -l>log.txt" // "ls -a -l>>log.txt" // "ls -a -l<log.txt" if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL) { continue; } cmd_line[strlen(cmd_line)-1] = '\0'; // 2.1: 分析是否有重定向, "ls -a -l>log.txt" -> "ls -a -l\0log.txt" //"ls -a -l -i\n\0" char *sep = CheckRedir(cmd_line); //printf("echo: %s\n", cmd_line); //3. 命令行字符串解析:"ls -a -l -i" -> "ls" "-a" "-i" // export myval=105 g_argv[0] = strtok(cmd_line, SEP); //第一次调用,要传入原始字符串 int index = 1; if(strcmp(g_argv[0], "ls") == 0) { g_argv[index++] = "--color=auto"; } if(strcmp(g_argv[0], "ll") == 0) { g_argv[0] = "ls"; g_argv[index++] = "-l"; g_argv[index++] = "--color=auto"; } //? while(g_argv[index++] = strtok(NULL, SEP)); //第二次,如果还要解析原始字符串,传入NULL if(strcmp(g_argv[0], "export") == 0 && g_argv[1] != NULL) { strcpy(g_myval, g_argv[1]); int ret = putenv(g_myval); if(ret == 0) printf("%s export success\n", g_argv[1]); //for(int i = 0; environ[i]; i++) // printf("%d: %s\n", i, environ[i]); continue; } //for debug //for(index = 0; g_argv[index]; index++) // printf("g_argv[%d]: %s\n", index, g_argv[index]); //4.内置命令, 让父进程(shell)自己执行的命令,我们叫做内置命令,内建命令 //内建命令本质其实就是shell中的一个函数调用 if(strcmp(g_argv[0], "cd") == 0) //not child execute, father execute { if(g_argv[1] != NULL) chdir(g_argv[1]); //cd path, cd .. continue; } //5. fork() pid_t id = fork(); if(id == 0) //child { if(sep != NULL) { int fd = -1; //说明命令曾经有重定向 switch(redir_status) { case INPUT_REDIR: fd = open(sep, O_RDONLY); dup2(fd, 0); break; case OUTPUT_REDIR: fd = open(sep, O_WRONLY | O_TRUNC | O_CREAT, 0666); dup2(fd, 1); break; case APPEND_REDIR: //TODO fd = open(sep, O_WRONLY | O_APPEND | O_CREAT, 0666); dup2(fd, 1); break; default: printf("bug?\n"); break; } } // printf("下面功能让子进程进行的\n"); // printf("child, MYVAL: %s\n", getenv("MYVAL")); // printf("child, PATH: %s\n", getenv("PATH")); //cd cmd , current child path //execvpe(g_argv[0], g_argv, environ); // ls -a -l -i //不是说好的程序替换会替换代码和数据吗?? //环境变量相关的数据,会被替换吗??没有! execvp(g_argv[0], g_argv); // ls -a -l -i exit(1); } //father int status = 0; pid_t ret = waitpid(id, &status, 0); if(ret > 0) printf("exit code: %d\n", WEXITSTATUS(status)); } }

再回到当初的问题,

#include <iostream> #include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> int main() { close(1); int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666); if(fd < 0) { perror("open"); return 0; } printf("hello world: %d\n", fd); // stdout -> 1 -> 数据会暂存在stdout的缓冲区中 //fflush(stdout); //? //const char *msg = "hello wrold\n"; //write(fd, msg, strlen(msg)); close(fd); //fd -> 1: 数据在缓冲区中,但是对应的fd先关了,数据变无法刷新了! return 0; }

这段代码的本意是将hello world写入到log.txt中,但实际上呢,我们发现并没有写到文件中。这是为啥呢??

因为数据在缓冲区中,但是对应的fd先关了,数据变无法刷新了!所以fllush一下就解决问题了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值