前言
作者:小蝸牛向前沖
名言:我可以接受失敗,但我不能接受放棄
??如果覺(jué)的博主的文章還不錯(cuò)的話,還請(qǐng)
點(diǎn)贊,收藏,關(guān)注??支持博主。如果發(fā)現(xiàn)有問(wèn)題的地方歡迎?大家在評(píng)論區(qū)指正
?本期學(xué)習(xí)目標(biāo):認(rèn)識(shí)什么是緩沖區(qū),緩沖區(qū)在哪里,模擬實(shí)現(xiàn)一個(gè)簡(jiǎn)單的緩沖區(qū)。
目錄
一、緩沖區(qū)
1、見(jiàn)一個(gè)現(xiàn)象
2、緩沖區(qū)的相關(guān)知識(shí)
3、解釋現(xiàn)象?
二、模擬實(shí)現(xiàn)緩沖區(qū)?
1、makefile?
2、myStdio.h?
?3、myStdio.c
4、test.c?
一、緩沖區(qū)
我們?cè)谥囟ㄏ虿┛椭性?jīng)發(fā)現(xiàn)了一個(gè)現(xiàn)象,在做重定向?qū)嶒?yàn)時(shí),我們將文件描述符fd = 1關(guān)閉掉,并通過(guò)open函數(shù)打開(創(chuàng)建)add.txt的文件,由于fd = 1 被關(guān)閉了,根據(jù)文件描述符fd的分配規(guī)則:是從小到大?,遵循尋找最小而且沒(méi)有被占用的的fd分配。這時(shí)候fd = 1中file*的指針會(huì)指向add.txt文件中,就不在向顯示器打印了,而要將open fd的內(nèi)容寫到add.txt中,但是我們通過(guò)cat命令查看add.txt中的內(nèi)容卻什么也沒(méi)有,這是為什么呢?
這就不得不提緩沖區(qū)的概念,其實(shí)緩存區(qū)就是一段內(nèi)存,但是這段內(nèi)存是誰(shuí)申請(qǐng)的?屬于誰(shuí)的?為什么要有緩沖區(qū)呢?
下面我們來(lái)看一個(gè)現(xiàn)象:
1、見(jiàn)一個(gè)現(xiàn)象
首先我們分別調(diào)用C接口和系統(tǒng)接口進(jìn)行打印測(cè)試。
我們將mytest中的文件內(nèi)容輸出重定向到log.txt中,我們也在log.txt中查找到了輸出的內(nèi)容.
下面我們繼續(xù)進(jìn)行在代碼測(cè)試,在代碼最后用fork建立一個(gè)子進(jìn)程
運(yùn)行程序:
?我們發(fā)現(xiàn) printf 和 fprintf及fputs(庫(kù)函數(shù))都輸出了2次,而 write 只輸出了一次(系統(tǒng)調(diào)用)。為什么呢?我們只是在多加了應(yīng)該子進(jìn)程而已,這說(shuō)明出現(xiàn)這種現(xiàn)象肯定是和fork函數(shù)有關(guān)。
2、緩沖區(qū)的相關(guān)知識(shí)
為什么庫(kù)函數(shù)會(huì)打印二次,而系統(tǒng)調(diào)用的函數(shù)只會(huì)被打印一次呢?毋庸置疑這肯定和緩沖區(qū)有關(guān)。
上面我們提到緩存沖區(qū)是一段內(nèi)存,那么既然是一段內(nèi)存肯定要被管理起來(lái),而管理緩沖區(qū)的結(jié)構(gòu)體我們稱之為FILE,而且我們可以知道是緩沖區(qū)肯定不在內(nèi)核中。
我們也可以在系統(tǒng)中見(jiàn)一見(jiàn)他
//輸入命令
vim /usr/include/libio.h
?打開文件在246行這樣就能看到_IO_FILE的結(jié)構(gòu)體,不對(duì)啊吖,不是說(shuō)FILE才是管理緩沖區(qū)的嗎?
怎么變成了_IO_FILE的結(jié)構(gòu)體,其實(shí)在其實(shí)是在:
typedef struct _IO_FILE FILE; 在/usr/include/stdio.h
中進(jìn)行了重命名的,第48行就對(duì)_IO_FILE的結(jié)構(gòu)體進(jìn)行了typedef。
這里我們需要注意的是FILE結(jié)構(gòu)體中也封裝了fd,這就會(huì)在合適的時(shí)候,就會(huì)將在緩沖區(qū)中內(nèi)容刷新到外設(shè)中。
緩沖區(qū)的刷新幾種形式:?
立刻刷新? ? ? ?-----無(wú)緩沖
行刷新? ? ? ? ? ?------顯示器
緩沖區(qū)滿刷新? ? ?-------磁盤文件
?那我們?cè)趺蠢斫馍厦娴膸追N刷新方式呢?
立刻刷新是只直接在內(nèi)存中的信息,刷新到外設(shè),這種場(chǎng)景是非常少見(jiàn)的,因?yàn)檫@樣非常消耗資源。
行刷新,就是緩沖區(qū)滿了一行就刷新,也就是說(shuō)我們?cè)谡{(diào)用函數(shù)時(shí)有"\n"時(shí)就會(huì)進(jìn)行刷新。
緩沖區(qū)滿刷新,就是指緩沖區(qū)的內(nèi)存滿了,才會(huì)把緩沖區(qū)里面的內(nèi)容刷新到外設(shè)中。
緩沖區(qū)的自動(dòng)刷新規(guī)則:
- 用戶強(qiáng)制刷新
- 進(jìn)程退出
3、解釋現(xiàn)象?
上面我們了解有關(guān)緩沖區(qū)的相關(guān)知識(shí),那么為什么會(huì)出現(xiàn)我們上面的現(xiàn)象呢?
在代碼結(jié)束前我們進(jìn)行了子進(jìn)程的創(chuàng)建:
代碼結(jié)束之前,進(jìn)行創(chuàng)建子進(jìn)程
? ?1. 如果我們沒(méi)有進(jìn)行>,看到了4條消息
stdout 默認(rèn)使用的是行刷新,在進(jìn)程fork之前,三條C函數(shù)已經(jīng)將數(shù)據(jù)進(jìn)行打印輸出到顯示器上(外設(shè)),你的FILE內(nèi)部,進(jìn)程內(nèi)部不存在對(duì)應(yīng)的數(shù)據(jù)啦。
? ? 2. 如果我們進(jìn)行了>, 寫入文件不再是顯示器,而是普通文件,采用的刷新策略是全緩沖,之前的3條c顯示函數(shù),雖然帶了\n,但是不足以stdout緩沖區(qū)寫滿!數(shù)據(jù)并沒(méi)有被刷新?。?!
? ? 執(zhí)行fork的時(shí)候,stdout屬于父進(jìn)程,創(chuàng)建子進(jìn)程時(shí), 緊接著就是進(jìn)程退出!誰(shuí)先退出,一定要進(jìn)行緩沖區(qū)刷新(就是修改)
? ? 由于寫時(shí)拷貝??!數(shù)據(jù)最終會(huì)顯示兩份,所以在父子進(jìn)程退出后,會(huì)立刻被緩沖區(qū)刷新,從而導(dǎo)致三條C函數(shù)分別進(jìn)行了二次打印。
3.?write為什么沒(méi)有呢?上面的過(guò)程都和wirte無(wú)關(guān),wirte沒(méi)有FILE,而用的是fd,就沒(méi)有C提供的緩沖區(qū)
?這里我們就可以回答:
緩沖區(qū)在哪里:
在FILE*指向的FILE結(jié)構(gòu)體中(這也就是為什么,我們自己要強(qiáng)制刷新的時(shí)候要傳文件指針,fflush(文件指針),fclose(文件指針))。
重定向?qū)嶒?yàn)的現(xiàn)象:
因?yàn)槲覀冸m然將open fd的內(nèi)容要寫入到add.txt中,但是由于add.txt是普通文件,他采取的方式是全緩存,就不足以以讓緩沖區(qū)刷新到顯示器(stdout)中,所以通過(guò)cat 命令查看會(huì)什么也查不出來(lái)。
二、模擬實(shí)現(xiàn)緩沖區(qū)?
這里我們分模塊化實(shí)現(xiàn):
1、makefile?
這里我們用makefile來(lái)完成對(duì)程序的自動(dòng)化編譯和構(gòu)建程序:
test:test.c myStdio.c //依賴關(guān)系
gcc -o $@ $^ -std=c99//依賴方法
.PHONY:clean//聲明偽目標(biāo)clean
clean:
rm -f test
2、myStdio.h?
在myStdio.h中對(duì)?緩沖區(qū)結(jié)構(gòu)進(jìn)行定義并且進(jìn)行相關(guān)的函數(shù)聲明:
#pragma once
#include <assert.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define SIZE 1024
#define SYNC_NOW 1//sync能夠馬上刷新緩沖區(qū)(馬上刷新)
#define SYNC_LINE 2//行刷新
#define SYNC_FULL 4//全緩沖刷新
typedef struct _FILE{
int flags; //刷新方式
int fileno;
int cap; //buffer的總?cè)萘? int size; //buffer當(dāng)前的使用量
char buffer[SIZE];
}FILE_;
FILE_ *fopen_(const char *path_name, const char *mode);
void fwrite_(const void *ptr, int num, FILE_ *fp);
void fclose_(FILE_ * fp);
void fflush_(FILE_ *fp)
?3、myStdio.c
?在myStdio.c中對(duì)?緩沖區(qū)功能函數(shù)進(jìn)行實(shí)現(xiàn):
這里我們主要實(shí)現(xiàn):
fopen_打開文件。fwrite_x向文件中寫入,fflush_刷新緩沖區(qū),fclose_關(guān)閉文件
#include "myStdio.h"
FILE_ *fopen_(const char *path_name, const char *mode)
{
int flags = 0;
int defaultMode=0666;
if(strcmp(mode, "r") == 0)
{
flags |= O_RDONLY;
}
else if(strcmp(mode, "w") == 0)
{
flags |= (O_WRONLY | O_CREAT |O_TRUNC);
}
else if(strcmp(mode, "a") == 0)
{
flags |= (O_WRONLY | O_CREAT |O_APPEND);
}
else
{
//TODO
}
int fd = 0;
if(flags & O_RDONLY) fd = open(path_name, flags);
else fd = open(path_name, flags, defaultMode);
if(fd < 0)
{
const char *err = strerror(errno);
write(2, err, strlen(err));
return NULL; // 為什么打開文件失敗會(huì)返回NULL
}
FILE_ *fp = (FILE_*)malloc(sizeof(FILE_));
assert(fp);
fp->flags = SYNC_LINE; //默認(rèn)設(shè)置成為行刷新
fp->fileno = fd;
fp->cap = SIZE;
fp->size = 0;
memset(fp->buffer, 0 , SIZE);
return fp; // 為什么你們打開一個(gè)文件,就會(huì)返回一個(gè)FILE *指針
}
void fwrite_(const void *ptr, int num, FILE_ *fp)
{
// 1. 寫入到緩沖區(qū)中
memcpy(fp->buffer+fp->size, ptr, num); //這里我們不考慮緩沖區(qū)溢出的問(wèn)題
fp->size += num;
// 2. 判斷是否刷新
if(fp->flags & SYNC_NOW)
{
write(fp->fileno, fp->buffer, fp->size);
fp->size = 0; //清空緩沖區(qū)
}
else if(fp->flags & SYNC_FULL)
{
if(fp->size == fp->cap)
{
write(fp->fileno, fp->buffer, fp->size);
fp->size = 0;
}
}
else if(fp->flags & SYNC_LINE)
{
if (strcmp(&(fp->buffer[fp->size - 1]), "\n") == 0)
{
write(fp->fileno, fp->buffer, fp->size);
fp->size = 0;
}
}
else{
}
}
void fflush_(FILE_ *fp)
{
if( fp->size > 0) write(fp->fileno, fp->buffer, fp->size);
fsync(fp->fileno); //將數(shù)據(jù),強(qiáng)制要求OS進(jìn)行外設(shè)刷新!
fp->size = 0;
}
void fclose_(FILE_ * fp)
{
fflush_(fp);
close(fp->fileno);
}
4、test.c?
#include "myStdio.h"
#include <stdio.h>
int main()
{
FILE_ *fp = fopen_("./hello.txt", "w");
if(fp == NULL)
{
return 1;
}
int cnt = 10;
const char *msg = "hello pjb ";
while(1)
{
fwrite_(msg, strlen(msg), fp);
sleep(1);
printf("count: %d\n", cnt);
cnt--;
if(cnt == 0) break;
}
fclose_(fp);
return 0;
}
?下面寫一個(gè)簡(jiǎn)單的bush腳本:
while :; do cat hello.txt;sleep 1;echo "###############";done
這是一個(gè)簡(jiǎn)單的 Bash 腳本,它的功能是循環(huán)讀取并打印文件 "hello.txt" 的內(nèi)容,并每隔 1 秒打印一條分隔線。
解釋一下腳本的含義:
while :; do
?表示開始一個(gè)無(wú)限循環(huán)。cat hello.txt
?使用?cat
?命令讀取并打印 "hello.txt" 文件的內(nèi)容。sleep 1
?表示暫停執(zhí)行 1 秒,即等待一秒鐘。echo "###############"
?打印一條分隔線,由多個(gè) "#" 字符組成。done
?表示循環(huán)結(jié)束。
因此,執(zhí)行這段腳本時(shí),會(huì)不斷循環(huán)讀取并打印 "hello.txt" 文件的內(nèi)容,每次打印之間會(huì)有一秒的暫停,并且在每次打印后會(huì)輸出一條分隔線。
請(qǐng)確保當(dāng)前目錄下存在名為 "hello.txt" 的文件,并且具有可讀權(quán)限。
測(cè)試:
?1、當(dāng)寫入文件的msg字符串不帶換行符時(shí)。
const char *msg = "hello pjb ";
?
?這里我們觀察到當(dāng)程序結(jié)束時(shí),才將緩沖區(qū)中的內(nèi)容刷新到hello.txt文件中。
2、當(dāng)寫入文件的msg字符串帶換行符
const char *msg = "hello pjb\n";
這里名為可以驗(yàn)證到帶\n普通文件是逐行進(jìn)行刷新的。
緩沖區(qū)總結(jié) :
看到這些現(xiàn)象我們不由的想緩存區(qū)的刷新策略:有全緩存,行緩沖,立即刷新。
上面是我們自己進(jìn)行的封裝,但是這和os(操作系統(tǒng))有什么關(guān)系呢?下面來(lái)看一幅圖
?
這幅圖大致說(shuō)明了字符串,要寫入到文件中,需要經(jīng)過(guò)層層拷貝在?最終由操作系統(tǒng)(OS)決定刷新到磁盤文件中。
這里我們要注意的是,在有用戶刷新到C語(yǔ)言的緩沖區(qū)(FILE)中才會(huì)遵循全緩沖,行緩沖。對(duì)于操作系統(tǒng)來(lái)說(shuō)他會(huì)自己調(diào)配資源進(jìn)行刷新。
特別注意:
我們也可以強(qiáng)制OS刷新,調(diào)用fflush()就可以了。
ffush()的底層:文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-725071.html
void fflush_(FILE_ *fp) { if( fp->size > 0) write(fp->fileno, fp->buffer, fp->size); fsync(fp->fileno); //將數(shù)據(jù),強(qiáng)制要求OS進(jìn)行外設(shè)刷新! fp->size = 0; }
其實(shí)是調(diào)用來(lái)fsync的接口進(jìn)行強(qiáng)制刷新。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-725071.html
到了這里,關(guān)于[Linux打怪升級(jí)之路]-緩沖區(qū)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!