文章目錄
- 預備知識
- C語言的文件接口
- 系統調用
- 文件fd
正文開始前給大家推薦個網站,前些天發現了一個巨牛的 人工智能學習網站, 通俗易懂,風趣幽默,忍不住分享一下給大家。 點擊跳轉到網站。
預備知識
我們平時說文件就是說文件里面有什么,那么空文件有大小嗎?答案肯定是有的,我們平時所說的文件除了文件的內容以外還存在文件的創建時間,大小等等,這是文件的屬性,所以文件 = 內容 + 屬性的。所以我們對文件的操作無非就是對文件內容或者對文件屬性的操作。不管是內容還是屬性,它們本質都是數據,所以他們被存儲在磁盤中,就是磁盤文件。所以我們要訪問一個文件的時候都是要把這個文件打開的,該文件被打開前就是磁盤文件,被打開后,因為CPU只和內存打交道,所以被打開的文件一定會被加載到內存。所以加載磁盤文件,一定會訪問外設,這部分工作是由OS來做的。
一個進程是可以打開很多文件的,所以進程和文件的比例關系一定是 1 :n 。而在我們的系統中是有很多進程的,所以被打開的文件也可能會非常的多,所以OS要對這些文件進行管理,管理的本質就是先描述在組織,因此在OS中存在struct file結構體來描述文件,所以以后打開一個文件就創建一個file結構體,把他們用鏈表鏈起來,對文件的管理就成了對該鏈表的增刪查改。
C語言的文件接口
C語言打開文件的是fopen
第一個參數就是打開文件的名稱,可以使用絕對路徑也可以使用相對路徑,第二個參數是打開的模式,經常用的就是w以寫的方式打開,但是每次打開都會清空文件,不存在就創建,還有一種常用的是a以追加的方式打開,每次打開不會清空文件,直接在文件結尾進行寫入,依然是不存在就創建。還有r方式,是以只讀的方式打開。經常用的就是這三種模式。
我們會發現w模式和我們指令所講的輸出重定向非常像。
a模式和追加重定向非常相似。
系統調用
我們再來認識一個系統調用open。
open函數是一個系統用調用,它的第一個參數就是文件名,和fopen一樣,但是第二個參數是標志位。標志位有很多但是這里我們只介紹常用的幾種。第三個參數是文件的權限,一般來說只有創建文件的時候需要設置。
關于函數傳入標志為的技巧是Linux中常用的傳參方式,接下來給兄弟們演示一下什么叫做標志位傳參。
#include <stdio.h>#define Print1 1
#define Print2 (1 << 1)
#define Print3 (1 << 2)
#define Print4 (1 << 3)void printflag (int flag)
{if(flag & Print1) printf("i am Print1\n");if(flag & Print2) printf("i am Print2\n");if(flag & Print3) printf("i am Print3\n");if(flag & Print4) printf("i am Print4\n");
}
int main()
{printflag(Print1);printf("============================\n");printflag(Print1 | Print2);printf("============================\n");printflag(Print1 | Print2 | Print3);printf("============================\n");printflag(Print1 | Print2 | Print3 | Print4);printf("============================\n");return 0;
}
所以open的第二個參數第原理和這個基本上是差不多的,它的選項常用的O_WRONLY(只寫)、O_RDONLY(只讀)、O_CREAT(不存在就創建)、O_TRUNC(每次打開時清空文件)、O_APPEND(追加寫,不清空文件)。open的返回值是一個fd(文件描述符),它是用來表示一個文件的。所以C語言中的FILE也一定封裝了這個數字。有了這些選項的基礎,我們可以來模仿實現一下fopen的各個選項的實現。
FILE _fopen(const char * str, char c)
{int flag = 0;int is_read;if(c == 'a'){flag = O_WRONLY | O_APPEND | O_CREAT;}else{if(c == 'w'){flag = O_WRONLY | O_TRUNC | O_CREAT;}else{if(c == 'r'){flag = O_RDONLY;is_read = 1;}else{//TODO}}}int fd = 0;if(is_read){fd = open(str, flag, 0x666);if(fd < 0){perror("open");exit(-1);} }else{fd = open(str, flag);if(fd < 0){perror("open");exit(-1);} }FILE file;// _fileno就是文件描述符file._fileno = fd;return file;
}
所以C語言的所有庫函數的本質都是封裝了系統調用。
文件fd
到這里我們可以來嘗試理解一下文件了。如何在系統層面上理解一下文件呢?
我們知道每個進程在被創建是都是會有自己的PCB的,在Linux中也就是task_struct,所以每個進程的PCB中都有一個struct files_struct* files 的指針,這個指針指向的結構體中有一個非常重要的一張表,struct file* fd_array[],這是一個指針數組,我們打開的每一個文件都會被在這個指針數組中被指向,一般來說是從小到大來排列的,而數組的下標就是我們上面系統調用返回的文件描述符。所以文件描述符的本質就是數組的下標。操作系統訪問文件只認識文件描述符。
我們進程在運行的時候,是會默認打開三個流,標準輸入流、標準輸出流、標準錯誤流。這三個流對應的硬件分別是鍵盤、顯示器、顯示器。因為Linux下一切皆文件,所以這三個流在進程被打開時會一次把文件描述符表的0、1、2位置給占了,所以我們自己打開的文件的fd一般是從3開始從小到大排的。
OS默認打開三個流,就是為了我們程序員默認進行輸入輸出的代碼的編寫。
我們如何理解一切接文件?
在file文件中是有函數指針的,所以對于不同的文件我們讓它的文件指針指向對應的方法,如果沒有改方法的話我們讓這個指針指向空就行了,所以在上層看來,文件就是這個方法,但是它是可能對于不同的文件指向的方法也是不同的。