文章目錄
- 1. 前言
- 1.1 C語言文件知識回顧
- 2. 文件
- 2.1 文件基礎知識
- 3. 被打開的文件
- 3.1 以C語言為主,先回憶一下C文件接口
- 3.2 過渡到系統,認識文件系統調用
- 3.3 訪問文件的本質
- 3.4 重定向&&緩沖區
- 序:在深入了解了進程的內容后,我們對于進程的學習將會告一個段落,現在我將對Linux中的基礎IO部分的內容進行一個梳理,本章節將圍繞文件描述符來走進文件的部分,進而了解對于基礎IO這個宏觀的理解。
1. 前言
1.1 C語言文件知識回顧
文件分為程序文件和數據文件
文件名 == 文件路徑+文件名主干+文件后綴
數據文件:
- 文本文件
- 二進制文件
流:
我們程序的數據需要輸出到各種外部設備,也需要從外部設備獲取數據,不同的外部設備的輸?輸出操作各不相同,為了方便程序員對各種設備進行方便的操作,我們抽象出了流的概念,我們可以把流想象成流淌著字符的河。
C程序針對文件、畫面、鍵盤等的數據輸?輸出操作都是通過流操作的。
?般情況下,我們要想向流里寫數據,或者從流中讀取數據,都是要打開流,然后操作。
標準流:
那為什么我們從鍵盤輸入數據,向屏幕上輸出數據,并沒有打開流呢?
那是因為C語言程序在啟動的時候,默認打開了3個流:
? stdin - 標準輸?流,在?多數的環境中從鍵盤輸?,scanf函數就是從標準輸?流中讀取數據。
? stdout - 標準輸出流,?多數的環境中輸出?顯?器界?,printf函數就是將信息輸出到標準輸出
流中。
? stderr - 標準錯誤流,?多數環境中輸出到顯?器界?。這是默認打開了這三個流,我們使用scanf、printf等函數就可以直接進行輸入輸出操作的。
stdin、stdout、stderr 三個流的類型是: FILE * ,通常稱為文件指針。
C語言中,就是通過 FILE* 的文件指針來維護流的各種操作的。
C語言提供的一些有關文件操作的接口函數:
scanf:從標準輸入流上讀取格式化的數據
fscanf:從指定的輸入流上讀取格式化的數據
sscanf:在字符串中讀取格式化的數據printf:把數據以格式化的形式打印在標準輸出流上
fprintf:把數據以格式化的形式打印在指定的輸出流上
sprintf:把格式化的數據轉化成字符串
2. 文件
2.1 文件基礎知識
共識原理:
1. 文件 == 內容+屬性
2. 文件分為打開的文件和沒打開的文件
3. 打開的文件----->誰打開的?自然是進程。(本質是研究進程和文件的關系)
4. 沒打開的文件----->在哪里放著呢?在磁盤上放著。我們最關心什么問題呢?沒有被打開的文件非常多。文件如何被分門別類的放置好----->我們要快速的進行增刪查改(快速找到文件)
問題一:文件是如何存儲的呢?
1. 文件被打開必須先被加載到內存里
2. 進程:打開的文件=1:n
基于上面兩點,操作系統內部一定存在大量的被打開的文件!
問題二:那么OS要不要管理這些被打開的文件呢?怎么管理呢???
一定是先描述,再組織!!!-----在內核中,一個被打開的文件都必須有自己的文件打開對象,該對象包含了文件的很多屬性struct XXX(文件屬性)
3. 被打開的文件
首先,確認我們的研究對象是被打開的文件
3.1 以C語言為主,先回憶一下C文件接口
問題一:當前路徑是什么?
當前路徑,就是當前進程的當前路徑cwd(current work direction)
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>int main(){printf("pid:%d\n",getpid());int fd=open("loggggg.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);if(fd<0){perror("open");return -1;}close(fd);sleep(1000);return 0;
}
當我們執行完后,發現我們在當前路徑下,創建了loggggg.txt文件。
int main()
{printf("pid:%d\n",getpid());chdir("/home/zby/");int fd=open("loggggg.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);if(fd<0){perror("open");return -1;}close(fd);sleep(1000);return 0;
}
問題二:如果我更改了當前進程的cwd,是不是就可以把文件新建到其他目錄?
事實確實是如此的!!!
當我調用系統接口,改變當前進程的路徑的時候,再去創建文件,發現創建文件的路徑也隨之改變
C程序啟動的時候會默認打開三個標準輸入輸出文件
實際上,默認打開這三個標準輸入輸出流不是c語言的特性,是操作系統的特性,進程會默認打開鍵盤,顯示器,顯示器!!!
3.2 過渡到系統,認識文件系統調用
文件其實是在磁盤上,磁盤是外部設備,訪問磁盤文件其實就是在訪問硬件!
幾乎所有的庫,只要是訪問硬件設備,必定要封裝系統調用!(printf/fprintf/fscanf/fwrite/fread/fgets/gets/…----庫函數系統調用接口)
open( )函數
#define ONE_FLAG 000000001<<0
#define TWO_FLAG 000000001<<1
#define THREE_FLAG 000000001<<2
#define FOUR_FLAG 000000001<<3void flag(int flag)
{if(flag&ONE_FLAG){printf("ONE_FLAG ");}if(flag&TWO_FLAG){printf("TWO_FLAG ");}if(flag&THREE_FLAG){printf("THREE_FLAG ");}if(flag&FOUR_FLAG){printf("FOUR_FLAG ");}printf("\n");
}int main()
{flag(ONE_FLAG);flag(ONE_FLAG|TWO_FLAG);flag(ONE_FLAG|TWO_FLAG|THREE_FLAG);flag(ONE_FLAG|TWO_FLAG|THREE_FLAG|FOUR_FLAG);return 0;
}
上面的代碼演示結果如下:
這里強調一下位圖的使用,open的第二個參數也是如此。
這里只展示了常用的幾個宏。
3.3 訪問文件的本質
文件描述符:
文件描述符0 1 2分別表述標準輸入(stdin)、標準輸出(stdout)、標準錯誤(stderr)。
在OS接口層面,只認fd ,即文件描述符。FILE * fopen( )中的FILE是C語言提供的一個結構體,既然操作系統只認文件描述符,那么該結構體中一定封裝了文件描述符fd。
例如: stdin -> _ fileno(stdin的fd)close:將該文件描述符所對應的數組下標所指向的對象置空,并且引用計數減一。(一個文件是可以被多個用戶使用的,如:stdout和stderr都是指向顯示器文件。)
上面的代碼是用戶層,下面的組織形式是OS層。
當我們訪問一個文件時,系統是通過該進程的task_struct結構體內的file_struct * file的結構體指針指向的文件描述符表,用該下標fd訪問對應文件的PCB結構體,然后對磁盤將內容存放到文件緩沖區內,當我們要訪問時,就將文件緩存區的文件內容拿出來用!!!
其中,read的本質是拷貝文件緩沖區的內容。
fd文件描述符的本質也是文件指針數組的下標
對文件內容,做任何操作,都必須把文件先加載(磁盤------>內存的拷貝)到內核對應的文件緩沖區內!!!
3.4 重定向&&緩沖區
文件描述符的的分配原則:從下標0開始,最小的,沒有被使用的數組位置作為最新的fd文件描述符給用戶
重定向:更改文件描述符的指針指向----->(數組下標不變)
dup2:進行重定向的系統調用
重定向:打開文件的方式+dup2進程歷史打開的文件與進行各種重定向關系都和未來進行程序替換無關!!!
程序替換不影響文件訪問
問題一:問題一:stdout和stderr都是指向顯示屏,有什么區別,為什么有了“1”,還要有“2”???
讓我們看下面一段代碼:
#include<stdio.h>
int main(){fprintf(stdout,"i am normal\n");fprintf(stdout,"i am normal\n");fprintf(stdout,"i am normal\n");fprintf(stdout,"i am normal\n");fprintf(stdout,"i am normal\n");fprintf(stderr,"i am error\n");fprintf(stderr,"i am error\n");fprintf(stderr,"i am error\n");fprintf(stderr,"i am error\n");fprintf(stderr,"i am error\n");return 0;
}
演示結果如下:
此時我們沒看出什么,但當我們將這些內容都輸入到一個文件中就會發生變化
這是為什么呢?讓我們接著看!!!
當運行該段代碼的時候
發現兩個分離了
當我們直接將結果輸入到log.txt文件當中時,默認是將文件描述符1所指向的文件內容傳到log.txt中,所以打印到stderr文件中的內容不會打印到log.txt中去,而為什么stderr和stdout都指向顯示器,是因為,當程序運行后的顯示中可能會有錯誤信息,而當打印的信息很繁雜的時候,就需要將這些錯誤信息統一起來!!!所以,結論:是為了將錯誤信息單獨拉出來。
問題二:如何理解“一切皆文件”?
當我們的使用系統調用接口read時,傳一個文件描述符fd,操作系統就會通過該進程teak_struct內的files指針找到一個文件描述符表,找到對應的文件的PCB結構,調用里面的f_ops指針,指向operationfunc的結果體,然后調用里面的函數指針int(&readp)(),該函數指針指向對應的硬件設備,不同的硬件設備提供不同的read和write接口,但在operationfunc里面都是統一的接口,從而調用對應的read接口!!!
(用統一的文件接口來訪問不同的硬件設備 == 一切皆文件)其中的各種struct file就是虛擬文件系統(VFS),所以不同的硬件對應的讀寫方式肯定是不一樣的,但是它們都有自己的 read 和 write 方法。也就是說,這里的硬件可以統一看作成一種特殊的文件。比如這里設計一種結構:struct file,它包括文件的屬性、文件的操作或方法等,Linux 說一切皆文件,Linux 操作系統就必須要能夠保證這點。在 C 語言中,怎么讓一個結構體既有屬性又有方法呢?函數指針。此時每一個硬件都對應這樣一個結構,硬件一旦數量很多,操作系統就需要對它們進行管理 —— 先描述,再組織。所謂的描述就是 struct file;而組織就是要把每一個硬件對應的結構體關聯起來,并用 file header 指向。所以在操作系統的角度,它所看到的就是一切皆文件,也就是說所有硬件的差異經過描述就變成了同一種東西,只不過當具體訪問某種設備時,使用函數指針執行不同的方法達到了不同的行為。現在就能理解為什么可以把鍵盤、顯示器這些設備當作文件,因為本質不同,設備的讀寫方法是有差異的,但我們可以通過函數指針讓不同的硬件包括普通文件在操作系統看來是同樣的方法、同樣的文件。所以,一切皆文件。
總結:
本篇文章從C語言入手,從C語言的文件接口出發,逐漸過渡到系統對于文件的調用,本文的研究對象是已經被打開的文件,我們用先描述,再組織6個字,創建文件結構體對文件進行管理,然后通過幾個細致的例子和對問題的講解,說出我對重定向和緩沖區的理解!!!