Linux——基礎IO(1)

?

目錄

0. 文件先前理解

1. C文件接口

1.1 寫文件

1.2?讀文件

1.3?輸出信息到顯示器

1.4 總結 and stdin & stdout & stderr

2. 系統調用文件I/O

2.1 系統接口使用示例

2.2?接口介紹

2.3?open函數返回值

3. 文件描述符fd及重定向

3.1?0 & 1 & 2

3.2 文件描述符fd的理解

3.3 文件描述符Linux內核源碼分析

3.4?文件描述符的分配規則

3.5?重定向

3.6 使用 dup2 系統調用

4. 在minishell中添加重定向功能

5. 有關FILE緩沖區及Linux一切皆文件理解

5.1 一切皆文件及C語言實現運行時多態

5.2 緩沖區

5.3?C標準庫FILE結構體及緩沖區

5.4 大致模擬緩沖區設計


0. 文件先前理解

文件 = 文件內容 + 文件屬性

因此,對文件進行的操作無非:對內容,對屬性

文件本質存放在磁盤上,訪問文件,先寫代碼 -> 編譯 -> exe -> 運行 ->訪問文件,其本質是進程在訪問文件!!!

????????要向硬件寫入,只有操作系統具備權利,而普通用戶寫入,則需要使用操作系統提供的文件類系統調用接口,而語言層面對系統調用接口進行了封裝,由于不同操作系統平臺提供的系統調用接口不同,語言把所有平臺的代碼都實現條件編譯—動態裁剪,使其語言具有跨平臺性!如果語言不提供對文件的系統接口的封裝,所有訪問文件的操作,都必須使用OS的接口,一旦使用系統接口,編寫所謂的文件代碼,就無法在其他平臺安全運行,就不再具有跨平臺性。

學習OS層面的文件系統,本質是為了更好的理解不同語言底層操作實際是相通的!

顯示器是硬件,而printf向顯示器打印,本質也是一種寫入!

Linux下,一切皆文件,又該如何理解?

文件而言:

曾經理解的文件,站在寫程序的角度,將文件打開,加載到內存,進行read,write

顯示器:printf,cout -> 一種write

鍵盤:scanf,cin -> 一種read

普通文件 -> fopen/fread -> 你的進程內部(內存) -> fwrite -> 文件中

? ? ? ? ?????????????????input? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? output

站在系統的角度,能夠被input讀取,或者output寫出的設備就叫做文件!

狹義的文件——普通磁盤文件

廣義上的文件——顯示器,鍵盤,網卡,聲卡,顯卡,磁盤。幾乎所有外色,都可以稱之為文件

1. C文件接口

打開文件:fopen

打開方式: r,r+,w,w+,a,a+...

fopen以當前路徑打開文件或者相對路徑打開文件

當前路徑默認是:當一個進程運行起來,在其pcb結構體中,沒個進程都會記錄當前所處的工作路徑—cwd(創建工作的目錄)即為該進程的當前路徑!

1.1 寫文件

//打開文件

FILE * fopen ( const char * filename, const char * mode );

//關閉文件

int fclose ( FILE * stream );

//fwrite:

size_t?fwrite(?const?void?*buffer,?size_t?size,?size_t?count,?FILE?*stream?);

buffer—要寫入的數據? size—一個元素的大小 count—幾個元素 stream—文件流

#include <stdio.h>
#include <string.h>
int main()
{FILE* fp = fopen("myfile", "w");if (!fp) {printf("fopen error!\n");}const char* msg = "hello Linux!\n";int count = 5;while (count--) {fwrite(msg, strlen(msg), 1, fp);}fclose(fp);return 0;
}

注意:這里寫入字符串不計算'\0',因為字符串結束標志是C語言提供的,而不是操作系統所具有的!

1.2?讀文件

//fread:

size_t?fread(?void?*buffer,?size_t?size,?size_t?count,?FILE?*stream?);

#include <stdio.h>
#include <string.h>
int main()
{FILE* fp = fopen("myfile", "r");if (!fp) {printf("fopen error!\n");}char buf[1024];const char* msg = "hello Linux!\n";while (1) {size_t s = fread(buf, 1, strlen(msg), fp);if (s > 0) {buf[s] = 0;printf("%s", buf);}if (feof(fp)) {break;}}fclose(fp);return 0;
}

