記事ディレクトリ
1. 緩衝地帯
int main()
{
printf("hello linux");
sleep(2);
return 0;
}
printf
このようなコードの場合, まず第一に,ステートメントが最初に実行されることが確実ですsleep
. この場合, 最初にステートメントを出力してからスリープする必要があります. 結果を見てみましょう:
しかし、ここでは、まずスリープしてからステートメントを出力します. これは、バッファと呼ばれるものがあるためです. 周辺機器にデータを書き込みたい場合 (ディスプレイを表示させるとは、ディスプレイにデータを書き込むことを意味します)、データは一時的に格納されます. buffer. 領域で、バッファのフラッシュ ポリシーに従ってリフレッシュされます。
最初にスリープしてからデータを表示するのは、周辺機器に直接データを書き込まないためであり、一時的にデータを保存するためのバッファがあるため、スリープ後にデータをフラッシュできます。それでは、バッファについて話しましょう。
1. バッファーとは
バッファの本質はメモリ(物理メモリ)の一部です
2. 緩衝地帯の意味
私は奇抜なアイデアを持つ職人で、Thai Pants Spicy という親友がいます。何かを作るときはいつでも、自転車に乗って 100 キロ以上も彼に渡します。そんなある日、速達業界が興隆し、自転車で山や海を越えて彼に届ける必要がなくなり、荷物を速達ポイントに渡すだけでよい発明が生まれました。家に帰って発明を続けることができます. 宅配会社が送ってくれるので、時間を大幅に節約できます.
それから私はプロセスであり、親友のタイパンツはファイルであり、私の新しい発明はデータであり、バッファーは配送ポイントです。したがって、バッファの最大の意義は、送信者の時間を節約すること、つまりプロセスの時間を節約することです。周辺機器は非常に遅いため、周辺機器にアクセスするとき、ほとんどの時間は周辺機器の準備が整うのを待っており、実際の書き込み時間はほとんどありません。バッファがある場合、プロセスは、データがバッファに渡される限り、後続のコードを実行するために戻ることができます. バッファは、プロセスがペリフェラルの準備が整うのを待つ時間コストを負担するのに役立ちます.
3. バッファ リフレッシュ戦略
しかし、私が宅配便を送りに行くときは、宅配便業者が荷物を送る前に、荷物を一定期間宅配便業者に手渡した後になることがよくあります。これは非効率的であり、100% のお金を失うことになります。しかし、オフシーズンの場合、速達の配達を長時間待っている人は少なく、速達ポイントは数か月間荷物を預けるように言われません。また、車のサイズやグレードの物を送る場合は、宅配便ポイントも状況に応じて宅配便を1回発送します。そのため、宅配業者は通常、一定量の商品が溜まるのを待ってから発送しますが、特別な事情があります。
同様に、バッファの更新も同じですが、最も効率的なのは、バッファがいっぱいになった後 (フル バッファとも呼ばれます)、バッファ全体のデータを更新することですが、この更新方法はデータをディスク ファイルに更新するだけです。 .
データをディスプレイに書き込むとき、バッファはライン リフレッシュ (ライン バッファリング) を使用します。これは、ディスプレイはユーザーが見るものであり、人間の読書習慣は左から右へ行単位で読むためです. コンピュータの本質は人間が使用するためのツールであるため、表示を更新するときに行更新が使用されます.
フルバッファリングとラインバッファリングに加えて、アンバッファリングと呼ばれる珍しいリフレッシュ方法もあり、これはデータが書き込まれるとすぐにリフレッシュされることを意味します。たとえば、printf はすぐに fflush します。
さらに、次の 2 つの特別な更新方法があります。
1.ユーザー強制リフレッシュ
2. プロセスが終了します。プロセスが終了する前に、バッファー内のデータがフラッシュされてデータが失われるのを防ぐために、バッファーが再度更新されます。
4.私たちが話している緩衝地帯はどこですか
#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main()
{
//先写一批C语言函数接口
printf("hello printf\n");
fprintf(stdout,"hello fprintf\n");
fputs("hello fputs\n",stdout);
//再写一个系统调用
const char*s="hello write\n";
write(1,s,strlen(s));
fork();
return 0;
}
上記のコードでは、結果を画面に直接表示する場合と、結果をファイルにリダイレクトする場合の 2 つの異なる結果が得られます。
上の図によると、結果を画面に直接出力すると、合計 4 つのステートメントが出力されることがわかります。これは、私たちの推測と一致しています。しかし、出力結果がファイルにリダイレクトされると、7 つのステートメントを出力することになり、そのうち C 言語の関数インターフェイスが 2 回出力されます。まず第一に、この現象の理由はバッファゾーンに関連しており、第二にフォークに関連しています。
上記の現象は、これまでに説明したバッファーがカーネルにないことを説明できます。そうしないと、システム コールがwrite
2 回出力され、ユーザー層にしか存在できなくなります。ファイルにアクセスするには、まずこのファイルが必要なfd
ので、C 言語で使用される FILE 構造を含める必要があります。したがって、今日では、FILE 構造にバッファが必要であることがfd
わかります。そうでなければ、なぜ常に fflush 関数を渡す必要があるのでしょうか。? 上で説明したさまざまな更新戦略も、FILE 構造内のバッファーを対象としています。FILE*
上記の状況の説明:
1. ディスプレイはユーザーが見るための周辺機器であるため、左から右へ行単位でユーザーの読書習慣に適合する必要があります。ファイルへのデータの更新では\n
、効率のためにフル バッファリングが使用されます。4 つの出力ステートメントがすべて含まれていますが\n
、バッファを埋めるにはまだ十分ではありません。
2. fork によって作成された子プロセスは、親プロセスのコピーです. これらはコードとデータ (FILE 内のバッファーを含む) を共有し、fork の直後に終了します. プロセスが終了すると、プロセスの損失を防ぐためにバッファーが一度更新されますプロセスが独立しているため、バッファのリフレッシュはバッファをクリアすることであり、これは本質的に変更です。そうしないと、子プロセスの動作が親プロセスに影響し、コピーオンライトが発生します。親プロセスのバッファにデータをコピーしてファイルにフラッシュすると、親プロセスは終了し、データをファイルにフラッシュします。
3. システム コールは fd を使用し、FILE 構造体はなく、FILE によって提供されるバッファもありません。
5.ファイルをコピー
紙の上で達成されることは常に浅はかであり、私はこれが実行されなければならないことを知っています. 次に、システム コール インターフェイスを使用して FILE 構造をカプセル化します。
5.1myStdio.h
#pragma once #include<unistd.h> #include<assert.h> #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h>
#include<assert.h> #include<stdlib.h>
#include<string.h> #include<stdio.h>
//FILE中有缓冲区,刷新方式,以及fd
//定义缓冲区大小
#define SIZE 1024 //定义刷新方式
#define SYNC_NOW 1 #define SYNC_LINE 2 #define SYNC_ALL 3 typedef struct FILE_
{
int flag;//刷新方式 int feilno;//fd int cap;//记录缓冲区容量 int size;//记录缓冲区使用
char buff[];//缓冲区 }FILE_;
//实现四个函数:fopen,fflush,fwrite,fclose
FILE_*fopen_(const char*path,const char*mode); void fflush_(FILE_*fp); void fwrite_(const char*ptr,size_t num,FILE_*fp); void fclose_(FILE_*fp);
5.2myStdio.c
#include"myStdio.h"
//实现四个函数
FILE_ *fopen_(const char*path,const char*mode)
{
int flags=0;//设置文件打开的方式
int defaultmode=0666;//设置文件打开的默认权限
if(strcmp(mode,"r")==0)
{
flags|=O_RDWR;
}
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;
}
int fd=0;
if(flags&O_RDWR)
fd=open(path,flags);
else
fd=open(path,flags,defaultmode);
if(fd<0)//文件打开失败
{
perror("open");
return NULL;
}
FILE_*fp=(FILE_*)malloc(sizeof(FILE_));//为FILE_结构体开辟空间
assert(fp);
//初始化FILE_
fp->cap=SIZE;
fp->feilno=fd;
fp->flag=SYNC_LINE;//默认设为行刷新
fp->size=0;
memset(fp->buff,0,SIZE);
return fp;
}
void fwrite_(const char*ptr,size_t num,FILE_*fp)
{
//将字符串拷贝到缓冲区
memcpy(fp->buff+fp->size,ptr,num);
//更新缓冲区使用量
fp->size+=num;
//按照刷新方式刷新
if(fp->flag&SYNC_NOW)
{
write(fp->feilno,fp->buff,fp->size);
fp->size=0;
}
else if(fp->flag&SYNC_ALL)
{
if(fp->size==fp->cap)
{
write(fp->feilno,fp->buff,fp->size);
fp->size=0;
}
}
else if(fp->flag&SYNC_LINE)
{
if(fp->buff[fp->size-1]=='\n')//如果最后一个字符是\n
{
write(fp->feilno,fp->buff,fp->size);
fp->size=0;
}
}
}
void fflush_(FILE_*fp)
{
//所谓刷新,不过就是将缓冲区中的内容刷新到外设中,有内容才刷新
if(fp->size>0)
write(fp->feilno,fp->buff,fp->size);
fsync(fp->feilno);//强制刷新到磁盘
//刷新完以后缓冲区就没数据了,要将缓冲区置空
fp->size=0;
}
void fclose_(FILE_*fp)//在关闭文件之前,还要刷新缓冲区
{
fflush_(fp);
close(fp->feilno);
}
6.オペレーティングシステムのバッファ
ユーザー層にバッファがあるだけでなく、カーネルにもカーネル バッファがあります。C 言語のファイル操作関数を使用してデータを書き込む場合、まずデータを FILE 構造体のバッファにコピーし、バッファなし/ライン バッファ/フル バッファのリフレッシュ戦略に従ってデータをカーネル バッファにリフレッシュし、最後にオペレーティング システムは、カーネル バッファ内のデータをディスクに自動的にフラッシュします。
fwrite などの関数は、書き込み関数として理解するのではなく、コピー関数として理解した方がよいでしょう。
カーネル バッファ内のデータを強制的にペリフェラルにフラッシュする場合は、システム コールを使用できますfsync
。