【Linux】基礎 IO(文件描述符、重定向、緩沖區)

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.理解文件

狹義理解:

  1. 文件在磁盤中。
  2. 磁盤是永久性存儲介質,因此文件在磁盤上的存儲是永久性的。
  3. 磁盤是外設(輸出設備/輸入設備)
  4. 磁盤上的文件本質是對文件的所有操作,都是對外設的輸入和輸出,簡稱 “IO”。

廣義理解:

Linux 下一切皆文件(鍵盤、顯示器、網卡、磁盤……)

文件操作:

  1. 對于 0KB 的空文件是占用磁盤空間的。
  2. 文件 = 文件屬性(元數據) + 文件內容。
  3. 所有的文件操作本質是文件內容操作和文件屬性的操作。

系統角度:

  1. 訪問文件的前提是先打開文件,誰打開文件呢?答案是 “進程打開文件”。對文件的操作本質就是 “進程對文件的操作”。
  2. 磁盤的管理者是操作系統,訪問文件本質就是 “訪問磁盤”,只有操作系統才能訪問磁盤文件。
  3. 文件的讀寫本質不是通過C語言/C++的庫函數來操作的,這些庫函數只是為用戶提供方便,而是通過文件相關的系統調用接口來實現的。fopen 和 fclose 封裝了操作系統對文件的系統調用。
  4. 操作系統通過 “先描述,再組織” 的方式對文件進行管理。在操作系統內部對被打開的文件創建 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

  1. C 默認會打開三個輸入輸出流,分別是stdin、stdout、stderr
  2. 仔細觀察發現,這三個流的類型都是FILE*, fopen返回值類型是文件指針。
#include <stdio.h>extern FILE *stdin;   //標準輸入:鍵盤文件
extern FILE *stdout;  //標準輸出:顯示器文件
extern FILE *stderr;  //標準錯誤:顯示器文件

5.打開文件的方式

在這里插入圖片描述

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

3.系統接口 IO

  1. 打開文件的方式不僅僅是fopen,ifstream等語言層的方案,其實系統才是打開文件最底層的方案。
  2. 不過,在學習系統文件 IO 之前,先要了解下如何給函數傳遞標志位,該方法在系統文件 IO 接口中會使用到:

1.傳遞標志位

  1. 當存在多個標記位時,一般的做法是傳遞多個參數,用起來非常麻煩。
  2. 操作系統采用 “位圖” 來 “傳遞標志位” 的方式,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);
  1. pathname:要打開或創建的目標文件。
  2. flags:文件描述符。打開文件時,可以傳入多個參數選項,用下面的一個或者多個常量進行 “按位或” 運算,構成 flags。參數:O_RDONLY(只讀打開)、O_WRONLY(只寫打開)、O_RDWR(讀,寫打開)。這三個常量,必須指定一個且只能指定一個。O_CREAT:若文件不存在,則創建它。O_APPEND:追加寫。
  3. mode:當文件不存在時,以O_WRONLY打開文件,指明創建新文件的訪問權限。
  4. 返回值:成功時返回新打開的文件描述符。失敗時返回-1
#include <unistd.h>int close(int fd);
  1. fd:文件描述符,open 函數的返回值。
  2. 返回值:成功時返回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);
  1. fd:文件描述符,open 函數的返回值。
  2. buf:指向要寫入的數據的指針。
  3. count:指定了要寫入的字節數。
  4. 返回值:成功時返回真實寫入文件的字節數,失敗時返回-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);
  1. fd:文件描述符。
  2. buf:將數據讀入到該指針 buffer 指向的字符串中。
  3. count:需要讀取的字節數。
  4. 返回值:成功時返回讀取的字節數,失敗時返回-1

在這里插入圖片描述
在這里插入圖片描述

4.文件描述符

1.是什么?

文件描述符就是從 0 開始的小整數。當我們打開文件時,操作系統在內存中要創建相應的數據結構來描述目標文件。于是就有了 file 結構體。表示一個已經打開的文件對象。而進程執行 open 系統調用,所以必須讓進程和文件關聯起來。每個進程都有一個指針 *files,指向一張表files_struct,該表最重要的部分就是包含一個指針數組,每個元素都是一個指向打開文件的指針!本質上,文件描述符就是該數組的下標。所以,只要拿著文件描述符,就可以找到對應的文件。