1.3?輸出信息到顯示器

將寫入的數據流入stdout,即輸出到顯示器

#include <stdio.h>
#include <string.h>
int main()
{const char* msg = "hello fwrite\n";fwrite(msg, strlen(msg), 1, stdout);printf("hello printf\n");fprintf(stdout, "hello fprintf\n");return 0;
}

1.4 總結 and stdin & stdout & stderr

C默認會打開三個輸入輸出流,分別是stdin, stdout, stderr

仔細觀察發現,這三個流的類型都是FILE*, fopen返回值類型,文件指針

打開文件的方式:

r? ? ? ? ? Open text file for reading. The stream is positioned at the beginning of the file.

r+ ???????Open for reading and writing. The stream is positioned at the beginning of the file.

w ????????Truncate(縮短) file to zero length or create text file for writing. The stream is? ? ? ? ? ? ? ? ? ? ? ? ? positioned at the beginning of the file.

w+ ??????Open for reading and writing.The file is created if it does not exist, otherwise it is? ? ? ? ? ? ? ? ? ? truncated. The stream is positioned at the beginning of the file.

a? ? ? ? ? Open for appending (writing at end of file). The file is created if it does not exist.? ? ? ? ? ? ? ? ? ? The?stream is positioned at the end of the file.

a+ ???????Open for reading and appending (writing at end of file). The file is created if it does? ? ? ? ? ? ? ? not exist. The initial file position for reading is at the beginning of the file, but output? ? ? ? ? ? ? is always appended to the end of the file.

如上,是我們之前學的文件相關操作。還有 fseek ftell rewind 的函數,在C部分已經有所涉獵,請同學們自 行復習。

2. 系統調用文件I/O

操作文件,除了上述C接口(當然,C++也有接口,其他語言也有),我們還可以采用系統接口來進行文件訪問, 先來直接以代碼的形式,實現和上面一模一樣的代碼!

2.1 系統接口使用示例

open close read write O_CREAT O_TRUNC O_WRONLY O_RDONLY O_RDWR...

