Linux
- 1.理解文件
- 2.C文件接口
- 1.打開 + 寫文件
- 2.讀文件 + 簡單實現cat命令
- 3.輸出信息到顯示器的方式
- 4.stdin、stdout、stderr
- 5.打開文件的方式
- 3.系統接口 IO
- 1.傳遞標志位
- 2.open、close
- 3.write、read
- 4.文件描述符
- 1.是什么?
- 2.分配規則
- 3.重定向原理
- 4.通過dup2系統調用重定向
- 5.標準錯誤重定向
- 6.自定義shell添加重定向功能
- 5.理解一切皆"文件"
- 6.緩沖區
- 1.什么是緩沖區?
- 2.FILE
- 3.緩沖類型
- 4.為什么要引入緩沖區機制
- 5.設計文件libc庫
1.理解文件
狹義理解:
- 文件在磁盤中。
- 磁盤是永久性存儲介質,因此文件在磁盤上的存儲是永久性的。
- 磁盤是外設(輸出設備/輸入設備)
- 磁盤上的文件本質是對文件的所有操作,都是對外設的輸入和輸出,簡稱 “IO”。
廣義理解:
Linux 下一切皆文件(鍵盤、顯示器、網卡、磁盤……)
文件操作:
- 對于 0KB 的空文件是占用磁盤空間的。
- 文件 = 文件屬性(元數據) + 文件內容。
- 所有的文件操作本質是文件內容操作和文件屬性的操作。
系統角度:
- 訪問文件的前提是先打開文件,誰打開文件呢?答案是 “進程打開文件”。對文件的操作本質就是 “進程對文件的操作”。
- 磁盤的管理者是操作系統,訪問文件本質就是 “訪問磁盤”,只有操作系統才能訪問磁盤文件。
- 文件的讀寫本質不是通過C語言/C++的庫函數來操作的,這些庫函數只是為用戶提供方便,而是通過文件相關的系統調用接口來實現的。fopen 和 fclose 封裝了操作系統對文件的系統調用。
- 操作系統通過 “先描述,再組織” 的方式對文件進行管理。在操作系統內部對被打開的文件創建 struct 結構體(包含被打開文件的相關屬性),這些結構體通過鏈表的形式組織起來,對被文件的管理轉化成對鏈表的 “增刪查改”。
2.C文件接口
1.打開 + 寫文件
#include<stdio.h>
#include<string.h>int main()
{FILE* fp = fopen("log.txt", "w");if(fp == NULL){perror("fopen:");return 1; }int cnt = 1;const char* msg = "Hello Linux:"; while(cnt <= 10) { char buffer[1024]; snprintf(buffer, sizeof(buffer), "%s%d\n", msg, cnt++);fwrite(buffer, strlen(buffer), 1, fp);} fclose(fp);return 0;
}
2.讀文件 + 簡單實現cat命令
#include<stdio.h>
#include<string.h>int main(int argc, char* argv[])
{if(argc != 2){printf("Usage:%s filename\n", argv[0]); return 1;}FILE* fp = fopen(argv[1], "r");if(fp == NULL){perror("fopen");return 2;}while(1){char buffer[128];memset(buffer, 0, sizeof(buffer));int n = fread(buffer, sizeof(buffer) - 1, 1, fp);if(n > 0)printf("%s", buffer);if(feof(fp)) //當到了文件的結尾,退出循環break;} fclose(fp);return 0;
}
3.輸出信息到顯示器的方式
Linux 中一切皆文件,顯示七也是一種文件
#include<stdio.h>
#include<string.h> int main()
{ printf("Hello printf\n"); fprintf(stdout, "Hello fprintf\n");const char* msg = "Hello fwrite\n";fwrite(msg, strlen(msg), 1, stdout);return 0;
}
4.stdin、stdout、stderr
- C 默認會打開三個輸入輸出流,分別是stdin、stdout、stderr
- 仔細觀察發現,這三個流的類型都是FILE*, fopen返回值類型是文件指針。
#include <stdio.h>extern FILE *stdin; //標準輸入:鍵盤文件
extern FILE *stdout; //標準輸出:顯示器文件
extern FILE *stderr; //標準錯誤:顯示器文件
5.打開文件的方式
3.系統接口 IO
- 打開文件的方式不僅僅是fopen,ifstream等語言層的方案,其實系統才是打開文件最底層的方案。
- 不過,在學習系統文件 IO 之前,先要了解下如何給函數傳遞標志位,該方法在系統文件 IO 接口中會使用到:
1.傳遞標志位
- 當存在多個標記位時,一般的做法是傳遞多個參數,用起來非常麻煩。
- 操作系統采用 “位圖” 來 “傳遞標志位” 的方式,32個比特位,每一個比特位的 0/1 代表是否被設置。
傳遞標志位的代碼案例:
#include<stdio.h>#define ONE_FLAG 1<<0 //0000 0000 ... 0000 0001
#define TWO_FLAG 1<<1 //0000 0000 ... 0000 0010
#define THREE_FLAG 1<<2 //0000 0000 ... 0000 0100
#define FOUR_FLAG 1<<3 //0000 0000 ... 0000 1000void fun(int flags)
{if(flags & ONE_FLAG) printf("one\n");if(flags & TWO_FLAG) printf("two\n");if(flags & THREE_FLAG) printf("three\n");if(flags & FOUR_FLAG) printf("four\n");
}int main()
{fun(ONE_FLAG); printf("\n");fun(ONE_FLAG | TWO_FLAG); printf("\n");fun(ONE_FLAG | TWO_FLAG | THREE_FLAG); printf("\n");fun(ONE_FLAG | TWO_FLAG | THREE_FLAG | FOUR_FLAG); printf("\n");return 0;
}
2.open、close
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
- pathname:要打開或創建的目標文件。
- flags:文件描述符。打開文件時,可以傳入多個參數選項,用下面的一個或者多個常量進行 “按位或” 運算,構成 flags。參數:O_RDONLY(只讀打開)、O_WRONLY(只寫打開)、O_RDWR(讀,寫打開)。這三個常量,必須指定一個且只能指定一個。O_CREAT:若文件不存在,則創建它。O_APPEND:追加寫。
- mode:當文件不存在時,以O_WRONLY打開文件,指明創建新文件的訪問權限。
- 返回值:成功時返回新打開的文件描述符。失敗時返回-1
#include <unistd.h>int close(int fd);
- fd:文件描述符,open 函數的返回值。
- 返回值:成功時返回0,失敗時返回-1
#include <sys/types.h>
#include <sys/stat.h>mode_t umask(mode_t mask); //修改權限掩碼
3.write、read
#include <unistd.h>ssize_t write(int fd, const void* buf, size_t count);
- fd:文件描述符,open 函數的返回值。
- buf:指向要寫入的數據的指針。
- count:指定了要寫入的字節數。
- 返回值:成功時返回真實寫入文件的字節數,失敗時返回-1
FILE* fp = fopen("log.txt", "w"); //低層就是下面的系統調用
int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);FILE* fp = fopen("log.txt", "a"); //低層就是下面的系統調用
int fd = open("log.txt", O_CREAT | O_WRONLY | O_APPEND, 0666);
文本寫入 VS 二進制寫入
#include<unistd.h>ssize_t read(int fd, void* buf, size_t count);
- fd:文件描述符。
- buf:將數據讀入到該指針 buffer 指向的字符串中。
- count:需要讀取的字節數。
- 返回值:成功時返回讀取的字節數,失敗時返回-1
4.文件描述符
1.是什么?
文件描述符就是從 0 開始的小整數。當我們打開文件時,操作系統在內存中要創建相應的數據結構來描述目標文件。于是就有了 file 結構體。表示一個已經打開的文件對象。而進程執行 open 系統調用,所以必須讓進程和文件關聯起來。每個進程都有一個指針 *files,指向一張表files_struct,該表最重要的部分就是包含一個指針數組,每個元素都是一個指向打開文件的指針!本質上,文件描述符就是該數組的下標。所以,只要拿著文件描述符,就可以找到對應的文件。
- 文件描述符:進程對應的文件描述符表的數組下標。
- 當用戶層進行 open(“log.txt”, “w”) 調用時:操作系統創建新的 struct file,在文件描述符表中找到未被使用的下標,將 struct file 的地址填寫進去,此時進程與文件就關聯了。
- 當用戶層進行 read(fd, buffer, sizeof(buffer)) 調用時:操作系統拿著 fd 索引文件描述符表找到對應的 struct file,每一個 struct file 都對應內存中的一個 “文件緩沖區”,操作系統先將磁盤文件中的內容預加載到文件緩沖區中,再將文件緩沖區中的內容拷貝到 buffer 中。read 函數本質:內核到用戶空間的拷貝函數。
- 當用戶層進行 write(fd, buffer, strlen(buffer)) 調用時:先將 buffer 指向的內容拷貝到文件緩沖區中,再將緩沖區中的內容定期刷新到磁盤文件中。
- 對文件做任何操作,都必須先將文件加載(磁盤->內存的拷貝)到內核對應的文件緩沖區中。
內核代碼如下:
通過 open 系統調用的返回值,得知文件描述符(fd)是一個整數。 如下代碼所示:
思考:文件描述符為什么從3開始,值為0、1、2 的文件描述符是什么?
- 文件描述符值為0、1、2 分別是:標準輸入、標準輸出、標準錯誤。
- C語言中的 fopen 返回值 FILE* 中的 FILE 是一個結構體。
- 在操作系統接口層面上,只認文件描述符 fd
- 結構體 FILE 一定封裝了文件描述符 fd
封裝:
- 在 Windows、Linux 等不同的平臺下的系統調用不同,使用系統調用不具備可移植性。
- C/C++封裝各個平臺關于文件操作的系統調用,在不同的平臺下通過條件編譯進行裁剪,成為語言級接口,具備可移植性。
- 語言增加可移植性的原因:為了讓更多人使用,防止被淘汰。
2.分配規則
文件描述符的分配規則:在 struct file* array[] 數組當中,找到當前沒有被使用的最小的一個下標,作為新的文件描述符。
3.重定向原理
解釋圖如下:
- printf 函數就是往 stdout 文件中打印內容,也就是文件描述符值為 1 的文件,但修改了 fd_array[1] 的指向時,便打印到了 log.txt 文件中。
- 更改文件描述符表中 fd_array[] 數組某個下標內容
指針的指向
(數組下標不變),叫做 “重定向”
4.通過dup2系統調用重定向
#include <unistd.h>int dup2(int oldfd, int newfd);
作用:makes newfd be the copy of oldfd, closing newfd first if necessary
5.標準錯誤重定向
- 思考:都是輸出到顯示器中,為什么要區分標準輸出printf、cout和標準錯誤perror、cerr / 為什么存在標準錯誤?
- 答案:標準輸出和標準錯誤占用不同的文件描述符。雖然都是顯示器,但是可以通過重定向,將常規消息和錯誤消息分離。
將標準輸出和標準錯誤都重定向到一個文件中該如何做呢?
6.自定義shell添加重定向功能
- 如果內建命令做重定向,需要更改 shell 的標準輸入、輸出、錯誤。此時需要創建臨時文件,類似兩個整數交換的過程,進行一次重定向后需要恢復。
- 一個文件可以被多個進程打開。若一個進程將其中一個文件關閉,就會影響其它正在讀取該文件的進程。所以 struct file 中有一個 ref_count(引用計數)的整形變量,操作系統打開時引用計數為0,指針指向該文件時引用計數++,關閉文件時引用計數–,當引用計數為0時,struct file 被釋放。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unordered_map>
#include<ctype.h>
#include<sys/stat.h>
#include<fcntl.h>#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]# "//命令行參數表
#define MAXARGC 128
char* g_argv[MAXARGC];
int g_argc = 0;//環境變量表
#define MAX_ENVS 128
char* g_env[MAX_ENVS];
int g_envs = 0;//別名映射表
std::unordered_map<std::string, std::string> alias_list;//重定向,關心的內容
#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3int redir = NONE_REDIR;
std::string filename;//for test
char cwd[1024];
char cwdenv[1024];//last exit code
int lastcode = 0;void InitEnv()
{extern char** environ;memset(g_env, 0, sizeof(g_env));g_envs = 0;//1.獲取環境變量for(int i = 0; environ[i]; i++){//申請空間g_env[i] = (char*)malloc(strlen(environ[i] + 1));//拷貝環境變量strcpy(g_env[i], environ[i]);g_envs++;}g_env[g_envs++] = (char*)"XZY=123456";g_env[g_envs] = NULL;//2.導入環境變量for(int i = 0; g_env[i]; i++){putenv(g_env[i]);}environ = g_env;
}//獲取用戶名
const char* GetUserName()
{const char* name = getenv("USER");return name == NULL ? "None" : name;
}//獲取主機名
const char* GetHostName()
{const char* name = getenv("HOSTNAME");return name == NULL ? "None" : name;
}//獲取當前路徑
const char* GetPwd()
{//const char* pwd = getenv("PWD"); 根據環境變量PWD獲得當前路徑(當cd修改路徑時:環境變量不會被修改)const char* ret = getcwd(cwd, sizeof(cwd)); //通過系統調用getcwd:獲取當前路徑cwdif(ret != NULL) //獲取成功時:ret也是當前路徑{snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", cwd); //格式化環境變量"PWD=cwd"putenv(cwdenv); //更新環境變量"PWD=cwd"}return ret == NULL ? "None" : cwd;
}//獲取家目錄
const char* GetHome()
{const char* home = getenv("HOME");return home == NULL ? "NULL" : home;
}//根據當前絕對路徑修改為相對路徑
std::string DirName(const char* pwd)
{
#define SLASH "/"std::string dir = pwd;if(dir == SLASH) return SLASH;auto pos = dir.rfind(SLASH);if(pos == std::string::npos) return "BUG?";return dir.substr(pos + 1);
}//制作命令行提示符
void MakeCommandLinePrompt(char prompt[], int size)
{//snprintf(prompt, size, FORMAT, GetUserName(), GetHostName(), DirName(GetPwd()).c_str()); snprintf(prompt, size, FORMAT, GetUserName(), GetHostName(), GetPwd());
}//打印命令行提示符
void PrintCommandLinePrompt()
{char prompt[COMMAND_SIZE];MakeCommandLinePrompt(prompt, sizeof(prompt));printf("%s", prompt);fflush(stdout);
}//獲取命令
bool GetCommand(char* command, int size)
{char* ret = fgets(command, size, stdin);if(ret == NULL) return false;command[strlen(command) - 1] = '\0'; //清理\nif(strlen(command) == 0) return false;return true;
}//命令解析
bool CommandPrase(char* command)
{
#define SEP " "g_argc = 0;g_argv[g_argc++] = strtok(command, SEP);while((bool)(g_argv[g_argc++] = strtok(NULL, SEP)));g_argc--;return g_argc > 0 ? true : false;
}//打印命令行參數
void PrintArgv()
{for(int i = 0; g_argv[i]; i++){printf("argv[%d]:%s\n", i, g_argv[i]);}printf("argc:%d\n", g_argc);
}//父進程執行cd命令
bool Cd()
{if(g_argc == 1){std::string home = GetHome();if(home.empty()) return true;chdir(home.c_str());}else {std::string where = g_argv[1];if(where == "~"){}else if(where == "-"){}else {chdir(where.c_str());}}return true;
}bool Echo()
{if(g_argc == 2){std::string opt = g_argv[1];if(opt == "$?") //echo $?{std::cout << lastcode << std::endl;lastcode = 0;}else if(opt[0] == '$'){std::string env_name = opt.substr(1);const char* env_value = getenv(env_name.c_str());if(env_value)std::cout << env_value << std::endl;}else {std::cout << opt << std::endl;}}return true;
}//檢測并執行內建命令:由父進程執行
bool CheckAndExecBuiltin()
{std::string cmd = g_argv[0];if(cmd == "cd"){Cd();return true;}else if(cmd == "echo"){Echo();return true;}else if(cmd == "export") {//1.在環境變量表中查找環境變量名:是否存在//2.存在修改,不存在新增}else if(cmd == "alias"){//std::string nickname = g_argv[1];//alias_list.insert(k, v)}return false;
}//執行普通命令:由子進程執行
void ExecuteCommand()
{ pid_t id = fork();if(id == 0){//子進程//子進程檢查重定向情況,父進程不能重定向int fd = -1;if(redir == INPUT_REDIR){fd = open(filename.c_str(), O_RDONLY);if(fd < 0) exit(1);dup2(fd, 0);close(fd);}else if(redir == OUTPUT_REDIR){fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0666);if(fd < 0) exit(2);dup2(fd, 1);close(fd);}else if(redir == APPEND_REDIR){fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_APPEND, 0666);if(fd < 0) exit(2);dup2(fd, 1);close(fd);}else {}//進程替換不影響,重定向的結果execvp(g_argv[0], g_argv);exit(1);}//父進程int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid > 0){lastcode = WEXITSTATUS(status);}
}void TrimSpace(char command[], int& end)
{while(isspace(command[end])){end++;}
}void RedirCheck(char command[])
{ redir = NONE_REDIR;filename.clear();int start = 0, end = strlen(command) - 1;while(end > start){if(command[end] == '<'){command[end++] = '\0';TrimSpace(command, end);redir = INPUT_REDIR;filename = command + end;break;}else if(command[end] == '>'){if(command[end - 1] == '>'){command[end - 1] = '\0';end++;TrimSpace(command, end);redir = APPEND_REDIR;filename = command + end;break;}else {command[end++] = '\0';TrimSpace(command, end);redir = OUTPUT_REDIR;filename = command + end;break;}}else {end--;}}
}int main()
{//shell啟動的時候,需用從系統中獲取環境變量//我們的環境變量信息應該從父shell中獲取InitEnv();while(true){ //1.輸出命令行提示符PrintCommandLinePrompt();//2.獲取用戶輸入的命令char command[COMMAND_SIZE];if(!GetCommand(command, sizeof(command)))continue;//3.重定向分析:"ls -a -l > file.txt" -> "ls -a -l" "file.txt" -> 判定重定向方式RedirCheck(command); //printf("redir = %d, filename = %s\n", redir, filename.c_str());//4.命令解析:"ls -a -l" -> "ls"、"-a"、"-l" if(!CommandPrase(command))continue;//PrintArgv();//檢測別名//5.檢測并處理內建命令if(CheckAndExecBuiltin())continue;//6.執行命令ExecuteCommand();}return 0;
}
5.理解一切皆"文件"
- 首先在windows中是文件的東西,它們在linux中也是文件。其次一些在windows中不是文件的東西(進程、磁盤、顯示器、鍵盤)這樣硬件設備也被抽象成了文件,你可以使用訪問文件的方法訪問它們獲得信息。甚至管道也是文件,網絡編程中的socket(套接字)這樣的東西,使用的接口跟文件接口也是一致的。
- 這樣做最明顯的好處是,開發者僅需要使用一套 API 和開發工具,即可調取 Linux 系統中絕大部分的資源。舉個簡單的例子,Linux 中幾乎所有讀(讀文件,讀系統狀態,讀 PIPE)的操作都可以用 read 函數來進行。幾乎所有更改(更改文件,更改系統參數,寫 PIPE)的操作都可以用 write 函數來進行。
- 當打開一個文件時,操作系統為了管理所打開的文件,都會為這個文件創建一個 file 結構體,該結構體定義在 /usr/src/kernels/3.10.0-1160.71.1.el7.x86_64/include/linux/fs.h 下,以下展示了該結構部分我們關系的內容:
struct file
{//...struct inode* f_inode; /* cached value */const struct file_operations* f_op;//...atomic_long_t f_count; //表示打開文件的引用計數,如果有多個文件指針指向它,就會增加f_count的值unsigned int f_flags; //表示打開文件的權限fmode_t f_mode; //設置對文件的訪問模式,例如:只讀,只寫等。loff_t f_pos; //表示當前讀寫文件的位置 //...
};
值得關注的是 struct file 中的 f_op 指針指向了一個 file_operations 結構體,這個結構體中的成員中存在
read 和 write 等函數指針。如下:
struct file_operations
{//...ssize_t(*read) (struct file*, char __user*, size_t, loff_t*);ssize_t(*write) (struct file*, const char __user*, size_t, loff_t*);//...
};
file_operation 就是把系統調用和驅動程序關聯起來的關鍵數據結構,這個結構的每一個成員都對應著一個系統調用。讀取 file_operation 中相應的函數指針,接著把控制權轉交給函數,從而完成了Linux設備驅動程序的工作。一張圖總結如下:
上圖中的外設,每個設備都可以有自己的 read 和 write,但對應著不同的操作方法!通過 struct file 下 file_operation 中的各種函數回調,讓我們開發者只用 file 便可調取 Linux 系統中絕大部分的資源!這便是 “linux下一切皆文件” 的核心理解。
6.緩沖區
1.什么是緩沖區?
緩沖區:內存中預留了一段存儲空間,這些空間用來緩沖輸入或輸出的數據,該空間就叫做緩沖區。緩沖區根據其對應的是輸入設備還是輸出設備,分為輸入緩沖區和輸出緩沖區。
以上是什么導致的呢?
- 通過C語言中的庫函數(printf/fprintf/fputs/fwrite)輸出數據,并不是直接寫到文件內核緩沖區中(若是直接寫到文件內核緩沖區中,那么進程關閉該文件時,會將緩沖區中的內容刷新到外設中,上面的代碼并未出現該結果)。而是在C語言的標準庫中,它為每一個打開的文件創建一個用戶層,語言級緩沖區。通過系統調用(write)輸出數據,直接刷新到文件內核緩沖區中。
- 當用戶
強制刷新 / 篩選條件滿足 / 進程退出
時:由C標準庫根據文件描述符fd + 系統調用(write),將語言級緩沖區中的內容刷新到文件內核緩沖區中。- 在調用 close 之前,進程還未退出,即沒有強制刷新 / 篩選條件滿足 / 進程退出,數據會一直在C標準庫中的語言級緩沖區。當調用 close 時,文件描述符 fd 被關閉,然后進程退出了,此時打算將語言級緩沖區中的內容刷新到文件內核緩沖區,但是調系統調用時 fd 被關了,無法刷新到文件內核緩沖區,就無法看見內容。
2.FILE
- 問題:每一個文件都有自己對應的語言級緩沖區,那么語言級緩沖區在哪里呢?
- 答案:FILE 是 C 語言中的結構體,其中封裝了
文件描述符fd
和語言級緩沖區
如下是FILE的部分內容:
typedef struct _IO_FILE FILE;struct _IO_FILE
{int _fileno; //封裝的文件描述符//緩沖區相關//...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. *///...
};
3.緩沖類型
- 全緩沖:要求填滿整個緩沖區后才進行 I/O 系統調用操作。對于磁盤文件的操作通常使用全緩沖。
- 行緩沖:當在輸入和輸出中遇到換行符時,標準 I/O 庫函數將會執行系統調用操作。對于顯示器通常使用行緩沖。
- 無緩沖:標準 I/O 庫不對字符進行緩存,直接調用系統調用。標準出錯流 stderr 通常是不帶緩沖區的,這使得出錯信息能夠盡快地顯示出來。
數據交給系統,交給硬件的本質全是拷貝。計算機流動的本質:一切皆拷貝!
4.為什么要引入緩沖區機制
- 讀寫文件時,如果不會開辟對文件操作的緩沖區,直接通過系統調用對磁盤進行讀、寫等操作,那么每次對文件進行一次讀寫操作時,都需要使用讀寫系統調用來處理此操作,即需要執行一次系統調用,執行一次系統調用將涉及到CPU狀態的切換,即從用戶空間切換到內核空間,實現進程上下文的切換,這將損耗一定的CPU時間,頻繁的磁盤訪問對程序的執行效率造成很大的影響。
- 為了減少使用系統調用的次數,提高效率,我們就可以采用緩沖機制。比如我們從磁盤里取信息,可以在磁盤文件進行操作時,可以一次從文件中讀出大量的數據到緩沖區中,以后對這部分的訪問就不需要再使用系統調用了,等緩沖區的數據取完后再去磁盤中讀取,這樣就可以減少磁盤的讀寫次數,再加上計算機對緩沖區的操作快于對磁盤的操作,故應用緩沖區可大大提高計算機的運行速度。
- 又比如,我們使用打印機打印文檔,由于打印機的打印速度相對較慢,我們先把文檔輸出到打印機相應的緩沖區,打印機再自行逐步打印,這時我們的CPU可以處理別的事情。可以看出,緩沖區就是一塊內存區,它用在輸入輸出設備和CPU之間,?來緩存數據。它使得低速的輸入輸出設備和高速的CPU能夠協調工作,避免低速的輸入輸出設備占用CPU,解放出CPU,使其能夠高效率工作。
- 多次執行 printf 函數可能內部只執行一次系統調用 write,可以減少系統調用的次數,提高效率。