在這里插入圖片描述

  1. 文件描述符:進程對應的文件描述符表的數組下標。
  2. 當用戶層進行 open(“log.txt”, “w”) 調用時:操作系統創建新的 struct file,在文件描述符表中找到未被使用的下標,將 struct file 的地址填寫進去,此時進程與文件就關聯了。
  3. 當用戶層進行 read(fd, buffer, sizeof(buffer)) 調用時:操作系統拿著 fd 索引文件描述符表找到對應的 struct file,每一個 struct file 都對應內存中的一個 “文件緩沖區”,操作系統先將磁盤文件中的內容預加載到文件緩沖區中,再將文件緩沖區中的內容拷貝到 buffer 中。read 函數本質:內核到用戶空間的拷貝函數。
  4. 當用戶層進行 write(fd, buffer, strlen(buffer)) 調用時:先將 buffer 指向的內容拷貝到文件緩沖區中,再將緩沖區中的內容定期刷新到磁盤文件中。
  5. 對文件做任何操作,都必須先將文件加載(磁盤->內存的拷貝)到內核對應的文件緩沖區中。

內核代碼如下:
在這里插入圖片描述

通過 open 系統調用的返回值,得知文件描述符(fd)是一個整數。 如下代碼所示:

在這里插入圖片描述
在這里插入圖片描述
思考:文件描述符為什么從3開始,值為0、1、2 的文件描述符是什么?

  1. 文件描述符值為0、1、2 分別是:標準輸入、標準輸出、標準錯誤。
  2. C語言中的 fopen 返回值 FILE* 中的 FILE 是一個結構體。
  3. 在操作系統接口層面上,只認文件描述符 fd
  4. 結構體 FILE 一定封裝了文件描述符 fd

在這里插入圖片描述

封裝:

  1. 在 Windows、Linux 等不同的平臺下的系統調用不同,使用系統調用不具備可移植性。
  2. C/C++封裝各個平臺關于文件操作的系統調用,在不同的平臺下通過條件編譯進行裁剪,成為語言級接口,具備可移植性。
  3. 語言增加可移植性的原因:為了讓更多人使用,防止被淘汰。

2.分配規則

文件描述符的分配規則:在 struct file* array[] 數組當中,找到當前沒有被使用的最小的一個下標,作為新的文件描述符。

在這里插入圖片描述

3.重定向原理

在這里插入圖片描述

解釋圖如下:

在這里插入圖片描述
在這里插入圖片描述

  1. printf 函數就是往 stdout 文件中打印內容,也就是文件描述符值為 1 的文件,但修改了 fd_array[1] 的指向時,便打印到了 log.txt 文件中。
  2. 更改文件描述符表中 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.標準錯誤重定向

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

  1. 思考:都是輸出到顯示器中,為什么要區分標準輸出printf、cout和標準錯誤perror、cerr / 為什么存在標準錯誤?
  2. 答案:標準輸出和標準錯誤占用不同的文件描述符。雖然都是顯示器,但是可以通過重定向,將常規消息和錯誤消息分離。

在這里插入圖片描述

將標準輸出和標準錯誤都重定向到一個文件中該如何做呢?

在這里插入圖片描述

6.自定義shell添加重定向功能

  1. 如果內建命令做重定向,需要更改 shell 的標準輸入、輸出、錯誤。此時需要創建臨時文件,類似兩個整數交換的過程,進行一次重定向后需要恢復。
  2. 一個文件可以被多個進程打開。若一個進程將其中一個文件關閉,就會影響其它正在讀取該文件的進程。所以 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.理解一切皆"文件"

在這里插入圖片描述

  1. 首先在windows中是文件的東西,它們在linux中也是文件。其次一些在windows中不是文件的東西(進程、磁盤、顯示器、鍵盤)這樣硬件設備也被抽象成了文件,你可以使用訪問文件的方法訪問它們獲得信息。甚至管道也是文件,網絡編程中的socket(套接字)這樣的東西,使用的接口跟文件接口也是一致的。
  2. 這樣做最明顯的好處是,開發者僅需要使用一套 API 和開發工具,即可調取 Linux 系統中絕大部分的資源。舉個簡單的例子,Linux 中幾乎所有讀(讀文件,讀系統狀態,讀 PIPE)的操作都可以用 read 函數來進行。幾乎所有更改(更改文件,更改系統參數,寫 PIPE)的操作都可以用 write 函數來進行。
  3. 當打開一個文件時,操作系統為了管理所打開的文件,都會為這個文件創建一個 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.什么是緩沖區?