寫文件:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{umask(0);int fd = open("myfile", O_WRONLY | O_CREAT, 0644);if (fd < 0) {perror("open");return 1;}int count = 5;const char* msg = "hello Linux!\n";int len = strlen(msg);while (count--) {write(fd, msg, len);//fd: 后續, msg:緩沖區首地址, len: 本次讀取,期望寫入多少個字節的數//據。 返回值:實際寫了多少字節數據}close(fd);return 0;
}

讀文件:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{int fd = open("myfile", O_RDONLY);if (fd < 0) {perror("open");return 1;}const char* msg = "hello Linux!\n";char buf[1024];while (1) {ssize_t s = read(fd, buf, strlen(msg));//類比writeif (s > 0) {printf("%s", buf);}else {break;}}close(fd);return 0;
}

在應用層看到一個很簡單的動作,在系統接口層面甚至OS層面,可能要做非常多的動作!

2.2?接口介紹

open ——?man open

#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 : 若文件不存在,則創建它。需要使用mode選項,來指明新文件的訪問權限 O_APPEND: 追加寫
返回值:成功:新打開的文件描述符失敗:-1

mode_t理解:直接 man 手冊,比什么都清楚。

open 函數具體使用哪個,和具體應用場景相關,如目標文件不存在,需要open創建,則第三個參數表示創建文件的默認權限,否則,使用兩個參數的open。

2.3?open函數返回值

在認識返回值之前,先來認識一下兩個概念: 系統調用 和 庫函數

  • 上面的 fopen fclose fread fwrite 都是C標準庫當中的函數,我們稱之為庫函數(libc)
  • 而, open close read write lseek 都屬于系統提供的接口,稱之為系統調用接口
  • 操作系統概念時,畫的一張圖

系統調用接口和庫函數的關系,一目了然。 所以,可以認為,f#系列的函數,都是對系統調用的封裝,方便二次開發。

3. 文件描述符fd及重定向

由上述系統調用open可知成功返回新打開的文件描述符(file descriptor)(fp)

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{//未做差錯處理int fd1 = open("myfile1", o_rdonly);int fd2 = open("myfile2", o_rdonly);int fd3 = open("myfile3", o_rdonly);int fd4 = open("myfile4", o_rdonly);printf("open sucess, fd: %d", fd1);printf("open sucess, fd: %d", fd2);printf("open sucess, fd: %d", fd3);printf("open sucess, fd: %d", fd4);close(fd1);close(fd2);close(fd3);close(fd4);return 0;
}

?而打開多個文件,發現其fd從3開始依次遞增!0,1,2去哪里了?

可知文件描述符就是一個小整數,如何理解文件描述符?

3.1?0 & 1 & 2

  • Linux進程默認情況下會有3個缺省打開的文件描述符,分別是標準輸入0, 標準輸出1, 標準錯誤2.
  • 0,1,2對應的物理設備一般是:鍵盤,顯示器,顯示器
  • 所以輸入輸出還可以采用如下方式:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{char buf[1024];ssize_t s = read(0, buf, sizeof(buf));if (s > 0) {buf[s] = 0;write(1, buf, strlen(buf));write(2, buf, strlen(buf));}return 0;
}

3.2 文件描述符fd的理解

FILE* fopen(const char* path, const char* mode);

C標準庫提供的文件操作可知,FILE是一個由C標準庫提供的結構體

由上述可知,C語言庫函數內部一定封裝了系統調用接口,lib在系統調用之上,站在系統角度,操作系統并不認識C語言內部的FILE結構體,只認識fd,因此FILE結構體內部,必定封裝了fd!!

所以,fd是什么?

? ? ? ? 進程想要訪問文件,必須先打開文件

? ? ? ? 一般而言,進程 : 打開文件 = 1 :n

文件要被訪問,前提是加載到內存中,才能直接被訪問!

可知一個進程可以打開多個文件,多個進程運行時,會存在大量的被打開的文件,所以,操作系統需要把這些被各個進程打開的文件管理起來,如何管理?先描述,在組織!!!

?

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

3.3 文件描述符Linux內核源碼分析

文件:磁盤文件(未被打開的文件)????????? ? ? ? 內存文件(被進程打開的文件)

????????因此沒每個進程的PCB中都一個文件表指針struct files_struct * files,指向一個struct files_struct文件表結構體,該文件表結構體內一定包含一個struct file* fd_array[]文件指針數組,其數組每個對應存取指向一個被打開文件結構體file的指針,file記錄了該文件的所有屬性及所有內容。

?

?fwrite() -> FILE* -> fd -> write -> write(fd, ...) -> 自己執行操作系統內部的write方法 -> 能找到進程的task_struct -> *files -> files_struct -> fd_array[] -> fd_array[fd] -> struct file -> 內存文件被找到 -> 操作

#include<stdio.h>
#include<stdlib.h>struct File {void(*read_p)();void(*write_p)();
};void readByKeyBoard() {cout << "從鍵盤讀取" << endl;
}
void writeByKeyBoard() {cout << "nothing to do!" << endl;
}void readByFile() {cout << "從文件讀取" << endl;
}
void writeFile() {cout << "往文件寫入" << endl;
}void TestFile(struct File file) {file.read_p();file.write_p();
}int main() {struct File fileByKB;fileByKB.read_p = readByKeyBoard;fileByKB.write_p = writeByKeyBoard;struct File fileByFILE;fileByFILE.read_p = readByFile;fileByFILE.write_p = writeFile;TestFile(fileByFILE);TestFile(fileByKB);return 0;
}

每個文件被加載到內存,其文件管理創建對應的file結構體,結構體內部會根據驅動填充相應的函數指針write,read等操作函數的地址,因為磁盤、顯示器、鍵盤、網卡等不用硬件的讀寫方法不同,因此為了實現多態調用,C語言可以采用函數指針,實例化對象時填充不同的函數地址,以實現多態調用!

3.4?文件描述符的分配規則

直接看代碼:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{int fd = open("myfile", O_RDONLY);if(fd < 0){perror("open");return 1;}printf("fd: %d\n", fd);close(fd);return 0;
}

輸出發現是 fd: 3

關閉0或者2,在看

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{close(0);//close(2);int fd = open("myfile", O_RDONLY);if (fd < 0) {perror("open");return 1;}printf("fd: %d\n", fd);close(fd);return 0;
}

發現是結果是: fd: 0 或者 fd 2 可見,文件描述符的分配規則:在files_struct數組當中,找到當前沒有被使用的最小的一個下標,作為新的文件描述符。

3.5?重定向

那如果關閉1呢?看代碼:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{close(1);int fd = open("myfile", O_WRONLY | O_CREAT, 00644);if (fd < 0) {perror("open");return 1;}printf("fd: %d\n", fd);fflush(stdout);close(fd);exit(0);
}

此時,發現,本來應該輸出到顯示器上的內容,輸出到了文件 myfile 當中,其中,fd=1。這種現象叫做輸出重定向。常見的重定向有:>, >>, <

?重定向的本質,其實是在OS內部,更改fd對應的內容的指向!!!

3.6 使用 dup2 系統調用

函數原型如下:

#include<unistd.h>?

????????int dup2(int oldfd, int newfd);

這里的oldfd copy 給 newfd,必要時會釋放oldfd

?

代碼如下:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main() {int fd = open("./log", O_CREAT | O_RDWR);if (fd < 0) {perror("open");return 1;}close(1);dup2(fd, 1);for (;;) {char buf[1024] = { 0 };ssize_t read_size = read(0, buf, sizeof(buf) - 1);if (read_size < 0) {perror("read");break;}printf("%s", buf);fflush(stdout);}return 0;
}

?

4. 在minishell中添加重定向功能

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<assert.h>#define NUM 1024
#define SIZE 32
char cmd_line[NUM];void dealStr(char* str, char** argv) {char* dealstr = NULL;const char* sep = " ";size_t i = 0;for (dealstr = strtok(str, sep); dealstr != NULL; dealstr = strtok(NULL, sep)) {if (i < SIZE) {argv[i] = dealstr;i++;}}if(strcmp(argv[0], "ls") == 0){argv[i] = (char*)"--color=auto";}argv[++i] = NULL;
}#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3
#define NONE_REDIR 0
int redir_status = NONE_REDIR;char* CheckRedir(char* start){assert(start);//ls -a -l\0char* end = start + strlen(start) - 1;while(end >= start){if(*end == '>'){if(*(end - 1) == '>'){redir_status = APPEND_REDIR;*(end-1) = '\0';end++;break;}redir_status = OUTPUT_REDIR;*end = '\0';end++;break;//ls -a -l>myfile.txt//ls -a -l>>myfile.txt}else if(*end == '<'){redir_status = INPUT_REDIR;*end = '\0';end++;break;}else{end--;}}if(end >= start){return end;//要打開文件}else{return NULL;}
}//環境變量保存的是地址,而拿不到環境變量本質是地址內容被清空,MY_VAL=NULL
char buffer[64];//shell運行原理:通過讓子進程執行命令,父進程等待&&解析命令
int main(){//extern char** environ;while(1){//1.打印提示信息printf("[root@localhost myshell]#");fflush(stdout);//2.獲取用戶輸入memset(cmd_line, '\0', sizeof cmd_line);char *g_argv[SIZE] = { NULL };if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL){continue;}cmd_line[strlen(cmd_line) - 1] = '\0';//printf("echo:%s\n", cmd_line);//"ls -a -l > log.txt" -> "ls -a -l\0log.txt"char* sep = CheckRedir(cmd_line);//3.命令行字符串分割dealStr(cmd_line, g_argv);     //4.TODO 內置命令,讓父進程(shell)自己執行的命令,叫做內置命令,內建命令// 內建命令本質就是shell中的一個函數調用if(strcmp("cd", g_argv[0]) == 0){//not child execute, father executeif(g_argv[1] != NULL)chdir(g_argv[1]);continue;}if(strcmp("export", g_argv[0]) == 0 && g_argv[1] != NULL){strcpy(buffer, g_argv[1]);putenv(buffer);//int checkPutenv = putenv(buffer);//if(checkPutenv == 0){//    printf("Putenv sucess\n");//    //for(size_t i = 0; environ[i]; i++){//        //printf("%s\n", environ[i]);//   // }//}else{//    printf("Putenc fail\n");//}continue;}//5.forkpid_t id = fork();if(id < 0){perror("fork");exit(1);}else if(id == 0){if(sep != NULL){int fd = -1;switch(redir_status){case INPUT_REDIR:fd = open(sep, O_RDONLY);dup2(fd, 0);break;case OUTPUT_REDIR:fd = open(sep, O_WRONLY | O_TRUNC | O_CREAT, 0666);dup2(fd, 1);break;case APPEND_REDIR:fd = open(sep, O_WRONLY | O_APPEND);dup2(fd, 1);break;default:printf("bug?\n");break;}}//printf("getenv : %s\n", getenv("MY_VAL"));//execvpe(g_argv[0], g_argv, environ);//子進程繼承父進程環境變量execvp(g_argv[0], g_argv);exit(1);}int status = 0;pid_t ret = waitpid(id, &status, 0);if(ret > 0){printf("exit code:%d -> result:%s\n",WEXITSTATUS(status), strerror(WEXITSTATUS(status)));}else{printf("wait fail!\n");exit(1);}}//end whilereturn 0;
}

5. 有關FILE緩沖區及Linux一切皆文件理解

Linux設計則學——一切皆文件——體現在操作系統的軟件設計層面的

5.1 一切皆文件及C語言實現運行時多態

Linux是C語言寫的!如何用C語言實現面向對象,甚至是運行時多態?

可以使用結構體及函數指針實現面向對象!!!及運行時多態!!!

在Linux下,根據馮諾依曼體系結構,大部分硬件都輸入IO設備,IO設備主要用于輸入輸出,因此根據其驅動層提供的讀寫方法,即可實現一切皆文件的概念!

?所有的底層設備,都可以有自己的read和write方法的具體實現,但是,其具體的代碼一定是不一樣的!而通過函數指針使其根據硬件指向不同的讀寫方法,在調用時,就像在使用同一個函數!

5.2 緩沖區

1. 什么是緩沖區??

? ? ? ? 就是一段內存空間(這個空間社提供? 用戶(char buffer[64], scanf(buffer))? 語言? OS?)

2. 為什么要有緩沖區

? ? ? ? 提高整機效率,主要是為了提高用戶的響應速度!

寫透模式(WT模式): 只要寫就刷新,會進行頻繁的IO操作,成本高,且效率慢!

回寫模式(WB模式):提供緩沖區,定義刷新策略,IO操作次數降低,效率高!

緩沖區的刷新策略:

????????1. 立即刷新

????????2. 行刷新(行緩沖)

????????3. 滿刷新(全緩沖)

特殊情況:

????????1. 用戶強制刷新(fflush)

????????2. 進程退出

緩沖策略 = 一般 + 特殊, 緩沖策略是可控制的!

一般而言:行緩沖設備文件 —— 顯示器? ? ? ? ? ?全緩沖設備文件 —— 磁盤文件

所有的設備,永遠都傾向于全緩沖! —— 緩沖區滿了,才刷新 --> 需要更少次的IO操作 --> 更少次的外設訪問 --> 提高效率!

和外部設備進行IO的時候,數據量的大小不是主要矛盾,而和外設預備IO的過程是最耗費時間的!其他刷新策略是結合具體情況所做的妥協!

顯示器:需要直接給用戶看的,一方面照顧效率,一方面照顧用戶體驗,極端情況,是可以自定義規則的!

5.3?C標準庫FILE結構體及緩沖區

因為IO相關函數與系統調用接口對應,并且庫函數封裝系統調用,所以本質上,訪問文件都是通過fd訪問的。 所以C庫當中的FILE結構體內部,必定封裝了fd。

來段代碼在研究一下:

#include <stdio.h>
#include <string.h>
int main()
{const char* msg0 = "hello printf\n";const char* msg1 = "hello fwrite\n";const char* msg2 = "hello write\n";printf("%s", msg0);fwrite(msg1, strlen(msg0), 1, stdout);write(1, msg2, strlen(msg2));fork();return 0;
}

運行出結果:

hello printf
hello fwrite
hello write

但如果對進程實現輸出重定向呢? ./hello > file , 我們發現結果變成了:

hello write
hello printf
hello fwrite
hello printf
hello fwrite

我們發現 printf 和 fwrite (庫函數)都輸出了2次,而 write 只輸出了一次(系統調用)。為什么呢?肯定和 fork有關!

  • 一般C庫函數寫入文件時是全緩沖的,而寫入顯示器是行緩沖。
  • printf fwrite 庫函數會自帶緩沖區(進度條例子就可以說明),當發生重定向到普通文件時,數據 的緩沖方式由行緩沖變成了全緩沖。
  • 而我們放在緩沖區中的數據,就不會被立即刷新,甚至fork之后
  • 但是進程退出之后,會統一刷新,寫入文件當中。
  • 但是fork的時候,父子數據會發生寫時拷貝,所以當你父進程準備刷新的時候,子進程也就有了同樣的 一份數據,隨即產生兩份數據。
  • write 沒有變化,說明沒有所謂的緩沖。

綜上: printf fwrite 庫函數會自帶緩沖區,而 write 系統調用沒有帶緩沖區。另外,我們這里所說的緩沖區, 都是用戶級緩沖區。其實為了提升整機性能,OS也會提供相關內核級緩沖區,不過不再我們討論范圍之內。 那這個緩沖區誰提供呢? printf fwrite 是庫函數, write 是系統調用,庫函數在系統調用的“上層”, 是對系統 調用的“封裝”,但是 write 沒有緩沖區,而 printf fwrite 有,足以說明,該緩沖區是二次加上的,又因為是 C,所以由C標準庫提供。

如果有興趣,可以看看FILE結構體:

在 / usr / include / libio.h
struct _IO_FILE {int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags//緩沖區相關/* 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 0int _blksize;
#elseint _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
};

5.4 大致模擬緩沖區設計

#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
typedef struct MyFILE_{int fd;char buffer[NUM];int end;//當前緩沖區結尾
}MFILE;MFILE* fopen_(const char* pathname, const char* mode){assert(pathname && mode);MFILE* fp = 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_CREAT | O_TRUNC, 0666);if(fd >= 0){fp = (MFILE*)malloc(sizeof(MFILE));memset(fp, 0, sizeof(MFILE));fp->fd = fd;}}else if(strcmp(mode, "w+") == 0){}else if(strcmp(mode, "a") == 0){}else if(strcmp(mode, "a+") == 0){}else{}return fp;
}void fputs_(const char* message, MFILE* fp){assert(message && fp);strcpy(fp->buffer + fp->end, message);fp->end += strlen(message);//for debugfprintf(stderr, "%s\n", fp->buffer);//暫時未刷新//制定刷新策略,刷新策略是由誰來執行的呢?//答案是用戶通過執行C標準庫中的代碼邏輯,來完成刷新//效率提高體現,幾行代碼就減少頻繁的IO操作執行次數(注:不是數據量)//stdinif(fp->fd == 0){//stdout}else if(fp->fd == 1){if(fp->buffer[fp->end-1] == '\n'){
//            //for debug
//            fprintf(stderr, "fllush : %s", fp->buffer);write(fp->fd, fp->buffer, fp->end);fp->end = 0;}//stderr}else if(fp->fd == 2){}else{}
}void fflush_(MFILE* fp){assert(fp);if(fp->end != 0){//暫且認為刷新了 -- 其實是把數據寫到了內核中//如果想將數據由內核刷新到外設,系統調用sync-syncfswrite(fp->fd, fp->buffer, fp->end);syncfs(fp->fd);//將數據寫入磁盤fp->end = 0;}
}void fclose_(MFILE* fp){assert(fp);fflush_(fp);close(fp->fd);free(fp);
}
int main(){//close(1);MFILE* fp = fopen_("./log.txt", "w");if(fp == NULL){printf("open file error\n");return 1;}fputs_("one:hello world\n", fp);//fputs_("two:hello world\n", fp);//fputs_("three:hello world", fp);//fputs_("four:hello world\n", fp);//fputs_("five:hello world", fp);//行刷新策略 one one:two tree tree:four fivefork();//fork時,上下文數據寫時拷貝,在緩沖區的one刷新兩次fclose_(fp);return 0;
}

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

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

相關文章

【Spring Cloud Alibaba】RocketMQ的基礎使用,如何發送消息和消費消息

在現代分布式架構的開發中&#xff0c;消息隊列扮演著至關重要的角色&#xff0c;用于解耦系統組件、保障可靠性以及實現異步通信。RocketMQ作為一款開源的分布式消息中間件&#xff0c;憑借其高性能、高可用性和良好的擴展性&#xff0c;成為了眾多企業在構建高可靠性、高吞吐…

運維面試大全

文章目錄 第一階段平常怎么處理故障,思路是什么樣的公網和私網分類以及范圍,本機地址,網絡地址,廣播地址交換機的工作原理ICMP是什么干什么用的,它有哪些命令TCP和UDP協議的區別tcp有哪些控制位,分別是什么意思你是用過哪些Linux命令Linux 系統安全優化與內核優化經常使用…

stable diffusion 單張圖片換頭roop安裝配置

1.首先安裝秋葉大佬的webui 2.然后在拓展里面搜索roop,下載roop插件,然后重啟webui 3.重啟后,在文生圖和圖生圖的界面,就可以看到roop的入口 4.這里面,需要提前安裝Visual Studio. 勾選一些必要的選項,這里可以參照b站的視頻 # 秋葉版本Stablediffusion的Roop插件的安裝 …

JavaScript reduce深入了解

reduce() 是 JavaScript 數組的一個高階函數&#xff0c;它允許你將數組中的元素按順序依次合并為一個單一的值。reduce() 可以用于數組求和、計算平均值、連接字符串等各種情況。它的工作原理是通過迭代數組的每個元素&#xff0c;然后將元素和累加器進行某種操作&#xff0c;…

使用 Python 在 NLP 中進行文本預處理

一、說明 自然語言處理 &#xff08;NLP&#xff09; 是人工智能 &#xff08;AI&#xff09; 和計算語言學的一個子領域&#xff0c;專注于使計算機能夠理解、解釋和生成人類語言。它涉及計算機和自然語言之間的交互&#xff0c;允許機器以對人類有意義和有用的方式處理、分析…

Java # JVM內存管理

一、運行時數據區域 程序計數器、Java虛擬機棧、本地方法棧、Java堆、方法區、運行時常量池、直接內存 二、HotSpot虛擬機對象 對象創建&#xff1a; 引用檢查類加載檢查分配內存空間&#xff1a;指針碰撞、空閑列表分配空間初始化對象信息設置&#xff08;對象頭內&#xff0…

?可視化繪圖技巧100篇進階篇(五)-階梯線圖(Step Chart)

目錄 前言 圖表類型特征 適用場景 圖例 繪圖工具及代碼實現 ECharts SMARTBI

安卓中常見的字節碼指令介紹

問題背景 安卓開發過程中&#xff0c;經常要通過看一些java代碼對應的字節碼&#xff0c;來了解java代碼編譯后的運行機制&#xff0c;本文將通過一個簡單的demo介紹一些基本的字節碼指令。 問題分析 比如以下代碼&#xff1a; public class test {public static void main…

Java課題筆記~ JSP編程

4.1 JSP基本語法 JSP (全稱Java Server Pages) 是由 Sun Microsystems 公司倡導和許多公司參與共同創建的一種使軟件開發者可以響應客戶端請求&#xff0c;而動態生成 HTML、XML 或其他格式文檔的Web網頁的技術標準。 JSPHTMLJava JSP的本質是Servlet 訪問JSP的時候&#x…

【設計模式】原型模式

原型模式&#xff08;Prototype Pattern&#xff09;是用于創建重復的對象&#xff0c;同時又能保證性能。這種類型的設計模式屬于創建型模式&#xff0c;它提供了一種創建對象的最佳方式之一。 這種模式是實現了一個原型接口&#xff0c;該接口用于創建當前對象的克隆。當直接…

javaScript:數組的認識與使用以及相關案例

目錄 一.前言 二.數組 1.認識 2.數組的聲明 1.let arr [1,2,3,4] 2.結合構造函數&#xff0c;創建數組 注意&#xff1a; 3.數組長度的設置和獲取 注意 4.刪除數組元素 5.清空數組 三.獲取數組元素 獲取數組元素的幾種方法 1.使用方括號 [] 訪問元素&#xff1…

Keepalived+Lvs高可用高性能負載配置

環境準備 IP配置VIPnode1192.168.134.170LVSKeepalived192.168.134.100node3192.168.134.172LVSKeepalived192.168.134.100node2192.168.134.171做web服務器使用node4192.168.134.173做web服務器使用 1、準備node1與node3環境&#xff08;安裝LVS與Keepalived&#xff09;>…

基于微服務+Java+Spring Cloud +Vue+UniApp +MySql實現的智慧工地云平臺源碼

基于微服務JavaSpring Cloud VueUniApp MySql開發的智慧工地云平臺源碼 智慧工地概念&#xff1a; 智慧工地就是互聯網建筑工地&#xff0c;是將互聯網的理念和技術引入建筑工地&#xff0c;然后以物聯網、移動互聯網技術為基礎&#xff0c;充分應用BIM、大數據、人工智能、移…

滾動條樣式更改

::-webkit-scrollbar 滾動條整體部分&#xff0c;可以設置寬度啥的 ::-webkit-scrollbar-button 滾動條兩端的按鈕 ::-webkit-scrollbar-track 外層軌道 ::-webkit-scrollbar-track-piece 內層滾動槽 ::-webkit-scrollbar-thumb 滾動的滑塊 ::-webkit-scrollbar…

Android布局【RelativeLayout】

文章目錄 介紹常見屬性根據父容器定位根據兄弟組件定位 通用屬性margin 設置組件與父容器的邊距padding 設置組件內部元素的邊距 項目結構主要代碼 介紹 RelativeLayout是一個相對布局&#xff0c;如果不指定對齊位置&#xff0c;都是默認相對于父容器的左上角的開始布局 常見…

TypeScript教程(二)基礎語法與基礎類型

一、基礎語法 TypeScript由以下幾個部分組成 1.模塊 2.函數 3.變量 4.語句和表達式 5.注釋 示例&#xff1a; Runoob.ts 文件代碼&#xff1a; const hello : string "Hello World!" console.log(hello) 以上代碼首先通過 tsc 命令編譯&#xff1a; tsc …

MQTT寶典

文章目錄 1.介紹2.發布和訂閱3.MQTT 數據包結構4.Demo5.EMQX 1.介紹 什么是MQTT協議 MQTT&#xff08;消息隊列遙測傳輸協議&#xff09;&#xff0c;是一種基于發布/訂閱&#xff08;publish/subscribe&#xff09;模式的“輕量級”通訊協議&#xff0c;該協議構建于TCP/IP協…

php、 go 語言怎么結合構建高性能高并發商城。

一、php、 go 語言怎么結合構建高性能高并發商城。 將PHP和Go語言結合起來構建高性能高并發的商城系統可以通過多種方法實現&#xff0c;以利用兩種語言的優勢。下面是一些可能的方法和策略&#xff1a; 1. **微服務架構&#xff1a;** 使用微服務架構&#xff0c;將系統拆分…

安卓快速開發

1.環境搭建 Android Studio下載網頁&#xff1a;https://developer.android.google.cn/studio/index.html 第一次新建工程需要等待很長時間&#xff0c;新建一個Empty Views Activity 項目&#xff0c;右上角選擇要運行的機器&#xff0c;運行就安裝上去了(打開USB調試)。 2…

【Linux】UDP協議——傳輸層

目錄 傳輸層 再談端口號 端口號范圍劃分 認識知名端口號 兩個問題 netstat與iostat pidof UDP協議 UDP協議格式 UDP協議的特點 面向數據報 UDP的緩沖區 UDP使用注意事項 基于UDP的應用層協議 傳輸層 在學習HTTP等應用層協議時&#xff0c;為了便于理解&#xff…