????????
目錄
1. 理解“文件”
????????1.1 文件的定義
2. 回顧 C 語言的文件操作
????????2.1 文件操作
2.2 實現cat
2.3? 可以實現打印的幾種方式
?3. 系統文件的IO
?3.2 使用系統的接口
?3.3 內部的實現
?3.4 重定向
4. 文件系統的內核結構
5. 緩沖區
????????5.1 是什么
5.2 為什么
5.3 有什么
5.4 見見緩沖區
? ? 6. 其他知識匯總
?6.1 FILE
?6.2 自己實現lib
?
1. 理解“文件”
? ??1.1 文件的定義
? ? ??文件具有狹義與廣義的定義。狹義的理解:存放在磁盤里面的文件。因為狹義的文件定義是在磁盤上的所以對于一切文件的操作就是對于外設的操作,就是對于外設的輸入與輸出簡稱為IO。
? ? ? ??廣義的文件:在 Linux 下一切皆文件。為什么一切皆文件呢?如何理解?Linux 認為一切皆文件也就是說操作系統將所有對于外設的操作都看成是對于文件的操作,這個的原因可以方便進程對于低下所有的不同外設進行管理(類似于多態的思想,結構體改變指針的指向)。盡管不同的外設的內部的具體方法以及函數不同但是都具有相同類型的函數,通過欺騙進程認為一切皆文件,然后達到用戶層同時認為一切皆文件。這個解釋通過后面看Linux的內核結構會更好理解。
2. 回顧 C 語言的文件操作
? ? ? ?2.1 文件操作
? ? ? ? 通過回顧C的具體的文件的接口操作,可以幫我們更好的理解操作系統的IO操作。下面展示一個文件的打開,關閉,追加,寫入,讀文件操作。
FILE* fd = fopen("myfile.txt", "w");if(!fd){printf("fopen file\n");}//要寫入的需要使用write,先定義一個msg字符產const char* msg = "hello word\n";fwrite(msg, strlen(msg), 1, fd);
//展示讀的操作FILE* fd = fopen("myfile.txt", "r");if(!fd){printf("fopen file\n");}//讀需要使用buffer//printf("hello word\n");const char* msg = "hello word\n";char buffer[1024];// fread 參數含義:數據存放區,按照幾個字節讀取,讀多少,從哪里讀ssize_t s = fread(buffer, 1, 2, fd);if(s > 0){buffer[s] = 0; // 最后讀取的實際個數會存放在s中printf("%s", buffer);}
//追加操作FILE* fd = fopen("myfile.txt", "a");if(!fd){printf("fopen file\n");}const char* msg = "hello word\n";fwrite(msg, strlen(msg), 1, fd);fclose(fd);
2.2 實現cat
? ? ? ? 通過對于上面代碼的簡單修改即可實現 cat 指令的具體內容。
#include <stdio.h>
#include <cstring>
int main(int argc,char* argv[])
{if(argc < 2){printf("you need write right formote:\n ./hello xxxx\n");return -1;}//使用讀的方式進行打開FILE* fd = fopen(argv[1], "r");char buffer[1024];while(1){//對于不確定的就要使用循環實現最后使用fopf進行判斷是否讀到了最后int s = fread(buffer, 1, sizeof(buffer), fd);if(s > 0){buffer[s] = 0;printf("%s", buffer);}if(feof(fd)){//讀到最后不能再讀了就要進行返回break;}}fclose(fd);return 0;
}
2.3? 可以實現打印的幾種方式
? ? ? ? 再屏幕上打印有三個常見的方式 fwrite, printf, fprintf。下面是實現的代碼。
const char* msg = "hello djx\n";fwrite(msg, strlen(msg), 1, stdout);printf("%s", msg);fprintf(stdout, msg);
?3. 系統文件的IO
? ? ?3.1 標志位,文件描述符
? ? ? ? 標志位可以理解為使用位圖表示的一個數,通過宏定義的方式變成易讀的命名。這個標識位可以用來實現系統的打開以及其他的操作。
? ? ? ? ? ? ? ? 文件描述符:專門用來表示打開文件的數組下標,每打開一個文件就會形成一個文件描述符從3開始(因為os會自動占用0,1,2 表示標準輸入,標準輸出,標準錯誤)如果修改文件描述符指向(通過調用 dup2 這個函數)可以實現重定向。
?3.2 使用系統的接口
? ? ? ? 系統的接口主要是有 open, wirte, read 等。一下是詳細的解釋以及代碼。
umask(0);//寫size_t fd = open("myfile", O_CREAT | O_WRONLY | O_TRUNC, 0666);const char* msg = "I am DJX\n";write(fd, msg, strlen(msg));//追加 size_t fd = open("myfile", O_APPEND | O_WRONLY, 0666);const char* msg = "I am DJX\n";write(fd, msg, strlen(msg));close(fd);//讀size_t fd = open("myfile", O_RDONLY , 0666);char buffer[1023];ssize_t s = read(fd, buffer, 123);if(s > 0){buffer[s] = 0;printf("%s", buffer);}close(fd);
? ? ? ? 需要注意的幾點是可以看到,我們主要看的是文件描述符 fd,然后我是以2進制,還是其他的讀法繼續寫入,或者是讀出都不關心,其實我們的語言層就是封裝了這些接口實現的 fwrte 等?。后面會有代碼來進行實現我自己封裝一個stdid。下圖表示的就是r,w等對應的文件的打開方式。
?
?3.3 內部的實現
? ? ? ? 我們說了這么多,其實在Linux的代碼中對于這么多打開的文件是對于進行了先描述,再組織的方式對于打開的文件文件進行了管理。在 task_struct 中具有 file_struct ,在 file_struct 中存放著文件描述表,通過文件描述表就可以找到已經打開了的 file。
3.4 重定向
? ? ? ? 有輸出重定向 > >>,一個是覆蓋式,另外一個為追加。?> 就是使用?O_CREAT | O_WRONLY | O_TRUNC 結合 dup2 將標準輸出定位到另外一個文件當中(本來應該打印到顯示器上,將 fd 新的文件描述符覆蓋到舊的文件描述符的上面, dup2(fd, 1))。
????????也就是說,我本來 printf是要打印到文件描述的 1 上面(也就是打印到顯示器上面)但是我現在改變了指向就讓其指向了 fd 這個文件描述符從而把東西打印到了文件里面,所以打開的文件需要以寫的方式進行打開。
? ? ? ? 輸入重定向:本來是要從 stdin 文件描述符為 0, 這個里面進行讀入數據,但是改變一下,讓另外的一個文件指向變成 0,也就是從這個文件里面讀到內容放到相應的位置,進行對應的操作。使用函數為dup2(fd, 0),打開的文件的方式為讀的方式打開從 stdin 中讀入,但是需要改變文件描述符;
4. 文件系統的內核結構
? ? ? ?在內核當中,可以看到 file 這個接口里面有一個 struct file_operations 的結構體,這個結構體定義了一個結構體指針,在這個結構體里面可以看到具有許多的函數指針,指向了不同設備的相同類型的操作,但是每個函數的具體操作是不相同的。類似于多態。? ? ?
????????
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; // 設置對?件的訪問模式,例如:只讀,只寫等。所 有的標志在頭?件<fcntl.h> 中定義loff_t f_pos; // 表?當前讀寫?件的位置... }struct file_operations {struct module *owner;//指向擁有該模塊的指針;loff_t (*llseek) (struct file *, loff_t, int);//llseek ?法?作改變?件中的當前讀/寫位置, 并且新位置作為(正的)返回值.ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);//?來從設備中獲取數據. 在這個位置的?個空指針導致 read 系統調?以 -EINVAL("Invalid argument") 失敗. ?個?負返回值代表了成功讀取的字節數( 返回值是?個"signed size" 類型, 常常是?標平臺本地的整數類型).ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);//發送數據給設備. 如果 NULL, -EINVAL 返回給調? write 系統調?的程序. 如果?負,返回值代表成功寫的字節數.ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long,loff_t);//初始化?個異步讀 -- 可能在函數返回前不結束的讀操作.ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsignedlong, loff_t);//初始化設備上的?個異步寫.int (*readdir) (struct file *, void *, filldir_t);//對于設備?件這個成員應當為 NULL; 它?來讀取?錄, 并且僅對**?件系統**有?.... }
? ??????
? ? ? ? 這樣實現的目的前面也已經說過了,進程對于設備的管理也就變成了對于文件的管理,所以一切皆文件。更加深層次一點說就是,我們通過 file 這個結構體就可以調用 Linux 下面的所有資源。這便是一切皆文件!!!!
5. 緩沖區
? ? ? ? 5.1 是什么
? ? ? ? ?緩沖區簡單可以理解為:當我們進行打印一個東西的時候相關的數據會存放在一個區域中,當這個區域符合了一定的條件(比如緩沖區滿了放不下了不要刷新,強制刷新,進程退出,滿足了刷新條件)?就會將相關的內容給到os(操作系統),讓os完成相應的操作。
? ? ? ? ?官方的解釋為:內存中的一部分,也就是會預留出這一部分空間來進行數據的輸入與輸出,起到了一個緩沖的作用。緩沖區根據其對應的是輸?設備還是輸出設備,分為輸?緩沖區和輸出緩沖區。本章主要探討以及上面所說的就是輸出緩沖區。
5.2 為什么
? ? ? ? 它存在的意義就在于可以提高計算機的運行效率!
? ? ? ? 在理解他是如何如何提高效率之前,我們需要直到 os 去繼續工作(無論是進行打印,去磁盤中尋找文件)任何操作都會消耗資源,降低 計算機的速度。然后 緩沖區 就可以解決這個樣的問題。舉個例子:1. 我們需要打印一份東西,如何有一條打印語句就進行打印的話,他的效率遠遠不如我們將所有的要打印信息都放到一個內存中,然后對于這個內存進行統一的打印。 2. 向磁盤的文件讀取數據的時候我們一次性讀取大量的數據,然后放回到 os 中,可以極大的提高效率。
? ? ? ? 我們還可以把它理解為一個快遞驛站,將東西從一個地方運送到另外一個地方的時候,統一打包以及集中的繼續郵寄可以極大的提高我們想要郵寄東西需求。操作系統設計緩沖區的目的也在于此。
5.3 有什么
? ? ? ? 根據類型的分類可以分為:行緩沖區(打印到\n 的時候就進行打印,調用系統), 全緩沖區(滿整個緩沖區后才進?I/O系統調?操作。),?緩沖區(標準I/O庫不對字符進?緩存,直接調?系統調?。)
? ? ? ? 下列特殊情況也會引發緩沖區的刷新:1. 緩沖區滿時;2. 執?flush語句 3. 進程結束
?5.4 見見緩沖區
? ? ? ? 下面一段代碼,會有不一樣的結果請看。
#include <stdio.h>
#include <cstring>
#include <unistd.h>
int main()
{const char* msg1 = "hello print\n";const char* msg2 = "hello fwrite\n";const char* msg3 = "hello write\n";printf("%s", msg1);fwrite(msg2, strlen(msg2), 1, stdout);write(1, msg3, strlen(msg3));fork();
}
????????最后跑出來結果為,可以看到 當對于程序的內容進行重定向放到myfile中時,首先使用的是write因為這是一個系統的函數,直接放到了顯示器上面,然后后面的 print 和 fwrite 打印了兩次是因為當我們進行重定向的時候會修改文件的刷新方式,從行緩沖(因為有 \n )變成了 文件緩沖。也就是這個 p 和 fwrite 的打印的內容都在緩沖區當中,然后子進程(對于父進程代碼的一份拷貝)結束了講緩沖區的內容刷新到了操作系統進行打印,然后是父進程結束刷新緩沖區,進行打印。
?????????但是當我們進行普通的使用的時候(也就是運行程序的時候顯示的就還是行緩沖)遇到 \n 就會進行打印。?
? ? ? ?
? ? ? ? 將沒有fork的重定向到文件當中。會先使用系統函數write。
? ? ? ? 這樣的例子就很好的說明了緩沖區對于磁盤文件的使用,以及在進行打印時候的作用。?
? ? 6. 其他知識匯總
? ? ? ? 6.1 FILE
? ? ? ? ? ? ?它是c語言庫中使用文件操作的數據類型,那么它究竟是什么??沒錯他就是一個結構體,里面進行封裝了系統調用函數需要的文件描述符 fd。原碼的結構如下:
{#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}
6.2 自己實現lib
? ? ? ? 里面需要實現 fwrite(使用系統 open ,以寫入,或者是追加的方式進行打開), fopen,fclose, fflush。下章對于這部分代碼進行說明。
#include "mystdio.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>static MyFile *BuyFile(int fd, int flag)
{MyFile *f = (MyFile*)malloc(sizeof(MyFile));if(f == NULL) return NULL;f->bufferlen = 0;f->fileno = fd;f->flag = flag;f->flush_method = LINE_FLUSH;memset(f->outbuffer, 0, sizeof(f->outbuffer));return f;
}MyFile *MyFopen(const char *path, const char *mode)
{int fd = -1;int flag = 0;if(strcmp(mode, "w") == 0){flag = O_CREAT | O_WRONLY | O_TRUNC;fd = open(path, flag, 0666);}else if(strcmp(mode, "a") == 0){flag = O_CREAT | O_WRONLY | O_APPEND;fd = open(path, flag, 0666);}else if(strcmp(mode, "r") == 0){flag = O_RDWR;fd = open(path, flag);}else{//TODO}if(fd < 0) return NULL;return BuyFile(fd, flag);
}
void MyFclose(MyFile *file)
{if(file->fileno < 0) return;MyFFlush(file);close(file->fileno);free(file);
}
int MyFwrite(MyFile *file, void *str, int len)
{// 1. 拷貝memcpy(file->outbuffer+file->bufferlen, str, len);file->bufferlen += len;// 2. 嘗試判斷是否滿足刷新條件!if((file->flush_method & LINE_FLUSH) && file->outbuffer[file->bufferlen-1] == '\n'){MyFFlush(file);}return 0;
}
void MyFFlush(MyFile *file)
{if(file->bufferlen <= 0) return;// 把數據從用戶拷貝到內核文件緩沖區中int n = write(file->fileno, file->outbuffer, file->bufferlen);(void)n;fsync(file->fileno);file->bufferlen = 0;
}
??????????以上是對于IO的回顧。這個文章用于我的學習記錄,如果是有其他的錯誤還請批評指正。如果對你有幫助還請給我點個贊👍👍👍。?????
????????????????
?????????
?