緩沖區:內存中預留了一段存儲空間,這些空間用來緩沖輸入或輸出的數據,該空間就叫做緩沖區。緩沖區根據其對應的是輸入設備還是輸出設備,分為輸入緩沖區和輸出緩沖區。

在這里插入圖片描述

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

以上是什么導致的呢?

在這里插入圖片描述

  1. 通過C語言中的庫函數(printf/fprintf/fputs/fwrite)輸出數據,并不是直接寫到文件內核緩沖區中(若是直接寫到文件內核緩沖區中,那么進程關閉該文件時,會將緩沖區中的內容刷新到外設中,上面的代碼并未出現該結果)。而是在C語言的標準庫中,它為每一個打開的文件創建一個用戶層,語言級緩沖區。通過系統調用(write)輸出數據,直接刷新到文件內核緩沖區中。
  2. 當用戶強制刷新 / 篩選條件滿足 / 進程退出時:由C標準庫根據文件描述符fd + 系統調用(write),將語言級緩沖區中的內容刷新到文件內核緩沖區中。
  3. 在調用 close 之前,進程還未退出,即沒有強制刷新 / 篩選條件滿足 / 進程退出,數據會一直在C標準庫中的語言級緩沖區。當調用 close 時,文件描述符 fd 被關閉,然后進程退出了,此時打算將語言級緩沖區中的內容刷新到文件內核緩沖區,但是調系統調用時 fd 被關了,無法刷新到文件內核緩沖區,就無法看見內容。

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

2.FILE

  1. 問題:每一個文件都有自己對應的語言級緩沖區,那么語言級緩沖區在哪里呢?
  2. 答案: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.緩沖類型

  1. 全緩沖:要求填滿整個緩沖區后才進行 I/O 系統調用操作。對于磁盤文件的操作通常使用全緩沖。
  2. 行緩沖:當在輸入和輸出中遇到換行符時,標準 I/O 庫函數將會執行系統調用操作。對于顯示器通常使用行緩沖。
  3. 無緩沖:標準 I/O 庫不對字符進行緩存,直接調用系統調用。標準出錯流 stderr 通常是不帶緩沖區的,這使得出錯信息能夠盡快地顯示出來。

在這里插入圖片描述

數據交給系統,交給硬件的本質全是拷貝。計算機流動的本質:一切皆拷貝!

4.為什么要引入緩沖區機制

  1. 讀寫文件時,如果不會開辟對文件操作的緩沖區,直接通過系統調用對磁盤進行讀、寫等操作,那么每次對文件進行一次讀寫操作時,都需要使用讀寫系統調用來處理此操作,即需要執行一次系統調用,執行一次系統調用將涉及到CPU狀態的切換,即從用戶空間切換到內核空間,實現進程上下文的切換,這將損耗一定的CPU時間,頻繁的磁盤訪問對程序的執行效率造成很大的影響。
  2. 為了減少使用系統調用的次數,提高效率,我們就可以采用緩沖機制。比如我們從磁盤里取信息,可以在磁盤文件進行操作時,可以一次從文件中讀出大量的數據到緩沖區中,以后對這部分的訪問就不需要再使用系統調用了,等緩沖區的數據取完后再去磁盤中讀取,這樣就可以減少磁盤的讀寫次數,再加上計算機對緩沖區的操作快于對磁盤的操作,故應用緩沖區可大大提高計算機的運行速度。
  3. 又比如,我們使用打印機打印文檔,由于打印機的打印速度相對較慢,我們先把文檔輸出到打印機相應的緩沖區,打印機再自行逐步打印,這時我們的CPU可以處理別的事情。可以看出,緩沖區就是一塊內存區,它用在輸入輸出設備和CPU之間,?來緩存數據。它使得低速的輸入輸出設備和高速的CPU能夠協調工作,避免低速的輸入輸出設備占用CPU,解放出CPU,使其能夠高效率工作。
  4. 多次執行 printf 函數可能內部只執行一次系統調用 write,可以減少系統調用的次數,提高效率。

