??歡迎來(lái)到Linux專欄~~基礎(chǔ)IO
- (???(??? )??,我是Scort
- 目前狀態(tài):大三非科班啃C++中
- ??博客主頁(yè):張小姐的貓~江湖背景
- 快上車??,握好方向盤(pán)跟我有一起打天下嘞!
- 送給自己的一句雞湯??:
- ??真正的大師永遠(yuǎn)懷著一顆學(xué)徒的心
- 作者水平很有限,如果發(fā)現(xiàn)錯(cuò)誤,可在評(píng)論區(qū)指正,感謝??
- ????歡迎持續(xù)關(guān)注!
![]()
一. 緩沖區(qū)
??緩沖區(qū)是什么
??緩沖區(qū) (buffer),它是內(nèi)存空間的一部分。 也就是說(shuō),在內(nèi)存空間中預(yù)留了一定的存儲(chǔ)空間,這些存儲(chǔ)空間用來(lái)緩沖輸入或輸出的數(shù)據(jù),這部分預(yù)留的空間就叫做緩沖區(qū),顯然緩沖區(qū)是具有一定大小的
??為什么要引入緩沖器
高速設(shè)備與低速設(shè)備的不匹配(cpu運(yùn)算是納秒,內(nèi)存是微秒,磁盤(pán)是毫秒甚至是秒相差1000倍),勢(shì)必會(huì)讓高速設(shè)備花時(shí)間等待低速設(shè)備,我們可以在這兩者之間設(shè)立一個(gè)緩沖區(qū)
??舉個(gè)例子:(順豐就是緩沖區(qū))
- 可以解除兩者的制約關(guān)系,數(shù)據(jù)可以直接送往緩沖區(qū),高速設(shè)備不用再等待低速設(shè)備,提高了計(jì)算機(jī)的效率
- 可以減少數(shù)據(jù)的讀寫(xiě)次數(shù),如果每次數(shù)據(jù)只傳輸一點(diǎn)數(shù)據(jù),就需要傳送很多次,這樣會(huì)浪費(fèi)很多時(shí)間,因?yàn)殚_(kāi)始讀寫(xiě)與終止讀寫(xiě)所需要的時(shí)間很長(zhǎng),如果將數(shù)據(jù)送往緩沖區(qū),待緩沖區(qū)滿后再進(jìn)行傳送會(huì)大大減少讀寫(xiě)次數(shù),這樣就可以節(jié)省很多時(shí)間。例如:我們想將數(shù)據(jù)寫(xiě)入到磁盤(pán)中,不是立馬將數(shù)據(jù)寫(xiě)到磁盤(pán)中,而是先輸入緩沖區(qū)中,當(dāng)緩沖區(qū)滿了以后,再將數(shù)據(jù)寫(xiě)入到磁盤(pán)中,這樣就可以減少磁盤(pán)的讀寫(xiě)次數(shù),不然磁盤(pán)很容易壞掉
總的來(lái)說(shuō):
??緩沖區(qū)的初步認(rèn)識(shí)
?緩沖區(qū)刷新策略?。ㄒ话?特殊)
- 立即刷新
- 行刷新(行緩沖)
\n
- 滿刷新(全緩沖)
-
特殊情況:用戶強(qiáng)制刷新(
fflush
)、進(jìn)程退出(必須刷新)
一般而言 ,行緩沖的設(shè)備文件 —— 顯示器
全緩沖的設(shè)備文件 —— 磁盤(pán)文件
??所以的設(shè)備,永遠(yuǎn)都傾向于全緩沖!(傾向于,但不絕對(duì)) —— 緩沖區(qū)滿了,才刷新 —— 需要更少次的IO操作 —— 也就是更少次的外設(shè)訪問(wèn)(1次IO vs 10次IO)—— 也就可以提高效率
??其他刷新策略是結(jié)合具體情況做的妥協(xié)!
- 顯示器:直接給用戶看的,一方面要照顧效率,一方面要照顧用戶的體驗(yàn)( 極端情況,可以自定義規(guī)則的)
- 磁盤(pán)文件:用戶不需要立馬看見(jiàn)文件的內(nèi)容,可以把緩沖區(qū)寫(xiě)滿再輸出,更加注重效率的考量
我們可能有疑問(wèn):1000個(gè)字節(jié),刷一次是1000個(gè)字節(jié),刷十次整體也是1000個(gè)字節(jié),哪里效率高呢?
- ??和外設(shè)進(jìn)行溝通IO的時(shí)候,數(shù)據(jù)量的大小不是主要矛盾,和外設(shè)預(yù)備IO的過(guò)程才是最耗費(fèi)時(shí)間的
好比:別人找你借錢(qián),每一次都來(lái)找你嘮嗑大半天,分開(kāi)十次,溝通的時(shí)間花的很久,而轉(zhuǎn)賬的時(shí)間就幾秒鐘,一次溝通直接把錢(qián)全轉(zhuǎn)過(guò)去了,才是效率最高的
??解疑答惑
同樣的一個(gè)程序,向顯示器打印輸出4行文本,向普通文件(磁盤(pán)上)打印的時(shí)候,變成了7行,說(shuō)明上面測(cè)試,并不影響系統(tǒng)接口
- C的IO接口是打印了2次的
- 系統(tǒng)接口,只打印了一次
我們最后調(diào)用fork,上面的函數(shù)已經(jīng)被執(zhí)行完了,但不代表數(shù)據(jù)已經(jīng)被刷新了
??緩沖區(qū)是誰(shuí)提供的
??曾經(jīng)“我們所談的緩沖區(qū)”,絕對(duì)不是由OS提供的,如果是OS同一提供,那么我們上面的代碼,表現(xiàn)應(yīng)該是一樣的,而不是C的IO接口打印兩次,所以是C標(biāo)準(zhǔn)庫(kù)提供并且維護(hù)的用戶級(jí)緩沖區(qū)
fputs
把不是直接把數(shù)據(jù)直接放進(jìn)操作系統(tǒng),而是加載進(jìn)C標(biāo)準(zhǔn)庫(kù)的緩沖區(qū)中,加載完后自己可以直接返回;如果直接調(diào)用的是write接口,則是直接寫(xiě)給OS,不經(jīng)過(guò)緩沖區(qū)
- C語(yǔ)言提供的接口都是向顯示器打印的,刷新策略都是行刷新,那么最后執(zhí)行fork的時(shí)候 —— 一定是函數(shù)執(zhí)行完了 && 數(shù)據(jù)已經(jīng)被刷新了(因?yàn)槎紟?code>\n),所以fork執(zhí)行無(wú)意義
- 如你對(duì)應(yīng)的程序進(jìn)行了重定向 ——> 要向磁盤(pán)文件打印 ——> 隱形的刷新策略變成了全緩沖!—— >
\n
便沒(méi)有意義了 ——> 函數(shù)一定執(zhí)行完了,數(shù)據(jù)還沒(méi)有刷新!! 在當(dāng)前進(jìn)程對(duì)應(yīng)的C標(biāo)準(zhǔn)庫(kù)中的緩沖區(qū)中!!
這緩沖區(qū)的部分?jǐn)?shù)據(jù)是父進(jìn)程的數(shù)據(jù)嗎? 是的fork
之后,父子分流,父進(jìn)程的數(shù)據(jù)發(fā)生寫(xiě)時(shí)拷貝給子進(jìn)程,所以C標(biāo)準(zhǔn)庫(kù)會(huì)打印兩次
總結(jié):
- 重定向到文件導(dǎo)致:刷新策略改變(變成全緩沖)
- 寫(xiě)時(shí)拷貝:父子進(jìn)程各自刷新一次
??用戶級(jí)緩沖區(qū)在哪里?
當(dāng)我們用fflush
強(qiáng)制刷新的時(shí)候
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
//C語(yǔ)言提供的
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之前,強(qiáng)制刷新
fflush(stdout);
//最后調(diào)用fork的時(shí)候,上面的函數(shù)已經(jīng)被執(zhí)行完了
fork();//創(chuàng)建子進(jìn)程
return 0;
}
結(jié)果如下:
數(shù)據(jù)在fork之前,已經(jīng)被fflush刷新了,緩沖區(qū)里沒(méi)有數(shù)據(jù)了,也就不存在寫(xiě)時(shí)拷貝。
這里更夸張的是,fflush(stdout)
只告訴了stdout就能知道緩沖區(qū)在哪里?
FILE *fopen(const char *path, const char *mode);
- C語(yǔ)言中,open打開(kāi)文件,返回的是
FILE *
,struct FILE結(jié)構(gòu)體 — 內(nèi)部封裝了fd,還包含了該文件fd對(duì)應(yīng)的語(yǔ)言層的緩沖區(qū)結(jié)構(gòu)!(遠(yuǎn)在天邊,近在眼前)
我們可以看看FILE結(jié)構(gòu)體:
//在/usr/include/libio.h
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
//緩沖區(qū)相關(guān)
/* 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語(yǔ)言上,進(jìn)行寫(xiě)入的時(shí)候放進(jìn)緩沖區(qū),定期刷新
C語(yǔ)言打開(kāi)的FILE是文件流。C++中的cout 是類;里面必定包含了 fd、buffer(緩沖區(qū))
??設(shè)計(jì)用戶層緩沖區(qū)的代碼 ~ 實(shí)戰(zhàn)
??struct file
的設(shè)計(jì)
struct MyFILE_{
int fd; //文件描述符
char buffer[1024]; //緩沖區(qū)
int end; //當(dāng)前緩沖區(qū)的結(jié)尾
};
??主函數(shù)
open文件 —— fputs輸入 —— fclose關(guān)閉,接口函數(shù)都要我們逐一實(shí)現(xiàn)
int main()
{
MyFILE *fp = fopen_("./log.txt", "r");
if(fp = NULL)
{
printf("open file error");
return 0;
}
fputs_("hello world error", fp);
fclose_(fp);
}
我們發(fā)現(xiàn):C語(yǔ)言的接口一旦打開(kāi)成功,全部都要帶上FILE*
結(jié)構(gòu),原因很簡(jiǎn)單,因?yàn)槭裁磾?shù)據(jù)都在這個(gè)FILE結(jié)構(gòu)體中
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);
??接口實(shí)現(xiàn)
??fputs
//此處刷新策略還沒(méi)定 全部放進(jìn)緩沖區(qū)
void fputs_(const char *message, MyFILE *fp)
{
assert(message);
assert(fp);
strcpy(fp->buffer + fp->end, message);//abcde\0
fp->end += strlen(message);
}
運(yùn)行結(jié)果:
上面覆蓋了\0
,strcpy會(huì)在結(jié)尾時(shí)候自動(dòng)添加\0
若要往顯示器上打印:變成行刷新
if(fp->fd == 0)
{
//標(biāo)準(zhǔn)輸入
}
else if(fp->fd == 1)
{
//標(biāo)準(zhǔn)輸出
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)
{
//標(biāo)準(zhǔn)錯(cuò)誤
}
else
{
//其他文件
}
}
測(cè)試用例:
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);
結(jié)果:當(dāng)遇到\n,才刷新
??fflush刷新
當(dāng)end!=0 ,就刷新進(jìn)內(nèi)核
內(nèi)核刷新進(jìn)外設(shè),這就要用一個(gè)函數(shù)syncfs
#include <unistd.h>
//將緩沖區(qū)緩存提交到磁盤(pán)
int syncfs(int fd);
具體實(shí)現(xiàn):
void fflush(MyFILE *fp)
{
assert(fp);
if(fp->end != 0)
{
//暫且認(rèn)為刷新了 ——其實(shí)是把數(shù)據(jù)寫(xiě)到 內(nèi)核
write(fp->fd, fp->buffer, fp->end);
syncfs(fp->fd); //將數(shù)據(jù)寫(xiě)入到磁盤(pán)
fp->end = 0;
}
}
??fclose
關(guān)閉之前要先刷新
void fclose(MyFILE *fp)
{
assert(fp);
fflush(fp);
close(fp->fd);
free(fp);
}
??附源碼
#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]; // 緩沖區(qū)
int end; //當(dāng)前緩沖區(qū)的結(jié)尾
};
typedef struct MyFILE_ MyFILE;//類型重命名
MyFILE *fopen_(const char *pathname, const char *mode)
{
assert(pathname);
assert(mode);
MyFILE *fp = NULL;//什么也沒(méi)做,最后返回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;
}
//是不是應(yīng)該是C標(biāo)準(zhǔn)庫(kù)中的實(shí)現(xiàn)!
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);
//暫時(shí)沒(méi)有刷新, 刷新策略是誰(shuí)來(lái)執(zhí)行的呢?用戶通過(guò)執(zhí)行C標(biāo)準(zhǔn)庫(kù)中的代碼邏輯,來(lái)完成刷新動(dòng)作
//這里效率提高,體現(xiàn)在哪里呢??因?yàn)镃提供了緩沖區(qū),那么我們就通過(guò)策略,減少了IO的執(zhí)行次數(shù)(不是數(shù)據(jù)量)
if(fp->fd == 0)
{
//標(biāo)準(zhǔn)輸入
}
else if(fp->fd == 1)
{
//標(biāo)準(zhǔn)輸出
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)
{
//標(biāo)準(zhǔn)錯(cuò)誤
}
else
{
//其他文件
}
}
void fflush_(MyFILE *fp)
{
assert(fp);
if(fp->end != 0)
{
//暫且認(rèn)為刷新了--其實(shí)是把數(shù)據(jù)寫(xiě)到了內(nèi)核
write(fp->fd, fp->buffer, fp->end);
syncfs(fp->fd); //將數(shù)據(jù)寫(xiě)入到磁盤(pán)
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);
}
??寫(xiě)在最后
但行好事,莫問(wèn)前程文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-786796.html
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-786796.html
到了這里,關(guān)于【Linux】基礎(chǔ)IO —— 緩沖區(qū)深度剖析的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!