天天看點

了解unix 系統I/O --csapp讀書筆記IO是什麼?相關函數接口共享檔案與C語言中标準IO的差別

昨天,面百度,其中有一個問題是:open的傳回值是什麼?從你調用open函數到Linux下出現了一個檔案,這中間發生了什麼?

第二個問題,當時就懵逼了,後來仔細想想,發現自己其實看過,其實講檔案描述符相關~為此,自己又重新看了APUE第二章,同時對應看了CSAPP第10章,是以做一個總結~

IO是什麼?

i/o是input,output的縮寫,也就是輸入輸出的意思。在計算機中,I/O就是在主存和外部裝置(如磁盤,終端,網絡)之間拷貝資料的過程。

輸入:從IO裝置拷貝資料到主存 IO->主存

輸出:從主存拷貝資料到IO裝置 主存->IO

在Unix/Linux系統中,我們可以使用由核心提供的系統調用函數實作IO,比如read,write。。。(由于Unix和Linux在很多地方都相似,我們下面統稱Linux)

在Linux中,一個檔案就是一個位元組序列。而Linux有一個設計思想:Linux下一切皆檔案。

是以,我們上述所講的IO裝置(如磁盤,終端,網絡)都可以看作是檔案,是以所有的輸入輸出都可以看作是對檔案的讀和寫,這樣的一層抽象保證了所有的輸入輸出都能以一種統一和一緻的方式來執行。

那麼它是如何實作的呢?概括來說,它是通過一個叫檔案描述符的東西來進行操作的,具體如下:

了解unix 系統I/O --csapp讀書筆記IO是什麼?相關函數接口共享檔案與C語言中标準IO的差別

相關函數接口

一般來說,打開檔案,讀寫檔案,關閉檔案,對檔案操作等,隻需要用到5個函數,open,read,write,lseek,close,我們也可以通過man手冊來進行查閱~

ps:creat函數也可以用于建立檔案,而open是打開一個已有檔案或者建立一個新檔案

ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
int creat(const char *pathname, mode_t mode);

off_t lseek(int fd, off_t offset, int whence);
int close(int fd);
           

函數聲明如上,具體函數是如何使用,CSAPP,APUE都有着詳細的說明,本文不再贅述,我們重點談談上面所講的檔案描述符。

當我們利用open,creat函數建立一個檔案成功,它們會傳回一個整型數字,這就是檔案描述符fd,後面所有的讀寫等操作都必須用到這個fd,我們看上面的函數,除了建立函數等,其他函數第一個參數都是fd也印證了我們所說的這一點。

共享檔案

Linux系統支援在不同程序之間共享打開的檔案,核心維護了3種資料結構表示打開的檔案。

  1. 檔案描述符表
  2. 檔案表
  3. v-node表

下面我們重點來講一講這三者的概念以及彼此的關系,這對我們了解檔案IO,共享有着關鍵性的作用。

檔案描述符表

每個程序都具有它獨立的檔案描述符表,每個描述符占有一項,與每個檔案描述符相關聯的是:

  1. 檔案描述符标志
  2. 指向檔案表的指針

其中,0,1,2分别對應标準輸入,标準輸出,标準錯誤,是以,從标準輸入讀可以使用read(0, buf, sizeof(buf))

了解unix 系統I/O --csapp讀書筆記IO是什麼?相關函數接口共享檔案與C語言中标準IO的差別

檔案表

核心為每個打開檔案都維護一個檔案表,所有程序都共享這張表。它的表項有很多,包括檔案位置,引用計數(即目前有多少個指向該表的描述表項數),以及一個指向v-node中對應表項的指針。

關閉一個描述符(close(fd))會減少對應的檔案表表項的引用計數,直到這個引用計數為0,核心才會删除這個檔案件表項。

注意到:它的表項有很多,對于共享檔案,我們主要關注引用計數,實際上APUE寫的更為詳細:

了解unix 系統I/O --csapp讀書筆記IO是什麼?相關函數接口共享檔案與C語言中标準IO的差別
了解unix 系統I/O --csapp讀書筆記IO是什麼?相關函數接口共享檔案與C語言中标準IO的差別

在此,我們忽略這些。

