1.關于文件的共識原理
1.文件=內容+屬性
2.文件分為打開的文件和沒打開的文件
3.打開的文件:
文件被打開必須先被加載到內存,所以本質是研究進程和文件的關系,一個進程可以打開多個文件。操作系統內部一定存在大量被打開的文件,要進行管理就一定要先描述再組織,所以一個被打開的文件都必須有自己的文件打開對象,包含文件的很多屬性
4.沒打開的文件:
在磁盤上存放的,我們關心的問題是沒有被打開的文件非常多如何被分門別類的放置好,如何進行高效的存儲和增刪查改
2.回顧C文件接口
w:寫入之前會對文件進行清空處理,從頭開始寫
a:也是寫入,在文件結尾追加寫
C程序默認在啟動時會打開三個標準輸入輸出流,stdin鍵盤文件、stdout顯示器文件和stderr顯示器文件
-
- 文件打開與關閉
fopen:打開文件,返回文件指針(FILE*)
filename:文件名(含路徑)
mode:打開模式(常用:“r” 讀,“w” 寫,“a” 追加,“rb” 二進制讀,“wb” 二進制寫等)
返回值:成功返回文件指針,失敗返回 NULL
fclose:關閉文件
stream:fopen 返回的文件指針
返回值:成功返回 0,失敗返回非 0
-
- 文本文件讀寫
1.fgetc / fputc:讀寫單個字符
2.fgets / fputs:讀寫一行字符串
3.fscanf / fprintf:格式化讀寫
- 3.二進制文件讀寫
fread / fwrite:塊讀寫
ptr:數據緩沖區地址
size:每個元素的大小(字節)
nmemb:元素個數
返回值:成功讀寫的元素個數(nmemb)
- 文件定位
fseek:移動文件指針
offset:偏移量(字節)
whence:基準位置(SEEK_SET 開頭,SEEK_CUR 當前位置SEEK_END 末尾)
ftell:獲取當前文件指針位置
rewind:將文件指針移到開頭
-
- 錯誤處理
ferror:檢查文件操作是否出錯
feof:檢查是否到達文件末尾
3.系統文件IO
- 比特位方式的標志位傳遞方式
1 #include<stdio.h>2 3 #define ONE (1<<0) //14 #define TWO (1<<1) //25 #define THREE (1<<2) //46 #define FOUR (1<<3) //87 8 void show(int flags)9 {10 if(flags&ONE) printf("hellow function1\n");11 if(flags&TWO) printf("hellow function2\n");12 if(flags&THREE) printf("hellow function3\n");13 if(flags&FOUR) printf("hellow function4\n");14 } 15 16 int main()17 {18 printf("--------------------\n");19 show(ONE);// 只傳 ONE → 000120 printf("--------------------\n");21 show(TWO); // 只傳 TWO → 001022 printf("--------------------\n");23 show(ONE|TWO);// 傳 ONE|TWO → 0011(同時開 ONE、TWO)24 printf("--------------------\n");25 show(ONE|TWO|THREE);// 0111(開 ONE、TWO、THREE)26 printf("--------------------\n");27 show(THREE|FOUR);//110028 printf("--------------------\n");29 }
1.宏定義部分:
每個宏對應一個獨立的二進制位,可視為一個 “開關”(0 關,1 開)。
2.標志位的組合:show調用部分
用一個整數傳遞多個布爾狀態,避免了用多個參數或結構體傳遞的繁瑣。
3.標志位檢查:&運算
判斷某個標志位是否被設置(即是否為 1)。只要結果非 0,說明該位是 1。優勢:
1.高效簡潔:
用一個整數代替多個布爾變量,節省參數傳遞成本(尤其適合大量標志位的場景)。組合和檢查都通過位運算完成,執行速度極快(CPU 原生支持)。
2.可擴展性強:
新增標志位只需定義 (1<<n)(如 FIVE (1<<4)),無需修改 show 函數的參數或邏輯。支持任意組合(如 ONE|THREE|FOUR),無需額外代碼。
3.1常用接口介紹
- open
1.頭文件:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
2.函數原型:
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
2.1參數:
pathname: 要打開或創建的目標文件路徑,不加路徑默認為當前路徑
flags: 打開文件時,可以傳入多個參數選項,用下面的一個或者多個常量進行“或”運算,構成flags。
O_RDONLY: 只讀打開
O_WRONLY: 只寫打開
O_RDWR : 讀,寫打開
這三個常量,必須指定一個且只能指定一個
O_CREAT : 若文件不存在,則創建它。需要使用mode選項,來指明新文件的訪問權限
O_APPEND: 追加寫
mode:
設置文件權限(八進制)
3.返回值:
成功:新打開的文件描述符
失敗:-1
注意:open 函數具體使用哪個,和具體應用場景相關,如目標文件不存在,需要open創建,則第三個參數表示創建文件的默認權限,否則,使用兩個參數的open。
- read與write
定義在 <unistd.h> 頭文件中
參數說明:
fd:文件描述符(非負整數),標識要讀取的文件 / 設備(如通過 open 打開的文件、管道、套接字等)。
buf:指向用戶空間的內存緩沖區,用于存儲讀取到的數據(需預先分配足夠空間)。
count:期望讀取的字節數(size_t 類型,無符號整數)。一般為sizeof()-1
返回值:
正數:實際讀取到的字節數(可能小于 count,如文件剩余數據不足、非阻塞模式下數據未就緒等)。
0:到達文件末尾(無更多數據可讀取)。
-1:讀取失敗,同時設置 errno 標識錯誤原因(如 EBADF 表示 fd 無效,EIO 表示 I/O 錯誤)。
關鍵特性與注意事項:
1.無緩沖 I/O
read 和 write 是系統調用,直接與內核交互,不經過 C 標準庫的緩沖區(與 fread/fwrite 不同)。每次調用都會陷入內核,性能開銷相對較高。
2.文件指針自動移動
成功讀寫后,文件指針(維護在內核中)會自動向后移動,移動的字節數等于實際讀寫的字節數。例如:寫入 10 字節后,指針指向第 10 字節位置,下次讀取會從該位置開始。
3.對不同文件類型的行為差異
普通文件:讀寫按字節流順序進行,可通過 lseek 調整指針位置實現隨機訪問。
管道 / 套接字:屬于 “流式設備”,數據讀寫后會被消耗,無法通過 lseek 調整指針。
終端設備:通常為行緩沖模式,read 可能等待用戶輸入換行符后才返回。
4.錯誤處理
必須檢查返回值,尤其是 -1 的情況,通過 perror 或 strerror(errno) 打印具體錯誤信息。
- 測試代碼
1 #include<stdio.h>2 #include<unistd.h>3 #include<sys/types.h>4 #include<sys/stat.h>5 #include<fcntl.h>6 #include<string.h>7 8 int main()9 {10 umask(0);11 int fd=open("log.txt",O_RDWR|O_CREAT,0644);12 if(fd<0){13 perror("open");14 return 1; 15 }16 const char*msg="hellow linux\n";17 write(fd,msg,strlen(msg));18 19 //將文件指針移回文件開頭20 lseek(fd,0,SEEK_SET);21 char buf[1024];22 while(1)23 {24 ssize_t s=read(fd,buf,strlen(msg)); 25 // 注意:read 不會自動添加 '\0',需要手動確保字符串結束26 if(s>0) buf[s]='\0',printf("%s",buf);27 else break;28 }29 30 close(fd);31 return 0;32 }
得出結論:
幾乎所有的庫只要是訪問硬件設備,必定要封裝系統調用,類似printf/scanf/fread/fwrite等語言層面的接口內部必定封裝了系統調用接口
FILE類型是C庫自己封裝的結構體,里面一定封裝了文件描述符
4.訪問文件的本質
4.1文件描述符fd
文件描述符就是從0開始的小整數。當我們打開文件時,操作系統在內存中要創建相應的數據結構來描述目標文件。于是就有了?le結構體(文件描述符表)。表示一個已經打開的文件對象。而進程執行open系統調用,所以必須讓進程和文件關聯起來。每個進程都有一個指針*?les, 指向一張表?les_struct,該表最重要的部分就是包涵一個指針數組,每個元素都是一個指向打開文件的指針!所以,本質上,文件描述符就是該數組的下標。所以,只要拿著文件描述符,就可以找到對應的文件
Linux進程默認情況下會有3個缺省打開的文件描述符,分別是標準輸入0,標準輸出1,標準錯誤2.0,1,2對應的物理設備一般是:鍵盤,顯示器,顯示器。可通過代碼驗證,輸入輸出還可采取以下方式
1 #include<stdio.h>2 #include<unistd.h>3 #include<sys/types.h>4 #include<sys/stat.h>5 #include<fcntl.h>6 #include<string.h>7 8 int main()9 {10 char buf[1024];//接收鍵盤的輸入11 ssize_t s=read(0,buf,sizeof(buf));12 if(s>0)13 {14 buf[s]=0;15 write(1,buf,strlen(buf));16 write(2,buf,strlen(buf));17 }18 return 0;19 }
1.其中buf[s]=0;手動為read輸入的字符串添加\0結束標志,避免strlen的使用錯誤(找到\0停止計算長度),read讀取到什么輸入什么不會自動添加\0.
2.再通過write輸出流和錯誤流分別向顯示器中輸出,二者是不同的,可通過以下代碼證明
1.該段代碼關閉了標準輸出流,導致三個printf語句都失敗,因為printf默認輸出到stdout
2.fileno函數是C標準庫提供的獲取流對應的文件描述符的標準寫法
3.將 printf 函數的返回值存儲到變量 n 中。printf 的返回值是成功打印的字符總數(包括數字、字母、標點、換行符等),最終輸出12是因為printf認為自己打印成功,但標準輸出流關閉所以沒有顯示其printf語句
4.fprintf(stderr, …) 向標準錯誤流輸出,其文件描述符 2 未被關閉,因此能正常打印。
5.一個被打開的文件信息中要維護引用計數,可能多個fd指向一個文件如1、2都指向顯示器,當引用計數變為0時說明沒人用了就將文件中的數據刷新到磁盤中去然后關閉
通過對比,發現雖然標準輸出流和錯誤流都是向顯示器輸出,但互相獨立
結論:
1.已知C程序在啟動時,會打開三個標準輸入輸出流文件,但并不是C語言本身的特性,而是操作系統的特性,因為進程會默認打開鍵盤、顯示器、顯示器,而所有語言都是運行在操作系統之上的,所以為了符合操作系統特性并在其上運行,各個語言也必須支持從而程序啟動時打開三個標準輸入輸出流文件
2.新建的文件其文件描述符總是從3開始
4.2重定向本質
文件描述符分配規則:
從下標0開始尋找最小的沒有被使用的數組位置,它的下標就是新文件的文件描述符。一般是close掉哪個文件描述符就給新建文件分配哪個,若沒關閉正常創建就按順序分配fd。
- dup2系統調用接口實現重定向
dup2 是用于復制文件描述符的系統調用,定義在 <unistd.h> 頭文件中,主要功能是將一個文件描述符 “重定向” 到另一個文件描述符,常用于實現輸入輸出重定向、管道等功能。
參數說明:
oldfd:已存在的文件描述符(源文件描述符),必須是有效的(未關閉的),否則調用失敗。
newfd:目標文件描述符,即要被復制 / 重定向到的文件描述符。
核心功能:
讓 newfd 指向 oldfd 所對應的文件 / 設備,在files_struct中改變數組下標fd所對應的指針指向的內容,注意這個new和old并不是fd生成的先后關系
1.如果 newfd 已經打開,會先自動關閉 newfd(相當于先執close(newfd))。
2.將 newfd 復制為 oldfd 的副本,即 newfd 和 oldfd 指向同一個文件 / 設備,共享文件狀態(如文件指針、權限等)。
3.成功后,操作 newfd 和操作 oldfd 會對同一個文件 / 設備生效。
返回值:
成功:返回 newfd(即目標文件描述符)。
失敗:返回 -1,并設置 errno 標識錯誤(如 oldfd 無效時為 EBADF,newfd 超出合法范圍時為 EINVAL)。
注意事項:
newfd 與 oldfd 相等時:dup2 不做任何操作,直接返回 newfd(因為無需復制)。
原子操作(不可被中斷的操作):dup2 關閉 newfd 和復制的過程是原子的,避免了手close(newfd) 后、dup(newfd) 前被其他操作占用 newfd 的風險。
文件狀態共享:newfd 和 oldfd 共享文件指針和狀態,例如通過 oldfd 寫入數據后,newfd 的文件指針也會同步移動。
Shell 中的輸入重定向(<)、輸出重定向(>)和追加重定向(>>)等操作,其底層實現本質上都依賴 dup2 系統調用
1.輸出重定向測試:
mytest.c ? ?? buffers 4 #include<sys/stat.h>5 #include<fcntl.h>6 #include<string.h>7 8 int main()9 {10 int fd=open("file.txt",O_CREAT|O_WRONLY,0666);11 if(fd<0){12 perror("open");13 return 1;14 } 15 dup2(fd,1);16 printf("fd:%d\n",fd);17 printf("hello linux\n");18 close(fd);19 return 0;20 }
最終輸出結果并沒有往顯示器上打印,因為在維護文件信息的結構以下標fd為1的指針指向了新創建文件的fd而不是顯示器,實現了輸出重定向
2.追加重定向測試:
將輸出重定向的代碼做出以下更改
int fd = open(filename, O_CREAT|O_WRONLY|O_APPEND, 0666);添加一個O_APPEND追加選項,并且多執行兩次打印語句,結果如下完成追加重定向
3.輸入重定向測試
#include<stdio.h>2 #include<unistd.h>3 #include<sys/types.h>4 #include<sys/stat.h>5 #include<fcntl.h>6 #include<string.h>7 8 int main()9 { // 打開文件(只讀模式,若文件不存在則open失敗)11 int fd=open("file.txt",O_RDONLY);12 if(fd<0){13 perror("open");14 return 1; 15 } // 打開文件(只讀模式,若文件不存在則open失敗)16 dup2(fd,0);17 printf("fd:%d\n",fd);18 char buf[1024]; // 初始化緩沖區為空,避免初始值干擾19 ssize_t n=read(0,buf,sizeof(buf)-1);20 if(n<0){21 perror("read"); 22 return 1;23 }24 buf[n]='\0';25 printf("echo# %s\n",buf);29 close(fd);30 return 0;31 }
打印結果并不是鍵盤輸入得來,而是之前輸出和追加重定向到文件中的內容得來
注意:
文件重定向的本質是修改進程的文件描述符表(內核為進程維護的打開文件映射關系),而進程替換(如exec系列函數)的作用是替換進程的代碼段、數據段、堆棧等用戶空間內容,但會保留進程的內核上下文(包括 PID、文件描述符表、信號掩碼等)。所以重定向關系與進程替換無關
4.3區分標準輸出與錯誤輸出流
雖然標準輸出流和錯誤輸出流對應的都是顯示器文件;
核心區別:stdout 是行緩沖 / 全緩沖,stderr 是無緩沖,導致輸出時機、重定向到文件時的行為不同。
為什么要區分:可根據需求選擇用 stdout(正常輸出)還是 stderr(錯誤輸出),并且利用重定向(1>/2>)分離日志,方便調試和維護。
測試代碼:
還可以這樣直接用fd重定向,標準輸出重定向(1>),標準錯誤重定向(2>)該指令運行 mytest 程序后,會把程序正常的輸出信息保存到 normal.log 文件中,把錯誤信息保存到 err.log 文件中。這樣做的好處是:
1.便于查看和分析 2.保持終端整潔
還可以將二者合并輸出
1.>all.log( 1>all.log 的簡化寫法):將程序的標準輸出(stdout,文件描述符為 1) 重定向到 all.log 文件。若文件不存在則創建,若已存在則覆蓋原有內容(想追加可改用 >>all.log )。
2.2>&1:將標準錯誤輸出(stderr,文件描述符為 2) 重定向到 標準輸出當前指向的目標 ,也就是這里的 all.log 文件。
2>&1 的作用是讓 stderr “跟隨” stdout 的去向
4.4自定義shell添加重定向功能
//宏定義增加,代表不同的fd#define NONE -1#define IN_RDIR 0#define OUT_RDIR 1 #define APPEND_RDIR 2//全局變量增加,實現跨函數的數據共享和狀態維持
char*rdirfilename=NULL;
int rdir =NONE;//增加檢查重定向的函數,實現指令的分離和確定重定向
void check_redir(char*cmd){char*pos=cmd;while(*pos){if(*pos=='>'){ if(*(pos+1)=='>'){//將重定向符號置0,分割指令*pos++='\0';*pos++='\0';while(isspace(*pos)) pos++;rdirfilename=pos;rdir=APPEND_RDIR;break;}else{*pos++='\0';while(isspace(*pos)) pos++;rdirfilename=pos;rdir=OUT_RDIR;break; }}else if(*pos=='<'){*pos++='\0';while(isspace(*pos)) pos++;rdirfilename=pos;rdir=IN_RDIR;break;}else{// do nothing;}pos++;}}//交互函數增加重定向判斷函數的調用
void interact(char *cline,int size){getpwd();printf(LEFT"%s@%s %s"RIGHT""LABLE"",getusername(),gethostname1(),pwd) ; char *s =fgets(cline,size,stdin);assert(s);(void)s;cline[strlen(cline)-1]='\0';check_redir(cline);}
其余代碼見<<進程控制>>文章中部分
4.5理解Linux下一切皆文件
“文件” 的廣義定義:資源的抽象
在 Linux 中,“文件” 被定義為 “可以被讀取或寫入的字節流”,它不局限于存儲在磁盤上的文本 / 二進制數據,而是對所有 “可操作資源” 的抽象。無論是硬件設備(如鍵盤、硬盤)、進程間通信的管道,還是網絡連接,都被視為一種 “文件”,并通過統一的文件系統接口進行操作。
“一切皆文件” 的核心:統一接口
Linux 通過文件描述符和一套標準系統調用來統一操作所有 “文件”,無論其實際類型是什么:
打開文件:open()(適用于所有文件類型,返回文件描述符)。
讀取數據:read()(從文件描述符讀取字節流)。
寫入數據:write()(向文件描述符寫入字節流)。
關閉文件:close()(釋放文件描述符)。
開發者無需關心是磁盤文件還是外設文件,只需調用相同的接口
這種設計的優點:
簡化系統復雜性:
用一套邏輯管理所有資源,避免為不同設備 / 功能設計獨立接口。
統一權限控制:
通過文件權限(讀r、寫w、執行x)統一管理對資源的訪問(例如限制普通用戶操作/dev/sda硬盤)。
靈活擴展:
新增硬件或功能時,只需實現文件接口(如驅動程序),即可被系統接納。
腳本與工具兼容性:
現有命令(如cat、grep、cp)可直接操作各種 “文件”,例如cat /proc/cpuinfo查看 CPU 信息,無需專門工具。
總結:
“一切皆文件” 是 Linux 對 “資源管理” 的高度抽象:它將硬件、進程、網絡等所有可操作的實體,都封裝成 “字節流” 形式的 “文件”,并通過統一的接口(用戶層接口封裝底層系統調用接口)和文件系統結構進行管理。這種設計不僅簡化了系統實現,也讓開發者能以一致的方式與各種資源交互,是 Linux 靈活性和擴展性的重要基礎。體現了繼承、封裝、多態的思想
5.緩沖區
原理:
一共有兩種緩沖區,分為用戶級和內核級,用戶級就是語言層面,每個語言都會提供一個自己的緩沖區。以C語言為例,C語言接口函數的打印內容都在自己所提供的緩沖區中,用戶刷新的本質,就是將數據通過1(fd)+write寫入到內核緩沖區中,在合適的時機操作系統會自動將內核緩沖區中數據刷新到磁盤或其他外設中去
現象解釋1:
close(1)時,由于C語言接口函數寫入到用戶級緩沖區中的數據還沒來得及調用write刷新到內核級緩沖區中,所以close時直接將內核級緩沖區中數據刷新到磁盤或其他外設中去。此時打印結果就只有write直接往內核級緩沖區中寫入的內容
- 補充內容:
1.緩沖區的刷新問題
a:無緩沖—直接刷新
b:行緩沖—不刷新,直到碰到\n(典型:顯示器)
c:全緩沖—緩沖區滿了,才刷新(文件寫入)
注意:進程退出時,也會刷新
2.為什么要有緩沖區?
a:解決效率問題(用戶)
每刷新一次用戶級緩沖區都調用一次write,系統調用接口都要和操作系統交互一次,所以并不是每次都調用最好,頻繁的交互也會影響效率,所以才有不同的緩沖方式根據需求選擇;
類比生活中的快遞站,不是有人寄件就立馬發出,這樣成本太大,而是等發貨量累計到一定程度再統一發送這樣效率及成本最優
b:配合格式化
例如,在進行寫入時,需要一個整數,但從鍵盤中輸入的都是一個個字符,需要在緩沖區中將它們組成字符串的相應類型
現象解釋2:
1.正常打印時就是逐語句打印,重定向時C語言接口都各被打印了兩次,系統調用接口語句打印了一次,原因是,本來朝顯示器打印緩沖方案是行緩沖模式,遇到\n就刷新,所以fork創建子進程前用戶級緩沖區內的數據都被刷新到了內核級緩沖區去。
2.重定向后朝文件打印,緩沖模式變成全緩沖模式,遇到\n不再刷新,緩沖區寫滿才刷新,所以fork創建子進程后用戶級緩沖區中內容被父子進程所共享,當進程要結束時會刷新用戶級緩沖區,相當于共享的數據被修改,此時會發生寫時拷貝,父子進程各持有一份用戶級緩沖區中數據,通過write系統調用刷新到內核中,再由系統調度刷新到文件中,此過程父子進程都會執行,所以C語言接口函數的打印語句會打印兩次,而系統調用write打印的語句只有一次,證明其直接寫入內核中沒有經歷寫時拷貝父子各執行一次
- 加深理解FILE結構體
文件操作離不開FILE結構體,里面一定包含fd文件描述符信息,還有對應打開文件的緩沖區字段和維護信息
每個文件都有自己的fd和緩沖區
5.1模擬實現C文件標準庫
通過模擬實現,體會用戶層(語言層)封裝系統調用接口
- 頭文件Mystdio.h
//#pragma once
#ifndef __MYSTDIO_H__
#define __MYSTDIO_H__#include <string.h>#define SIZE 1024//通常作為_FILE結構體中flag成員的標志位使用
#define FLUSH_NOW 1//立即刷新,無緩沖
#define FLUSH_LINE 2//行緩沖
#define FLUSH_ALL 4//全緩沖typedef struct IO_FILE{int fileno;int flag; //char inbuffer[SIZE];//int in_pos;char outbuffer[SIZE]; // 用戶級輸出緩沖區數組,用于臨時存儲待輸出的數據int out_pos;// 用戶級輸出緩沖區數組,用于臨時存儲待輸出的數據
}_FILE;_FILE * _fopen(const char*filename, const char *flag);
int _fwrite(_FILE *fp, const char *s, int len);
void _fclose(_FILE *fp);
解析:
1.
#ifndef 是 “if not defined” 的縮寫,意思是 “如果后面的標識符未被定義,則執行后續代碼”。如果__MYSTDIO_H__未被定義(即頭文件首次被包含),則執行此句,定義__MYSTDIO_H__標識符。這樣,當該頭文件再次被包含時,#ifndef判斷會失敗,從而跳過后續內容。
(MYSTDIO_H 是一個自定義的標識符(通常以頭文件名大寫加下劃線組成,如頭文件名為mystdio.h,則標識符常用__MYSTDIO_H__),用于標記該頭文件是否已被包含過。)
作用:
避免因重復定義(如結構體、函數聲明重復)導致的編譯錯誤。
與#pragma once作用相同
2.
_FILE結構體:自定義文件緩沖管理結構,核心目的是實現用戶級緩沖,減少系統調用次數,封裝了文件描述符、緩沖區、刷新標志等核心信息,模仿了標準庫FILE的設計思想。
- 源文件Mystdio.c
#include "Mystdio.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>#define FILE_MODE 0666// "w", "a", "r"
_FILE * _fopen(const char*filename, const char *flag)
{assert(filename);assert(flag);int f = 0;int fd = -1;if(strcmp(flag, "w") == 0) {f = (O_CREAT|O_WRONLY|O_TRUNC);fd = open(filename, f, FILE_MODE);}else if(strcmp(flag, "a") == 0) {f = (O_CREAT|O_WRONLY|O_APPEND);fd = open(filename, f, FILE_MODE);}else if(strcmp(flag, "r") == 0) {f = O_RDONLY;fd = open(filename, f);}else return NULL;if(fd == -1) return NULL;_FILE *fp = (_FILE*)malloc(sizeof(_FILE));if(fp == NULL) return NULL;fp->fileno = fd;//fp->flag = FLUSH_LINE;fp->flag = FLUSH_ALL;fp->out_pos = 0;return fp;
}int _fwrite(_FILE *fp, const char *s, int len)
{// "abcd\n"memcpy(&fp->outbuffer[fp->out_pos], s, len); // 沒有做異常處理, 也不考慮局部問題fp->out_pos += len;if(fp->flag&FLUSH_NOW){write(fp->fileno, fp->outbuffer, fp->out_pos);fp->out_pos = 0;}else if(fp->flag&FLUSH_LINE){if(fp->outbuffer[fp->out_pos-1] == '\n'){ // 不考慮其他情況write(fp->fileno, fp->outbuffer, fp->out_pos);fp->out_pos = 0;}}else if(fp->flag & FLUSH_ALL){if(fp->out_pos == SIZE){write(fp->fileno, fp->outbuffer, fp->out_pos);fp->out_pos = 0;}}return len;
}void _fflush(_FILE *fp)
{if(fp->out_pos > 0){write(fp->fileno, fp->outbuffer, fp->out_pos);fp->out_pos = 0;}
}void _fclose(_FILE *fp)
{if(fp == NULL) return;_fflush(fp);close(fp->fileno);free(fp);
}
這段代碼實現了一個簡化版的 C 語言文件操作庫,包含了_fopen、_fwrite、_fflush和_fclose四個核心函數,模仿了標準庫中stdio.h的基本功能,重點體現了用戶級緩沖區的設計與工作機制。
1.
_fopen:根據文件名和打開模式(“r”/“w”/“a”)打開文件,創建并初始化_FILE結構體(用戶級文件管理結構)。
2.
_fwrite:將數據寫入用戶級緩沖區,并根據_FILE的flag判斷是否需要刷新到內核。
3.
_fflush:將用戶級緩沖區中未寫入內核的數據強制刷新(無論是否滿足刷新條件)。
4.
_fclose:關閉文件,釋放緩沖區和結構體資源,確保數據不丟失。調用_fflush強制刷新緩沖區,確保剩余數據寫入內核。調用close系統調用關閉文件描述符(釋放內核資源)。用free釋放_FILE結構體(釋放用戶態內存)。
- 測試main.c
#include "Mystdio.h"
#include <unistd.h>#define myfile "test.txt"int main()
{_FILE *fp = _fopen(myfile, "a");if(fp == NULL) return 1;const char *msg = "hello world\n";int cnt = 10;while(cnt){_fwrite(fp, msg, strlen(msg));// fflush(fp);sleep(1);cnt--;}_fclose(fp);return 0;
}