1. 前言 文件 = 內(nèi)容 + 屬性 訪問文件之前必須先打開它,為什么要先打開呢? 訪問一個文件的時候,是 進程 在訪問它當(dāng)文件沒有被打開的時候,是保存在 磁盤 中
為啥訪問一個文件是進程在訪問呢?來看一段代碼
代碼語言:JavaScript代碼運行次數(shù):0運行復(fù)制
#include <stdio.h>int main(){ FILE *fp = fopen("log.txt", "w"); if(fp == NULL) { perror("fopen"); return 1; } const char *message = "hello filen"; int i = 0; while(i < 5) { fputs(message, fp); i++; } fclose(fp); return 0;}
結(jié)果如下:

我們可以發(fā)現(xiàn):
程序結(jié)束之后,會在當(dāng)前目錄下新建 log,txt 文件查看文件時內(nèi)容已被寫入這個文件在磁盤中已經(jīng)被保存好了
? 那么我們現(xiàn)在有個問題,我們編好了代碼,這個文件是不是就打開了 — 沒有,因為我們把代碼寫好之后,這個還只是一個文本,那是不是把代碼編譯成可執(zhí)行程序,文件就打開了 — 答案也是沒有的,把原代碼編譯成可執(zhí)行程序僅僅是跑起來了。
? 那么什么時候文件才真正被打開呢?
當(dāng)我們的程序運行的時候,執(zhí)行到 fopen 函數(shù)時并且成功之后,文件才會打開。此時就知道 foepn 就和 malloc 、new 類似, 屬于運行時操作,當(dāng)程序執(zhí)行完 fopen ,這個文件才會打開。因此訪問一個文件,不是程序在訪問,而是進程在訪問。
進程 是在 內(nèi)存 當(dāng)中的,進程加載到內(nèi)存中,最終是由 CPU 去執(zhí)行,可是進程要進行文件讀取操作時,這個文件是在磁盤上的,它們又是咋聯(lián)系上的呢?
根據(jù) 馮諾依曼 體系,一個文件有內(nèi)容和屬性,將來也要被 CPU 所讀取,可是進程在內(nèi)存里,文件在磁盤上的,而CPU 無法直接訪問磁盤,就需要先去打開該文件,將文件也加載到內(nèi)存中,否則進程訪問不到,因為 CPU 也訪問不到文件 = 內(nèi)容 + 屬性,因此我們加載到內(nèi)存的就是 內(nèi)容 和 屬性,我們剛剛講的都是一個進程可以打開一個文件,此外一個進程也可以打開多個文件。 由于文件需要加載到內(nèi)存當(dāng)中,同時我們的文件數(shù)目比進程數(shù)更多,進程都需要 OS 管理,那么 OS 對于加載到 內(nèi)存的文件也需要做管理
結(jié)論:訪問一個文件之前必須先打開它,根據(jù)馮諾依曼,無法訪問磁盤上的文件,必須加載到內(nèi)存上
如何管理文件?
先描述再組織內(nèi)核中,文件 = 文件的內(nèi)核數(shù)據(jù)結(jié)構(gòu) + 文件的內(nèi)容
結(jié)論:我們研究打開的文件,就是在研究 進程 和 文件 的關(guān)系
2. 輸出重定向
我們上面 fopen 中的 ‘w’ 是 覆蓋式寫入,會將文件清空之后再寫入。這個就類似于 我們之前學(xué)的

這個 > 就叫作 輸出重定向,寫入前把文件先清空。
案例:給上面代碼加個 字符數(shù)組
代碼語言:javascript代碼運行次數(shù):0運行復(fù)制
int main(){ FILE *fp = fopen("log.txt", "w"); if(fp == NULL) { perror("fopen"); return 1; } char buffer[1024]; const char *message = "hello file"; int i = 0; while(i < 5) { snprintf(buffer, sizeof(buffer), "%s:%dn", message, i); fputs(buffer, fp); i++; } fclose(fp); return 0;}
輸出如下:

追加寫入 — a

同樣在 echo 命令中 我們也可以用 >> 來追加式寫入

3. 標(biāo)準(zhǔn)輸入輸出流 ?
概念補充:任何一個程序在啟動之前默認(rèn)需要打開三個流
stdin : 標(biāo)準(zhǔn)輸入 — 鍵盤stdout :標(biāo)準(zhǔn)輸出 — 顯示器stderr : 標(biāo)準(zhǔn)錯誤 — 顯示器
但是鍵盤、顯示器不是屬于硬件嘛,怎么跟文件流有關(guān)系,這個和我們之前學(xué)的 linux 下一切皆文件有關(guān)(TODO)

