其餘相關內容可參考個人部落格
Linux系統將所有裝置都當作檔案來處理,而Linux用檔案描述符來標識每個檔案物件。
檔案描述符在形式上是一個非負整數。實際上,它是一個索引值,指向內核爲每一個進程所維護的該進程開啓檔案的記錄表。當程式開啓一個現有檔案或者建立一個新檔案時,內核向進程返回一個檔案描述符。
每個進程都有一張檔案描述符的表,進程剛被建立時,標準輸入、標準輸出、標準錯誤輸出裝置檔案被開啓,對應的檔案描述符0、1、2 記錄在表中。在進程中開啓其他檔案時,系統會返迴檔案描述符表中最小可用的檔案描述符,並將此檔案描述符記錄在表中
檔案描述符 | 縮寫 | 描述 |
---|---|---|
0 | STDIN | 標準輸入 |
1 | STDOUT | 標準輸出 |
2 | STDERR | 標準錯誤 |
int dup(int oldfd);
功能:複製舊的檔案描述符,自動分配一個可用的最小的檔案描述符
成功返回新的檔案描述符,失敗返回-1
函數說明:
它們參照相同的開啓檔案描述,因此共用檔案偏移量和檔案狀態標誌。 例如,如果在舊的檔案描述符之上使用lseek
修改了檔案偏移,則新的也將更改。
關於檔案描述符與開啓檔案、檔案的關係在後續文章將會介紹,閱讀後能更容易理解上述說明
int dup2(int oldfd, int newfd);
功能:複製舊的檔案描述符,自動分配一個可用的最小的檔案描述符
成功返回新的檔案描述符,失敗返回-1
函數說明:
dup2
是dup
函數的升級版本,可以指定生成的檔案描述符(必須小於1024),如果這個指定的描述符已經打開了,那麼會原子地關閉和複製。oldfd
是無效的,則newfd
不會被關閉oldfd
是無效的,且newfd
和oldfd
相等,則dup2
函數什麼也不幹 不乾,直接返回newfd
標準檔案描述符0,1,2一旦被改變了就無法使用了,所以在重定向之前需要把他們三個儲存起來:
int new_stdout = dup(1);//在重定向之前儲存起來
dup2(new_stdout,1);//這樣就可以變回來了
寫例子的時候還有個小問題,我們重定向之後,printf
到檔案當中,然後把stdout
變回來,再printf
一句話,這個時候可以看到終端上兩句話都列印出來了,那是因爲重定向輸出到檔案的時候緩衝區是全緩衝的,所以數據還在緩衝區當中,沒有寫到檔案當中呢,爲了避免這類問題,可以選擇使用系統呼叫(無緩衝區)
關於緩衝區的問題可繼續閱讀本文後續章節
無論是fork
還是system
出子進程,如果父進程裡在open
某個檔案後(包括socket fd
)沒有設定FD_CLOEXEC
標誌,就會引起各種不可預料的問題,特別是socket的fd
本身又包括了本機ip,埠號等資訊資源,如果該socket fd
被子進程繼承並佔用,或者未關閉,就會導致新的父進程重新啓動時不能正常使用這些網路埠,嚴重的就是裝置掉線。
開啓檔案後預設未將該標誌位置位,即預設在exec後不關閉檔案描述符,可進行如下設定:
int flags;
flags = fcntl(fd, F_GETFD);//獲得標誌
flags |= FD_CLOEXEC; //開啓標誌位
flags &= ~FD_CLOEXEC; //關閉標誌位
fcntl(fd, F_SETFD, flags);//設定標誌
其實open函數的flag提供了
O_CLOEXEC
標誌位,可直接設定(僅Linux 2.6.23後支援)。
具體情況要具體分析,需要檢視由內核維護的3個數據結構:
檔案描述符表 | 開啓檔案表 | i-node表 | |
---|---|---|---|
記錄內容 | 檔案描述符操作標誌(目前內核僅定義了一個close-on-exec標誌) | 當前檔案偏移量 | 檔案型別 |
對開啓檔案控制代碼的參照 | 開啓檔案時使用的狀態標識(open的flags參數) | 檔案鎖 | |
檔案存取模式(open時設定的O_RDONLY等標誌) | 檔案擁有者的UID,GID | ||
對該檔案i-node物件的參照 | 檔案的時間戳:ctime,mtime,atime | ||
檔案型別(例如:常規檔案、通訊端或FIFO) | 鏈接數,即有多少檔名指向這個inode | ||
存取許可權 | 讀寫執行許可權 | ||
一個指針,指向該檔案所持有的鎖列表 | 檔案數據block的位置 | ||
檔案的各種屬性,包括大小以及各種時間戳 | 檔案的各種屬性,包括大小以及各種時間戳 | ||
與信號驅動相關的設定 |
範例如下圖所示:
dup、dup2
fork
後出現的open
呼叫,同一個進程兩次開啓同一個檔案,也會發生類似情況檢視方式:
sysctl -a | grep -i file-max --color
cat /proc/sys/fs/file-max
sysctl
命令和proc
檔案系統中檢視到的數值是一樣的,這屬於系統級限制,它是限制所有使用者開啓檔案描述符的總和
每個進程的最大檔案描述符限制:
ulimit -n
修改使用者級限制:
ulimit -SHn 10240
以上的修改只對當前對談起作用,是臨時性的,如果需要永久修改,則要修改/etc/security/limits.conf
檔案:
* soft nofile 100001
* hard nofile 100002
soft 指的是當前系統生效的設定值,hard 表明系統中所能設定的最大值
修改系統級限制:
[root@VM-0-4-centos ~]# cat /proc/sys/fs/file-max
350000
[root@VM-0-4-centos ~]# echo 50000 > /proc/sys/fs/file-max
[root@VM-0-4-centos ~]# cat /proc/sys/fs/file-max
50000
[root@VM-0-4-centos ~]# sysctl -a | grep -i file-max --color
fs.file-max = 50000
以上是臨時修改,重新啓動後失效,永久修改如下
把fs.file-max=400000
新增到/etc/sysctl.conf
中,使用sysctl -p
即可
出於速度和效率考慮,系統IO呼叫和標準 C語言庫的IO函數均會對數據進行緩衝,接下來將分類介紹:
read
和write
在操作磁碟檔案的時候不會直接發起磁碟存取,而是在使用者空間緩衝區和內核緩衝區快取記憶體之間複製數據。
write(fd,"abc",3);
上面的語句將3個位元組的數據從使用者空間記憶體傳遞到內核空間的緩衝區中,隨後write
返回,在後續的某個時刻,內核會將其緩衝區中的數據寫入(重新整理至)磁碟,在此期間如果有另一進程存取這幾個位元組,直接從快取記憶體中提供這些數據。對輸入而言同理。
這一設計不需要read
和write
等待磁碟操作,也減少了內核進行磁碟傳輸的次數。例如:讓磁碟寫1000次,每次寫入一個位元組,還是一次寫入1000個位元組,內核存取磁碟的次數都是相同的,因爲有緩衝區的存在,但是我們更趨向於後者,因爲只有一次系統呼叫,所以這部分是程式設計師需要思考的,這部分的緩衝也就是下面 下麪提到的stdio庫的緩衝了。
簡單來說,就是在write系統呼叫和實際的磁碟之間還有一層由內核維護的緩衝。
在操作磁碟檔案的時候,雖然有內核維護的緩衝來減少存取磁碟的次數以節省開銷,但是還有一部分開銷是由系統呼叫產生的,也就是程式中確定每次write
或者read
多少個位元組,而stdio庫的緩衝就是幫程式設計師幹這件事的,分爲以下三類:
無緩衝
每個stdio庫函數立即呼叫write
或者read
行緩衝
只帶終端裝置的流預設爲這一緩衝型別。對於輸出流,在輸出一個換行符(除非緩衝區已經填滿)前將緩衝數據,遇到換行符會重新整理緩衝區。對於輸入流,每次讀取一行數據
全緩衝
單次讀寫數據(通過write和read)的大小和緩衝區相同,只帶磁碟的流預設採用此模式。
int fflush(FILE *stream);
stream
爲NULL
,則將重新整理所有的輸出緩衝區int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
功能:開啓pathname所標識的檔案,並返迴檔案描述符,flags可以指定檔案的開啓方式,mode指定了存取許可權,如果flags中沒有建立檔案的標誌,mode可以忽略
flag取值:
標誌 | 用途 |
---|---|
O_RDONLY | 以只讀方式開啓 |
O_WRONLY | 以只寫方式開啓 |
O_RDWR | 以讀寫方式開啓 |
O_CLOEXEC | 設定close-on-exec標誌,預設關閉,即exec後檔案描述符不關閉 |
O_CREAT | 若檔案不存在則建立,需要指定mode |
O_EXCL | 結合O_CREAT標誌使用,專門用於建立檔案(若檔案已存在,則直接返回錯誤) |
O_NONBLOCK | 以非阻塞方式開啓 |
O_APPEND | 總在檔案尾追加數據(若多個進程同時對同一檔案追加數據,可能導致檔案損壞) |
O_TRUNC | 截斷已有檔案,使其長度爲0 |
O_SYNC | 以同步方式寫入檔案 |
O_ASYNC | 當IO操作可行時,產生信號通知進程(此特性僅適用終端、僞終端、socket和管道) |
O_DSYNC | 提供同步的IO數據完整性,即write返回後,數據均已輸出到硬體 |
O_DIRECT | 無緩衝的輸入輸出 |
O_DIRECTORY | 如果pathname不是目錄,則失敗 |
O_LARGEFILE | 在32位元系統中使用該標誌開啓大檔案 |
O_NOATIME | 呼叫read時不修改檔案最近存取時間 |
O_NOCTTY | 不要讓pathname(所指向的終端裝置)成爲控制終端 |
O_NOFOLLOW | 對符號鏈接不予解除參照 |
補充:
O_DIRECT和O_SYNC的區別
O_DIRECT:繞過內核的頁面快取將數據寫入裝置,但是裝置本身也存在快取所以並不能保證數據就一定固化到磁碟上
O_SYNC:檔案數據和所有檔案元數據同步寫入磁碟
O_DSYNC、O_RSYNC、O_SYNC的區別
ssize_t read(int fd, void *buf, size_t count);
功能:從fd檔案中讀取至多count位元組的數據並儲存到buf中。
返回值爲實際讀取到的位元組數,如再無位元組可讀(例如讀到檔案結尾符EOF時),返回值爲0
ssize_t write(int fd, const void *buf, size_t count);
功能:從buf中讀取多達count位元組的數據寫入fd指代的已開啓的檔案中
返回值爲實際寫入檔案中的位元組數,有可能小於count
int close(int fd);
功能:釋放檔案描述符fd及相關的內核資源
成功返回0,失敗返回-1
off_t lseek(int fd, off_t offset, int whence);
功能:改變檔案偏移量
名稱 | 說明 | |
---|---|---|
參數 | fd | 檔案描述符 |
offset | 指定了一個以位元組爲單位的數值 | |
whence | 表示參照哪個基點來解釋offset,取值如下: SEEK_SET 檔案開頭 SEEK_CUR 當前偏移量 SEEK_END 檔案末尾 |
|
返回值 | off_t | 成功返回距檔案開頭的偏移量,失敗返回-1 |
lseek不適用於所有型別的檔案,不能用於如管道、FIFO、socket和終端
如果程式的檔案偏移量已經跨越了檔案結尾,然後在執行I/O操作,將會發生read
呼叫返回0,表示檔案結尾,write
可以正常寫入數據。從檔案結尾到重新用write
寫入數據的這段空間被稱爲檔案空洞,從程式設計角度看,檔案空洞中是存在位元組的,讀取空洞將會返回以0(空位元組)填充的緩衝區。然而檔案空洞不會佔用磁碟空間。
ls
命令可以檢視檔案在檔案系統中的大小(邏輯大小),這個大小是包含檔案空洞的空位元組大小的.du
命令可以檢視檔案在磁碟中實際佔用的空間,du -s test
結果表示的是多少個1024位元組