Linux系統編程:基本I/O系統調用

文件描述符

進程每打開一個文件的時候,會得到該文件的文件描述符,然後續的讀寫操做都把文件描述符做爲參數。在用戶空間或者內核空間,都是經過文件描述符來惟一地索引一個打開的文件。文件描述符使用int類型表示,文件描述符的範圍從0開始,到上限值-1,默認狀況下,上限值爲1024,也就是說,進程默認狀況下最多能夠打開1024個文件。負數是不合法的文件描述符,當函數調用出錯時,返回的文件描述符爲-1。html

每一個進程都至少包含三個文件描述符:socket

文件描述符 表示
0 標準輸入(stdin) STDIN_FILENO
1 標準輸出(stdout) STDOUT_FINENO
2 標準錯誤(stderr) STDERR_FILENO

遵循Linux一切皆文件的概念,文件描述符除了訪問普通文件外,幾乎可以訪問任何可以讀寫的東西。包括設備文件、管道、先進先出緩衝區、套接字等。函數

open()系統調用

對文件進行讀寫以前,必須先打開文件。Linux提供了系統調用open()。open()有兩個函數原型:指針

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

兩個函數都可用來打開文件,第二個函數比第一個多了參數mode,mode指定文件的權限---當建立新文件的時候才須要。若是文件打開成功,則返回文件描述符,指向pathname所指定的文件。flags參數用於指定打開的方式,它支持三種訪問模式:code

訪問模式 描述
O_RDONLY 打開一個供讀取的文件
O_WRONLY 打開一個供寫入的文件
O_RDWR 打開一個可供讀寫的文件

flags參數還能夠與下面的值進行按位或運算,修改打開文件的行爲:htm

打開方式 描述
O_APPEND 寫入的全部數據將被追加到文件的末尾
O_CREAT 打開文件,若是文件不存在則創建文件
O_EXCL 若是已經設置了O_CREAT且文件存在,則強制open()失敗,只能與O_CREAT搭配使用
O_TRUNC 若是文件存在,且是普通文件,而且有寫權限,將文件內容清空
O_NONBLOCK 文件以非阻塞模式打開,請見read系統調用

舉個例子,下面的句子表示:以寫的方式打開文件,若是文件不存在,則建立新的文件,而且文件的內容爲空:blog

int fd ;
fd = open("file.txt",O_WRONLY|O_CREAT|O_TRUNC,0644);

這裏的0644指定了新建立的文件訪問權限,參數mode的取值以下:索引

打開方式 描述
S_IRUSR 文件全部者有讀權限
S_IWUSR 文件全部者有寫權限
S_IXUSR 文件全部者有執行權限
S_IRWXU 文件全部者有讀、寫、執行權限
S_IRGRP 組用戶有讀權限
S_IWGRP 組用戶有寫權限
S_IXGRP 組用戶有執行權限
S_IRWXG 組用戶有讀、寫、執行權限
S_IROTH 全部人有讀權限
S_IWOTH 全部人有寫權限
S_IXOTH 全部人有執行權限
S_IRWXO 全部人有讀、寫、執行權限

實際上最終寫入磁盤的文件訪問權限是由mode參數和用戶的文件建立掩碼(umask)執行按位與操做獲得的。舉個例子:進程

int main()

{
        int fd;
        fd = open("TEST.txt",O_WRONLY|O_CREAT|O_TRUNC,S_IRWXU|S_IRWXG|S_IRWXO );//以只讀方式打開文件  
        //等價於  fd = open("TEST.txt",O_WRONLY|O_CREAT|O_TRUNC,0777 );//以只讀方式打開文件  
        if(fd == -1)
                perror("open file error!");
        return 0;
}

按理來講,建立出來的文件的訪問權限應該是-rwxrwxrwx,而查看後發現其實不是:內存

ls -l TEST.txt 
-rwxrwxr-x 1 huanzhewu huanzhewu 0  5月  7 21:29 TEST.txt  【權限爲0775】

查看當前的掩碼:

$ umask
0002

能夠發現 0775 = 0777 ^ (~0002) ,因此0775纔是最後的文件訪問權限。umask是進程級屬性,經過調用umask()函數來修改,支持用戶修改新建立的文件和目錄的權限。

總結起來能夠獲得這樣一條公式:

newmode = mode ^ (~ umask)

總結一下:至此,咱們瞭解了文件打開所提供的兩個系統調用函數open(),瞭解了打開文件的方式、新建文件的訪問權限設置。若是文件打開成功,那麼將返回一個文件描述符,這是一個非零整數(由於0,1,2是進行已經擁有的文件描述符),不然函數將返回-1

creat()系統調用

顧名思義,creat函數用來建立一個文件,不過咱們可能產生疑問:前面的open函數使用一些選項後,不是也能夠建立新文件嗎?沒錯,creat函數徹底等價與下面的open語句:

int fd ;
fd = open("file.txt",O_WRONLY|O_CREAT|O_TRUNC,0644);   
fd = creat("file.txt,0644");   /*兩個語句的做用徹底等價*/