一個程序啟動時會打開三個流,而其中 C 語言底層所對應(yīng)的硬件時鍵盤、顯示器,但是它把這個鍵盤、顯示器包裝成了文件的樣子,最后就可以 File* 的形式來訪問文件了。
那么現(xiàn)在有個問題是誰默認(rèn)打開這三個流的呢?
進程默認(rèn)會打開這三個輸入輸出流,畢竟程序只是可執(zhí)行文件還沒有運行,而且訪問文件必須先把文件打開,就需要調(diào)用 fopen,三個標(biāo)準(zhǔn)輸入輸出流默認(rèn)就是進程打開。 同樣 c++ 也有三個輸入輸出流 — cin、cout、cerr
把打印內(nèi)容到顯示器的 三種方法
代碼語言:javascript代碼運行次數(shù):0運行復(fù)制
#include <stdio.h>int main(){ printf("hello worldn"); fputs("aaaa", stdout); fwrite("bbbb", 1, 4, stdout); fprintf(stdout, "cccc"); return 0;}
4. open 函數(shù) ?4.1 基本概念

上面的flags 表示打開文件的標(biāo)記位,以只讀或只寫等形式打開,mode 表示創(chuàng)建文件權(quán)限 O_RDONLY 以只讀方式打開文件O_WRONLY 以只寫方式打開文件O_RDWR 以可讀寫方式打開文件。 上述三種旗標(biāo)是互斥的,也就是不可同時使用,但可與下列的旗標(biāo)利用OR(|)運算符組合。 O_CREAT 若欲打開的文件不存在則自動建立該文件。注:需要使用mode選項,來指明新文件的訪問權(quán)限O_EXCL 如果O_CREAT 也被設(shè)置,此指令會去檢查文件是否存在。文件若不存在則建立該文件,否則將導(dǎo)致打開文件錯誤。此外,若O_CREAT與O_EXCL同時設(shè)置,并且欲打開的文件為符號連接,則會打開文件失敗。O_TRUNC 若文件存在并且以可寫的方式打開時,此旗標(biāo)會令文件長度清為0,而原來存于該文件的 資料也會消失。O_APPEND 當(dāng)讀寫文件時會從文件尾開始移動,也就是所寫入的數(shù)據(jù)會以附加的方式加入到文件后面。
③ 參數(shù)mode 組合 此為Linux2.2以后特有的旗標(biāo),以避免一些系統(tǒng)安全問題。參數(shù)mode 則有下列數(shù)種組合,只有在建立新文件時才會生效,此外真正建文件時的權(quán)限會受到umask值所影響,因此該文件權(quán)限應(yīng)該為(mode-umaks) ④ 返回值 若所有欲核查的權(quán)限都通過了檢查則返回文件描述符,表示成功,只要有一個權(quán)限被禁止則返回-1。
4.2 mode — 權(quán)限 代碼語言:javascript代碼運行次數(shù):0運行復(fù)制
#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>int main(){ open("log.txt", O_WRONLY|O_CREAT); return 0;}

運行上面代碼,發(fā)現(xiàn)創(chuàng)建了log.txt,但是它的權(quán)限是亂碼的。這是因為我們在最初的時候并沒有給其分配權(quán)限,修改如下:
代碼語言:javascript代碼運行次數(shù):0運行復(fù)制
open("log.txt", O_WRONLY|O_CREAT, 0666);

此時權(quán)限就正常了,但是我們明明指定的權(quán)限明明是 666 ,但是這上面為啥顯示的是 664 呢,因為系統(tǒng)存在 umask(0002)的默認(rèn)權(quán)限掩碼,權(quán)限掩碼會與我們設(shè)置的權(quán)限進行位運算。那么我們應(yīng)該怎么做,才能不讓其去掉這個權(quán)限呢?如下:

此時將代碼中的 umask 設(shè)置為對應(yīng)的 0 后,權(quán)限掩碼就不會給我們?nèi)サ?默認(rèn)的 umask (0002)了,結(jié)果就對上了
注意:權(quán)限掩碼按照就近原則,如果我們有設(shè)置默認(rèn)權(quán)限掩碼,就用我們設(shè)置的,如果沒有,就會使用系統(tǒng)默認(rèn)的。
4.3 close 和 write 函數(shù)

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制
int main(){ int fd1 = open("log.txt", O_WRONLY|O_CREAT, 0666); if(fd1 < 0) { perror("open"); return 1; } printf("fd1: %dn", fd1); const char* message = "hello worldn"; write(fd1, message, strlen(message)); close(fd1); return 0;}
上面是系統(tǒng)調(diào)用接口close和write。fd就是open的返回值。
輸出如下:

我們把message里的內(nèi)容換成aaa,然后直接運行代碼。
代碼語言:javascript代碼運行次數(shù):0運行復(fù)制
const char* message = "aaa";
發(fā)現(xiàn)之前的內(nèi)容還在,舊的內(nèi)容沒被完全清空:

這是因為這里的open默認(rèn)不存在就創(chuàng)建,存在就打開,默認(rèn)不清空文件
如果我們想像c語言fopen的“w”打開方式一樣, 打開就清空文件,就需要再傳 O_TRUNC

