Linux系統程式設計(2)——檔案與IO之系統呼叫與檔案IO操作

尹成發表於2014-07-24

 

系統呼叫是指作業系統提供給使用者程式的一組“特殊”介面,使用者程式可以通過這組“特殊”介面來獲得得作業系統核心提供的特殊服務。在linux中使用者程式不能直接訪部核心提供的服務。為了更好的保護核心空間,將程式的執行空間分為核心空間和使用者空間,他們執行在不同的級上,在邏輯上是相互隔離的。

在linux中使用者程式設計介面(API)遵循了在UNIX中最流行的應用程式設計介面標準——POSIX標準。這些系統呼叫程式設計介面主要通過C庫(libc)實現的。

 

POSIX(Portable OperatingSystem Interface)是由IEEE制定的標準,致力於統一各種UNIX系統的介面,促進各種UNIX系統向互相相容的發向發展。IEEE 1003.1(也稱為POSIX.1)定義了UNIX系統的函式介面,既包括C標準庫函式,也包括系統呼叫和其它UNIX庫函式。POSIX.1只定義介面而不定義實現,所以並不區分一個函式是庫函式還是系統呼叫,至於哪些函式在使用者空間實現,哪些函式在核心中實現,由作業系統的開發者決定,各種UNIX系統都不太一樣。IEEE 1003.2定義了Shell的語法和各種基本命令的選項等。本書的第三部分不僅講解基本的系統函式介面,也順帶講解Shell、基本命令、帳號和許可權以及系統管理的基礎知識,這些內容合在一起定義了UNIX系統的基本特性。

 

在UNIX的發展歷史上主要分成BSD和SYS V兩個派系,各自實現了很多不同的介面,比如BSD的網路程式設計介面是socket,而SYSV的網路程式設計介面是基於STREAMS的TLI。POSIX在統一介面的過程中,有些介面借鑑BSD的,有些介面借鑑SYSV的,還有些介面既不是來自BSD也不是來自SYSV,而是憑空發明出來的(例如本書要講的pthread庫就屬於這種情況),通過Man Page的COMFORMING TO部分可以看出來一個函式介面屬於哪種情況。Linux的原始碼是完全從頭編寫的,並不繼承BSD或SYSV的原始碼,沒有歷史的包袱,所以能比較好地遵照POSIX標準實現,既有BSD的特性也有SYSV的特性,此外還有一些Linux特有的特性,比如epoll(7),依賴於這些介面的應用程式是不可移植的,但在Linux系統上執行效率很高。

 

可用的檔案I/O函式——開啟檔案、讀檔案、寫檔案等等。大多數linux檔案I/O只需用到5個函式:open、read、write、lseek以及close。不帶快取指的是每read和write都呼叫核心中的一個系統呼叫。這些不帶快取的I/O函式不是ANSIC的組成部分,但是POSIX組成部分。

檔案描述符

對於核心而言,所有開啟檔案都由檔案描述符引用。檔案描述符是一個非負整數。當開啟一個現存檔案或建立一個新檔案時,核心向程式返回一個檔案描述符。當讀、寫一個檔案時,用open或creat返回的檔案描述符標識該檔案,將其作為引數傳送給read或write。在POSIX.1應用程式中,整數0、1、2應被代換成符號常數STDIN_FILEN0、STDOUT_FILENO和STDERR_FILENO。這些常數都定義在標頭檔案<unistd.h>中

檔案描述符的範圍是0~OPEN_MAX。早期的UNIX版本採用的上限值是19(允許每個程式開啟20個檔案),現在很多系統則將其增加至63。

 

open函式

#include<sys/types.h>
#include<sys/stat.h>
#inlcude<fcntl.h>
int open(const char *pathname,intoflag,.../*,mode_t mode*/);


返回:若成功為檔案描述符,若出錯為-1

pathname是要開啟或建立的檔案的名字。

oflag引數可用來說明此函式的多個選擇項。

對於open函式而言,僅當建立新檔案時才使用第三個引數。

用下列一個或多個常數進行或運算構成oflag引數(這些常數定義在<fcntl.h>標頭檔案中):

必選項:以下三個常數中必須指定一個,且僅允許指定一個。

O_RDONLY 只讀開啟

O_WRONLY 只寫開啟

O_RDWR 可讀可寫開啟

 

以下可選項可以同時指定0個或多個,和必選項按位或起來作為flags引數。可選項有很多,這裡只介紹一部分,其它選項可參考open(2)的Man Page:

O_APPEND 表示追加。如果檔案已有內容,這次開啟檔案所寫的資料附加到檔案的末尾而不覆蓋原來的內容。

O_CREAT 若此檔案不存在則建立它。使用此選項時需要提供第三個引數mode,表示該檔案的訪問許可權。

O_EXCL 如果同時指定了O_CREAT,並且檔案已存在,則出錯返回。