因爲選項O_WRONLY|O_CREAT|O_TRUNC組合常用,於是系統調用專門使用creat函數來提供這個功能。creat函數的原型以下:

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

其中參數的描述與open的參數一致,這裏再也不贅述。

read()系統調用

文件打開後,就可以讀文件了。read()是最基礎、最多見的讀取文件的機制。read的函數原型爲:

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

fd 爲文件描述符。每次調用read函數時,會從fd指向的文件的當前偏移(或稱文件位置)開始讀取count字節到buf所指的的內存中。隨着文件的讀取,fd的文件位置指針會向前移動。關於read的讀取,會出現不少須要思考的問題:

  1. 問題一:若是文件長度爲0
  2. 問題二:若是文件長度不夠count長度
  3. 問題三:若是讀取時,read被信號中斷了

咱們一一來看:

  1. 問題1屬於「沒有數據可讀」,此時read調用會阻塞,直到有數據可讀;
  2. 問題2屬於到達數據結尾(EOF),此時read調用返回0
  3. 問題三,read調用返回大於0小於count的值;若是在讀取任何數據以前被信號中斷,則返回-1,同時把errno設置爲EINTR。

因爲read有這麼多須要考慮的問題,若是但願每次都能讀入count個字節,下面是一段示例代碼:

//保證讀取200個字節到ptr中

ssize_t ret ;
int len = 200;
 while(len!= 0 && ( ret = read(fd, ptr, len )) != 0)
 {
                if(ret == -1)
                {
                        if( errno == EINTR)
                                continue;
                        perror("read");
                        break;
                }
                len -= ret ;
                ptr += ret ;
    }

再來看看問題1,當文件沒有數據能夠讀時(一開始就沒有),read調用會被阻塞,直到文件有數據能夠讀,這是一種阻塞I/O。若是文件以O_NONBLOCK模式打開,則文件爲非阻塞模式,當文件沒有數據能夠讀時,read系統調用返回-1,並把errno設置爲EAGAIN。

ssize_t ret ;
        int len = 200;
        while(len!= 0 && ( ret = read(fd, ptr, len )) != 0)
        {
                if(ret == -1)
                {
                        if( errno == EINTR)
                        {
                                printf("讀取被中斷\n");
                                continue;
                        }
                        if(errno== EAGAIN)
                        {
                                printf("文件沒有可讀\n");
                                //從新提交讀取
                                continue;
                        }
                        break;
                }
                len -= ret ;
                ptr += ret ;
        }

除了errno被設置爲EINTR與EAGAIN,其餘狀況下都是出現嚴重的文件讀取錯誤,從新執行讀操做不會成功。

write() 系統調用

write的函數原型爲:

#include <unistd.h>
    ssize_t write(int fd, const void *buf, size_t count);  【將buf中count個字節的內容寫入fd指定的文件中】

write的返回值比較簡單:

  • 寫入失敗返回-1 ,同時設置errno的值
  • 寫入成功返回成功寫入的字節數。
  • 返回0時沒有特殊含義,僅表示寫入了0個字節的內容。

對於普通文件,write基本能保證每次執行調用可以寫入所有的內容。對於其餘文件如socket,須要進行循環寫,保證全部的字節都寫入了文件中:

ssize_t ret ;
int len = 200;
 while(len!= 0 && ( ret = write(fd, ptr, len )) != 0)
 {
                if(ret == -1)
                {
                        if( errno == EINTR)
                                continue;
                        perror("write");
                        break;
                }
                len -= ret ;
                ptr += ret ;
    }

一樣的,當以非阻塞的模式打開文件時(O_NONBLOCK),系統調用write()會返回-1,並把errno設置爲EAGAIN。

系統調用write()時,數據從用戶空間的緩衝區中拷貝到了內核空間的緩衝區,但並無當即把數據寫入磁盤中,這稱爲延遲寫。延遲寫的問題在於,若是在數據真正寫入磁盤以前系統崩潰了,則數據可能丟失。內核設置了一個時間,在該時間內將內核空間緩衝區上的數據寫入磁盤,該時間稱爲"最大存放時效"。Linux系統也支持強制文件當即寫入磁盤上,這在後面介紹。

close()系統調用

程序完成文件的讀寫後,調用close函數關閉文件描述符與文件之間的鏈接,使得文件描述符能夠被重用。close的函數原型爲:

#incldue<unistd.h>
int close (int fd);

文件關閉成功返回0,出錯返回-1,並設置相應的errno。文件成功關閉並不覺得着該文件的數據已經被寫入磁盤,同步選項在後續介紹。

總結:本文簡單介紹了文件的打開、建立、讀寫、關閉操做,介紹了一些經常使用的open參數選項,creat與open的等價性,循環讀、循環寫的必要性,也關注了各個系統調用的返回值含義,瞭解如何設置非阻塞讀寫,並簡單提到了延遲寫的問題,在後續的文件中將介紹同步I/O的內容。

文章鏈接:http://www.cnblogs.com/QG-whz/p/5469891.html

相關文章
相關標籤/搜索