5.設計文件libc庫

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/901558.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/901558.shtml
英文地址,請注明出處:http://en.pswp.cn/news/901558.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Apache Doris SelectDB 技術能力全面解析

Apache Doris 是一款開源的 MPP 數據庫&#xff0c;以其優異的分析性能著稱&#xff0c;被各行各業廣泛應用在實時數據分析、湖倉融合分析、日志與可觀測性分析、湖倉構建等場景。Apache Doris 目前被 5000 多家中大型的企業深度應用在生產系統中&#xff0c;包含互聯網、金融、…

交換機與路由器的默契配合:它們的聯系與區別

交換機與路由器的默契配合&#xff1a;它們的聯系與區別 一. 交換機與路由器的基本功能1.1 交換機的功能1.2 路由器的功能 二. 交換機和路由器的區別三. 交換機和路由器的聯系3.1 數據轉發的協作3.2 網絡分段與分隔3.3 協同工作提供互聯網接入 四. 交換機和路由器的聯合應用場景…

【計算機系統結構】MIPSsim

目錄 雙擊MIPSsim.exe 問題1&#xff1a;Microsoft Defender SmartScreen阻止了無法是被的應用啟動&#xff0c;運行此應用可能會導致你的電腦存在風險 解決 出現下面的問題的話&#xff0c;建議直接在官網下載 問題2&#xff1a;.NET Framework 3.5安裝錯誤代碼0x80240438 …

map 中key 是否可以放置的自定義的對象?

在 Java 中,可以將自定義對象作為 Map 的 Key,但必須滿足以下條件: 1. 必須正確重寫 hashCode() 和 equals() 方法 原因:Map(如 HashMap)依賴這兩個方法確定鍵的唯一性和存儲位置。未正確重寫的風險: 無法正確查找值:即使兩個對象邏輯上相等,若 hashCode 不同,會被視…

【筆記ing】AI大模型-04邏輯回歸模型

一個神經網絡結構&#xff0c;其中的一個神經網絡層&#xff0c;本質就是一個邏輯回歸模型 深度神經網絡的本質就是多層邏輯回歸模型互相連接或采用一定的特殊連接的方式連接在一起構成的。其中每一個層本質就是一個邏輯回歸模型。 邏輯回歸模型基本原理 邏輯回歸&#xff0…

Android學習總結之算法篇七(圖和矩陣)

有向圖的深度優先搜索&#xff08;DFS&#xff09;和廣度優先搜索&#xff08;BFS&#xff09;的示例&#xff0c;以此來模擬遍歷 GC Root 引用鏈這種有向圖結構&#xff1a; 一、深度優先搜索&#xff08;DFS&#xff09; import java.util.*;public class GraphDFS {privat…

熟悉Linux下的編程

可能 目錄 熟悉Linux下Python編程的含義及與非Linux環境編程的區別 一、核心含義解析 二、與非Linux環境的關鍵區別 三、典型應用場景對比 四、能力培養建議 openfoem的下載之路&#xff1a; 方法一&#xff1a;使用cd命令 方法二&#xff1a;使用快捷方式 方法三&am…

c++引入nacos,詳細步驟

以下是將Nacos引入C項目的詳細步驟&#xff0c;包括安裝、配置和代碼實現&#xff1a; 1. 安裝Nacos服務器 下載Nacos服務器安裝包&#xff0c;可以從Nacos官網獲取最新版本。 解壓安裝包并啟動Nacos服務器&#xff1a; cd nacos/bin sh startup.sh -m standalone 這將啟動…

性能優化實踐