v-node表

每個打開檔案(裝置也是檔案)都有一個v-node結構,這個v-node結點包含了檔案類型等等。對于大多數檔案,v結點還包含了該檔案的i-node結點(索引結點),這些資訊是打開檔案時候從磁盤上讀入記憶體的。

了解unix 系統I/O --csapp讀書筆記IO是什麼?相關函數接口共享檔案與C語言中标準IO的差別

和檔案表一樣,所有程序共享這張v-node表。

三者關系

了解unix 系統I/O --csapp讀書筆記IO是什麼?相關函數接口共享檔案與C語言中标準IO的差別
了解unix 系統I/O --csapp讀書筆記IO是什麼?相關函數接口共享檔案與C語言中标準IO的差別
了解unix 系統I/O --csapp讀書筆記IO是什麼?相關函數接口共享檔案與C語言中标準IO的差別

CSAPP給出幾道題幫我們了解這些概念:

了解unix 系統I/O --csapp讀書筆記IO是什麼?相關函數接口共享檔案與C語言中标準IO的差別

其中10.2題就對應第二張圖,我們對同一個filename檔案“foobar.txt”打開兩次,就有兩個檔案描述符fd1,fd2,但是因為每個fd都有自己的檔案表表項,是以對于foobar.txt都有它自己檔案位置,是以放到

fd2讀完,最後結果是c = f。

而10.3題則對應第三張圖,子程序會繼承父程序的描述符表,同時所有程序共享同一個打開檔案表,是以父子程序都指向同一個打開檔案表表項:

了解unix 系統I/O --csapp讀書筆記IO是什麼?相關函數接口共享檔案與C語言中标準IO的差別

當子程序讀取一個字元,檔案偏移位置+1,父程序會讀取第二個位元組,是以結果是 c = o

重定向

在這篇了解重定向之dup,dup2部落格裡面我總結了重定向,同時給出如何實作重定向以及恢複重定向。

但是,我當時了解不夠,是以有些地方不太準,是以,我在這兒對于某些不太準備的地方做出解釋:

以dup2(oldfd, newfd)為例,它是将oldfd拷貝到newfd中,如果newfd打開就會先關閉newfd再拷貝fd。實際上呢,就是将newfd所對應的指針指向oldfd的指向的地方。

以dup(4,1)為例:

了解unix 系統I/O --csapp讀書筆記IO是什麼?相關函數接口共享檔案與C語言中标準IO的差別

與C語言中标準IO的差別

ANSI也定義一組進階IO函數,稱為标準IO庫,也可以用來讀寫,比如printf,scanf,fread,fwrite等,那麼它們有什麼差別?該如何選擇?

最主要的差別就是C語言讀取檔案自帶一個緩沖區,而read,write這些是不帶緩沖區的,實際上包括fread,fwrite這些c語言函數必然調用read這些系統調用。

标準C将檔案抽象為流,所謂的流就是指向FILE*類型的指針,和unix類似,它也有三個流對應标準輸入輸出錯誤。

了解unix 系統I/O --csapp讀書筆記IO是什麼?相關函數接口共享檔案與C語言中标準IO的差別

類似為FILE的流是對檔案描述符和流緩沖區的抽象,所謂的緩沖區目的實際上就是盡可能少使用開銷較大的系統調用,比如我們想用getc傳回讀取檔案的下一個字元,我們可以隻調用一次read來填充緩沖區,是以讀取字元隻需要到緩沖區内讀,而非系統調用。

如何選擇

實際上大部分時候,我們會選擇标準IO,因為它自帶緩沖區,不需要頻繁使用系統調用(這是有代價的),進而提高效率,當然在某些時候需要用到fflush函數重新整理緩沖區。

然而對于網絡這樣是不行的,前面我們說過Linux下一切皆檔案,是以對于網絡的IO,我們抽象為一種稱為套接字的檔案類型,和其他檔案一樣,對套接字檔案操作也是基于檔案描述符的,應用程序通過對套接字描述符讀寫與其他計算機上的程序通信。

然而不幸的是,标準IO和網絡檔案有很多不相容性,是以我們更多的會去選擇使用read,write這種低級IO。