O_TRUNC 如果檔案已存在,並且以只寫或可讀可寫方式開啟,則將其長度截斷(Truncate)為0位元組。

O_NONBLOCK 對於裝置檔案,以O_NONBLOCK方式開啟可以做非阻塞I/O(Nonblock I/O),非阻塞I/O在下一節詳細講解。

 

注意open函式與C標準I/O庫的fopen函式有些細微的區別:

以可寫的方式fopen一個檔案時,如果檔案不存在會自動建立,而open一個檔案時必須明確指定O_CREAT才會建立檔案,否則檔案不存在就出錯返回。

以w或w+方式fopen一個檔案時,如果檔案已存在就截斷為0位元組,而open一個檔案時必須明確指定O_TRUNC才會截斷檔案,否則直接在原來的資料上改寫。

 

第三個引數mode指定檔案許可權,可以用八進位制數表示,比如0644表示-rw-r--r--,也可以用S_IRUSR、S_IWUSR等巨集定義按位或起來表示,詳見open(2)的Man Page。要注意的是,檔案許可權由open的mode引數和當前程式的umask掩碼共同決定。

 

補充說明一下Shell的umask命令。Shell程式的umask掩碼可以用umask命令檢視:

 

$ umask
0022

creat函式

可用creat函式建立一個新檔案。

#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int creat(const char *pathname,mode_tmode);


返回:若成功只為寫開啟的檔案描述符,若出錯為-1。注意,此函式等效於:

open (pathname,O_WRONLY |O_CREAT|O_TRUNC,mode);


creat的一個不足之處是它以只寫方式開啟所建立的檔案。

 

close函式

可用close函式關閉一個開啟檔案:

#include<unistd.h>
int close(int filedes);


返回:若成功為0,若出錯為-1

當一個程式終止時,它所有的開啟檔案都由核心自動關閉。很多程式都使用這一功能而不顯式地用close關閉開啟的檔案。

 

 

lessk函式——檔案定位

每個開啟檔案都有一個與其相關聯的“當前檔案偏移量”。它是一個非負整數,用以度量從檔案開始處計算的位元組數。通常,讀、寫操作都從當前檔案偏移理處開始,並使偏移理增加所計或寫的位元組數。按系統預設,當開啟一個檔案時,除非指定O_APPEND選擇項,否則該偏移量被設定為0。可以呼叫lseek顯式地定位一個開啟檔案。

#include<sys/types.h>
#include<unistd.h>
off_t lseek(it filesdes,off_t offset,intwhence);


返回:若成功為新檔案位移,若出錯為-1。

對於引數offset的解釋與引數whence的值有關。

 

若whence是SEEK_SET,則將該檔案的位移量設定為距檔案開始處offset個位元組。若whence是SEEK_CUR,則將該檔案的位移量設定為其當前值加offset,offset可為正或負。若whence是SEEK_END,則將該檔案的位移量設定為檔案長度加offset,offset可為正或負。若lseek成功執行,則返回新的檔案位移量,為此可以用下列方式確定一個開啟檔案的當前位移量:

off_t currpos;
currpos=lseek(fd,0,SEEK_CUR);

read函式

用read函式從開啟檔案中讀資料

#include<unistd.h>
ssize_t read(int feledes,void *buff,size_tnbytes);


返回:讀到的位元組數,若已到檔案尾為0,若出錯為-1。如read成功,則返回讀到的位元組數。如已到達檔案的尾端,則返回0。有多種情況可使實際讀到的位元組數小於要求讀位元組數:

讀普通檔案時,在讀到要求位元組數之前已到達了檔案尾端。例如,若在到達檔案尾端之間還有30個位元組,而要求讀100個位元組,則read返回30,下一次再呼叫read時,它將返回0(檔案尾端)。當從終端裝置讀時,通常一次最多讀一行。

當從網路讀時,網路中的緩衝機構可能造成返回值小於所要求讀的位元組數。某些面向記錄的裝置,例如磁帶,一次最多返回一個記錄。讀操作從檔案的當前位移量處開始,在成功返回之前,該位移量增加實際讀得的位元組數。

 

 

write函式

用write函式向開啟檔案寫資料。

#include<unistd.h>
ssize_t write(int filedes,const void*buff,size_t nbytes);


返回:若成功為已寫的位元組數,若出錯為-1。其返回值通常與引數

 

nbytes的值不同,否則表示出錯。write出錯的一個常見原因是:磁碟已寫滿,或者超過了對一個給定程式的檔案長度限制。

對於普通檔案,寫操作從檔案的當前位移量處開始。如果在開啟該檔案時,指定了O_APPEND選擇項,則在每次寫操作之前,將檔案位移量設定在檔案的當前結尾處。在一次成功寫之後,該檔案位移量增加實際寫的位元組數。

 

 

 

 

 

 

相關文章