4.1 大規模量子態處理的性能優化 背景與問題分析 量子計算中的大規模量子態處理(如量子模擬、量子態可視化)需要高效計算和實時渲染能力。傳統圖形API(如WebGL)在處理高維度量子態時可能面臨性能瓶頸,甚至崩潰(如表格中14量子比特時WebGL的崩潰)。而現代API(如WebGPU…

課堂總結。

第三章第六節 Spark-SQL核心編程&#xff08;五&#xff09;自定義函數&#xff1a;UDF&#xff1a;val sparkConf new SparkConf().setMaster("local[*]").setAppName("SQLDemo")//創建SparkSession對象val spark :SparkSession SparkSession.builder()…

分庫分表-除了hash分片還有別的嗎?

在分庫分表的設計中,除了常見的 Hash 分片,還有多種策略根據業務場景靈活選擇。以下是幾種主流的分庫分表策略及其應用場景、技術實現和優缺點分析,結合項目經驗(如標易行投標服務平臺的高并發場景)進行說明: 一、常見分庫分表策略 1. 范圍分片(Range Sharding) 原理:…

AUTOSAR圖解==>AUTOSAR_SWS_GPTDriver

AUTOSAR GPT驅動 (通用定時器驅動) 分析 AUTOSAR標準軟件規范解析 目錄 1. GPT驅動概述 1.1 GPT驅動在AUTOSAR架構中的位置1.2 GPT驅動主要功能 2. GPT驅動模塊結構3. GPT驅動初始化流程4. GPT驅動狀態機5. GPT驅動錯誤處理6. GPT預定義定時器7. 總結 1. GPT驅動概述 GPT驅動…

MyBatis持久層框架

MyBatis持久層框架 目錄 一、Mybatis簡介 1. 簡介 2. 持久層框架對比 3. 快速入門&#xff08;基于Mybatis3方式&#xff09; 二、日志框架擴展 1. 用日志打印替代sout 2. Java日志體系演變 3. 最佳拍檔用法 4. Lombok插件的使用 4.1 Lombok簡介 4.2 Lombok安裝 4.3 …

域控制器升級的先決條件驗證失敗,證書服務器已安裝

出現“證書服務器已安裝”導致域控制器升級失敗時&#xff0c;核心解決方法是卸載已安裝的證書服務?。具體操作如下&#xff1a;? ?卸載證書服務? 以管理員身份打開PowerShell&#xff0c;執行命令&#xff1a; Remove-WindowsFeature -Name AD-Certificate該命令會移除A…

VMware虛擬機常用Linux命令進階指南(一)

摘要&#xff1a;本文涵蓋多方面 Linux 命令的使用。包括用戶與用戶組管理&#xff0c;創建用戶和組并設置權限&#xff1b;目錄結構操作&#xff0c;涉及創建和更改目錄結構&#xff1b;Vim 編輯器及文件歸檔&#xff0c;有文件創建、編譯、合并、打包等任務。 更多優質文章 …

【AI News | 20250415】每日AI進展

AI News 1、字節跳動發布Seaweed-7B視頻模型&#xff1a;70億參數實現音視頻同步生成與多鏡頭敘事 字節跳動推出新一代視頻生成模型Seaweed-7B&#xff0c;該模型僅70億參數卻實現多項突破&#xff1a;支持音視頻同步生成、多鏡頭敘事&#xff08;保持角色連貫性&#xff09;、…

如何實現動態請求地址(baseURL)

需求: 在項目中遇到了需要實時更換請求地址,后續使用修改后的請求地址(IP) 例如:原ip請求為http://192.168.1.1:80/xxx,現在需要你點擊或其他操作將其修改為http://192.168.1.2:80/xxx,該如何操作 tips: 修改后需要跳轉( 修改了IP之前的不可使用,需要訪問修改后的地址來操作 …

Open AI 使用篇

一.function Calling 大模型中的 function calling 指的是在人工智能模型&#xff08;如 GPT-4&#xff09;中調用外部函數或API&#xff0c;以便模型能夠執行更復雜的任務或獲取外部數據。這種方式允許模型在生成回答時不僅僅依賴于內部的訓練數據&#xff0c;還能夠與外部系…

6.DJI-PSDK:psdk訂閱無人機高度/速度/GPS/RTK/時間/經緯度等消息及問題解決

DJI-PSDK:psdk訂閱無人機高度/速度/GPS/RTK/時間/經緯度等消息 消息訂閱可以獲取絕大多數無人機的動態信息,包括無人機的姿態、速度、加速度、角速度、高度、GPS 位置、云 臺的角度和狀態、飛行模式和飛行狀態、電機和電池等各類關鍵信息。 這些信息并不會“一股腦兒地”全部…

100 個網絡安全基礎知識

1. 什么是網絡安全&#xff1f; 網絡安全是指采取必要措施&#xff0c;防范對網絡的攻擊、侵入、干擾、破壞和非法使用以及意外事故&#xff0c;使網絡處于穩定可靠運行的狀態&#xff0c;保障網絡數據的完整性、保密性、可用性。&#xff08;參考《中華人民共和國網絡安全法》…