帮助文档
专业提供香港服务器、香港云服务器、香港高防服务器租用、香港云主机、台湾服务器、美国服务器、美国云服务器vps租用、韩国高防服务器租用、新加坡服务器、日本服务器租用 一站式全球网络解决方案提供商!专业运营维护IDC数据中心,提供高质量的服务器托管,服务器机房租用,服务器机柜租用,IDC机房机柜租用等服务,稳定、安全、高性能的云端计算服务,实时满足您的多样性业务需求。 香港大带宽稳定可靠,高级工程师提供基于服务器硬件、操作系统、网络、应用环境、安全的免费技术支持。
服务器资讯 / 香港服务器租用 / 香港VPS租用 / 香港云服务器 / 美国服务器租用 / 台湾服务器租用 / 日本服务器租用 / 官方公告 / 帮助文档
【Linux】基础IO —— 缓冲区深度剖析
发布时间:2024-02-27 20:43:47   分类:帮助文档
【Linux】基础IO —— 缓冲区深度剖析 🌈欢迎来到Linux专栏~~基础IO (꒪ꇴ꒪(꒪ꇴ꒪ )🐣,我是Scort目前状态:大三非科班啃C++中🌍博客主页:张小姐的猫~江湖背景快上车🚘,握好方向盘跟我有一起打天下嘞!送给自己的一句鸡汤🤔:🔥真正的大师永远怀着一颗学徒的心作者水平很有限,如果发现错误,可在评论区指正,感谢🙏🎉🎉欢迎持续关注! 基础IO 🌈欢迎来到Linux专栏~~基础IO一. 缓冲区🌈缓冲区是什么🌈为什么要引入缓冲器🌈缓冲区的初步认识🌈解疑答惑🥑缓冲区是谁提供的🥑用户级缓冲区在哪里? 🌏设计用户层缓冲区的代码 ~ 实战💢`struct file`的设计💢主函数💢接口实现💢附源码 📢写在最后 一. 缓冲区 🌈缓冲区是什么 💦缓冲区 (buffer),它是内存空间的一部分。 也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区,显然缓冲区是具有一定大小的 🌈为什么要引入缓冲器 高速设备与低速设备的不匹配(cpu运算是纳秒,内存是微秒,磁盘是毫秒甚至是秒相差1000倍),势必会让高速设备花时间等待低速设备,我们可以在这两者之间设立一个缓冲区 💥举个例子:(顺丰就是缓冲区) 可以解除两者的制约关系,数据可以直接送往缓冲区,高速设备不用再等待低速设备,提高了计算机的效率可以减少数据的读写次数,如果每次数据只传输一点数据,就需要传送很多次,这样会浪费很多时间,因为开始读写与终止读写所需要的时间很长,如果将数据送往缓冲区,待缓冲区满后再进行传送会大大减少读写次数,这样就可以节省很多时间。例如:我们想将数据写入到磁盘中,不是立马将数据写到磁盘中,而是先输入缓冲区中,当缓冲区满了以后,再将数据写入到磁盘中,这样就可以减少磁盘的读写次数,不然磁盘很容易坏掉 总的来说: 🌈缓冲区的初步认识 ⚡缓冲区刷新策略!(一般+特殊) 立即刷新行刷新(行缓冲) \n满刷新(全缓冲)特殊情况:用户强制刷新(fflush)、进程退出(必须刷新) 一般而言 ,行缓冲的设备文件 —— 显示器 全缓冲的设备文件 —— 磁盘文件 💦所以的设备,永远都倾向于全缓冲!(倾向于,但不绝对) —— 缓冲区满了,才刷新 —— 需要更少次的IO操作 —— 也就是更少次的外设访问(1次IO vs 10次IO)—— 也就可以提高效率 🌈其他刷新策略是结合具体情况做的妥协! 显示器:直接给用户看的,一方面要照顾效率,一方面要照顾用户的体验( 极端情况,可以自定义规则的)磁盘文件:用户不需要立马看见文件的内容,可以把缓冲区写满再输出,更加注重效率的考量 我们可能有疑问:1000个字节,刷一次是1000个字节,刷十次整体也是1000个字节,哪里效率高呢❓ 👍和外设进行沟通IO的时候,数据量的大小不是主要矛盾,和外设预备IO的过程才是最耗费时间的 好比:别人找你借钱,每一次都来找你唠嗑大半天,分开十次,沟通的时间花的很久,而转账的时间就几秒钟,一次沟通直接把钱全转过去了,才是效率最高的 🌈解疑答惑 同样的一个程序,向显示器打印输出4行文本,向普通文件(磁盘上)打印的时候,变成了7行,说明上面测试,并不影响系统接口 C的IO接口是打印了2次的系统接口,只打印了一次 我们最后调用fork,上面的函数已经被执行完了,但不代表数据已经被刷新了 🥑缓冲区是谁提供的 🔥曾经“我们所谈的缓冲区”,绝对不是由OS提供的,如果是OS同一提供,那么我们上面的代码,表现应该是一样的,而不是C的IO接口打印两次,所以是C标准库提供并且维护的用户级缓冲区 fputs把不是直接把数据直接放进操作系统,而是加载进C标准库的缓冲区中,加载完后自己可以直接返回;如果直接调用的是write接口,则是直接写给OS,不经过缓冲区 C语言提供的接口都是向显示器打印的,刷新策略都是行刷新,那么最后执行fork的时候 —— 一定是函数执行完了 && 数据已经被刷新了(因为都带\n),所以fork执行无意义如你对应的程序进行了重定向 ——> 要向磁盘文件打印 ——> 隐形的刷新策略变成了全缓冲!—— > \n便没有意义了 ——> 函数一定执行完了,数据还没有刷新!! 在当前进程对应的C标准库中的缓冲区中!! 这缓冲区的部分数据是父进程的数据吗? 是的 fork之后,父子分流,父进程的数据发生写时拷贝给子进程,所以C标准库会打印两次 总结: 重定向到文件导致:刷新策略改变(变成全缓冲)写时拷贝:父子进程各自刷新一次 🥑用户级缓冲区在哪里? 当我们用fflush强制刷新的时候 #include #include #include int main() { //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之前,强制刷新 fflush(stdout); //最后调用fork的时候,上面的函数已经被执行完了 fork();//创建子进程 return 0; } 结果如下: 数据在fork之前,已经被fflush刷新了,缓冲区里没有数据了,也就不存在写时拷贝。 这里更夸张的是,fflush(stdout)只告诉了stdout就能知道缓冲区在哪里? FILE *fopen(const char *path, const char *mode); C语言中,open打开文件,返回的是FILE * ,struct FILE结构体 — 内部封装了fd,还包含了该文件fd对应的语言层的缓冲区结构!(远在天边,近在眼前) 我们可以看看FILE结构体: //在/usr/include/libio.h struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags //缓冲区相关 /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */ char* _IO_read_ptr; /* Current read pointer */ char* _IO_read_end; /* End of get area. */ char* _IO_read_base; /* Start of putback+get area. */ char* _IO_write_base; /* Start of put area. */ char* _IO_write_ptr; /* Current put pointer. */ char* _IO_write_end; /* End of put area. */ char* _IO_buf_base; /* Start of reserve area. */ char* _IO_buf_end; /* End of reserve area. */ /* The following fields are used to support backing up and undo. */ char *_IO_save_base; /* Pointer to start of non-current get area. */ char *_IO_backup_base; /* Pointer to first valid character of backup area */ char *_IO_save_end; /* Pointer to end of non-current get area. */ struct _IO_marker *_markers; struct _IO_FILE *_chain; int _fileno; //封装的文件描述符 #if 0 int _blksize; #else int _flags2; #endif _IO_off_t _old_offset; /* This used to be _offset but it's too small. */ #define __HAVE_COLUMN /* temporary */ /* 1+column number of pbase(); 0 is unknown. */ unsigned short _cur_column; signed char _vtable_offset; char _shortbuf[1]; /* char* _save_gptr; char* _save_egptr; */ _IO_lock_t *_lock; #ifdef _IO_USE_OLD_IO_FILE }; 所以在C语言上,进行写入的时候放进缓冲区,定期刷新 C语言打开的FILE是文件流。C++中的cout 是类;里面必定包含了 fd、buffer(缓冲区) 🌏设计用户层缓冲区的代码 ~ 实战 💢struct file的设计 struct MyFILE_{ int fd; //文件描述符 char buffer[1024]; //缓冲区 int end; //当前缓冲区的结尾 }; 💢主函数 open文件 —— fputs输入 —— fclose关闭,接口函数都要我们逐一实现 int main() { MyFILE *fp = fopen_("./log.txt", "r"); if(fp = NULL) { printf("open file error"); return 0; } fputs_("hello world error", fp); fclose_(fp); } 我们发现:C语言的接口一旦打开成功,全部都要带上FILE*结构,原因很简单,因为什么数据都在这个FILE结构体中 FILE *fopen(const char *path, const char *mode); //以下全是要带FILE* int fputc(int c, FILE *stream); int fclose(FILE *fp); size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); 💢接口实现 💦fputs //此处刷新策略还没定 全部放进缓冲区 void fputs_(const char *message, MyFILE *fp) { assert(message); assert(fp); strcpy(fp->buffer + fp->end, message);//abcde\0 fp->end += strlen(message); } 运行结果: 上面覆盖了\0,strcpy会在结尾时候自动添加\0 若要往显示器上打印:变成行刷新 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 { //其他文件 } } 测试用例: fputs_("one:hello world error", fp); fputs_("two:hello world error\n", fp); fputs_("three:hello world error", fp); fputs_("four:hello world error\n", fp); 结果:当遇到\n,才刷新 💦fflush刷新 当end!=0 ,就刷新进内核 内核刷新进外设,这就要用一个函数syncfs #include //将缓冲区缓存提交到磁盘 int syncfs(int fd); 具体实现: void fflush(MyFILE *fp) { assert(fp); if(fp->end != 0) { //暂且认为刷新了 ——其实是把数据写到 内核 write(fp->fd, fp->buffer, fp->end); syncfs(fp->fd); //将数据写入到磁盘 fp->end = 0; } } 💦fclose 关闭之前要先刷新 void fclose(MyFILE *fp) { assert(fp); fflush(fp); close(fp->fd); free(fp); } 💢附源码 #include #include #include #include #include #include #include #include #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;//什么也没做,最后返回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 error", fp); fputs_("two:hello world error", fp); fputs_("three:hello world error", fp); fputs_("four:hello world error", fp); fclose(fp); } 📢写在最后 但行好事,莫问前程
香港云服务器租用推荐
服务器租用资讯
·租用美国服务器配置
·怎样使用美国服务器(新的服务器怎样使用)
·怎么联系美国服务器(本服务器在美国受到法律)
·云服务器美国电影(美国高防云服务器)
·源服务器在美国(美国服务器ip)
·邮箱搭建美国服务器(群晖搭建邮箱服务器)
·微信美国服务器(微信小程序要服务器吗)
·受美国服务器保护(此服务器受美国保护)
·手机vpn美国服务器
服务器租用推荐
·美国服务器租用
·台湾服务器租用
·香港云服务器租用
·香港裸金属服务器
·香港高防服务器租用
·香港服务器租用特价