表示 如果文件已經(jīng)存在,而且是個常規(guī)文件,并以寫的方式打開,傳入這個選項后,他就會把文件清空。
補充: 我們還可以用 O_APPEND 來對內(nèi)容進行 追加式寫入
5. 系統(tǒng)調(diào)用和庫函數(shù)
還記得我們上面寫的 fd 作返回值嘛,在認(rèn)識返回值之前,先來認(rèn)識一下兩個概念:系統(tǒng)調(diào)用和庫函數(shù)
fopen fclose fread fwrite都是C標(biāo)準(zhǔn)庫當(dāng)中的函數(shù),我們稱之為庫函數(shù)(libc)open close read write lseek 都屬于系統(tǒng)提供的接口,稱之為系統(tǒng)調(diào)用接口

系統(tǒng)調(diào)用接口和庫函數(shù)的關(guān)系,一目了然。 所以,可以認(rèn)為,f#系列的函數(shù),都是對系統(tǒng)調(diào)用的封裝,方便二次開發(fā)。
舉個例子:
代碼語言:javascript代碼運行次數(shù):0運行復(fù)制
int main(){ int a = 12345; write(1, &a, sizeof(a)); return 0;}
經(jīng)過輸出,我們發(fā)現(xiàn)最后輸出結(jié)果不是 12345
原因: 12345 是整數(shù),但是顯示器是個字符設(shè)備只認(rèn)字符
解決如下:
代碼語言:javascript代碼運行次數(shù):0運行復(fù)制
int main(){ int a = 12345; char buffer[1024]; snprintf(buffer, sizeof(buffer), "%d", a); write(1, buffer, strlen(buffer)); return 0;}
? 因此我們可以知道直接把數(shù)字打印到顯示器用系統(tǒng)調(diào)用接口是不行的,必須做相關(guān)的轉(zhuǎn)化變成相關(guān)字符然后依次地達(dá)到顯示器上。
? 那么我們有個問題,我們已經(jīng)有了對應(yīng)的 read 接口向顯示器寫,為啥還需要提供這么多寫入接口呢?
因為很多情況下需要把我們內(nèi)存級別的二進制數(shù)據(jù)轉(zhuǎn)化成字符串風(fēng)格,然后通過 write 打印到顯示器上,這個就叫作 格式化 的過程,然后由于系統(tǒng)調(diào)用,這個需要用戶自己來實現(xiàn),為了方便,就提供了這些接口.
6. 文件描述符 fd 6.1 基本了解

輸出如下:

標(biāo)準(zhǔn)輸入stdin標(biāo)準(zhǔn)輸出stdout標(biāo)準(zhǔn)錯誤stderr

情況一: write 向 1 輸出
可以用write配合文件描述符在顯示器上打印 ---
代碼語言:javascript代碼運行次數(shù):0運行復(fù)制
int main(){ const char *message = "hello writen"; write(1, message, strlen(message)); // 默認(rèn)提供的}// 輸出描述:[lighthouse@VM-8-10-centos File-IO]$ ./filecodehello write
情況二:read 向 0 讀取
代碼語言:javascript代碼運行次數(shù):0運行復(fù)制
int main(){ char buffer[128]; ssize_t s = read(0, buffer, sizeof(buffer)); if(s > 0){ buffer[s - 1] = 0; // 吞掉最后一個換行符 printf("%sn", buffer); } return 0;}// 輸出描述:[lighthouse@VM-8-10-centos File-IO]$ ./filecodeabcdabcd
情況三:把字符串 中文国产成人精品久久亚洲精品AⅤ无码精品| 国产精品久久久天天影视香蕉 | 99久久99这里只有免费费精品| 亚洲AV无码久久寂寞少妇| 久久精品中文騷妇女内射| 18岁日韩内射颜射午夜久久成人| 国产精品gz久久久| 伊人久久精品无码av一区| 国产精品一区二区久久| 久久久精品人妻无码专区不卡| 免费精品久久天干天干| 欧美精品一本久久男人的天堂| 亚洲欧美成人久久综合中文网| 久久久久亚洲精品无码蜜桃| 久久97久久97精品免视看| 伊人久久大香线蕉av不变影院 | 97精品伊人久久大香线蕉| 久久精品人人做人人妻人人玩| 欧美一级久久久久久久大片| 无码伊人66久久大杳蕉网站谷歌 | 中文字幕无码精品亚洲资源网久久| 久久精品国产69国产精品亚洲| 久久久久久久久久久| 国产—久久香蕉国产线看观看| 久久久精品国产sm调教网站| 亚洲人成电影网站久久| 91精品无码久久久久久五月天| 久久人人妻人人爽人人爽| 亚洲欧美一级久久精品| 久久久久婷婷| 欧美伊人久久大香线蕉综合69| 日本精品久久久久中文字幕8| 久久99精品久久只有精品| 色综合久久无码中文字幕| 精品久久久无码21p发布| 久久久亚洲欧洲日产国码是AV| 无码国内精品久久人妻麻豆按摩| 久久国产精品波多野结衣AV| 国产三级精品久久| 精品久久久久久无码国产| 久久久久久一区国产精品|