Linux學習:文件IO(不帶緩衝區),原子操作概念

在前面的文章我們介紹過不帶緩衝區的IO,這節我們主要介紹不帶緩衝區的IO相關內容。原子操作對於文件共享是十分重要的,因此我們將介紹一些原子操作相關概念。

1:文件描述符
對於內核而言,所有打開的文件都通過文件描述符引用。當我們打開或者創建一個文件的時候,都會返回一個非負的整數,我們打開和創建文件都用這個非負的整數。
同時,通常在我們執行一個進程的時候,都會默認打開三個文件描述符,他們分別是標準輸入(0:STDIN_FILENO)、輸出(1:STDOUT_FILENO)、錯誤(2:STDERR_FILENO)相關聯的。他們的宏定義在unistd.h文件中。
文件描述符上限是OPEN_MAX。


2:open函數介紹

函數原型:int open(const char* pathname, int flag);
                                         int open(const char* pathname, int flag, mode_t mode);
pathname:是打開文件的路徑名。
flag:是打開文件的一些選項。
mode:只有我們需要創建文件的時候,纔會將指定mode的模式。

O_RDONLY:只以讀的方式打開文件。
O_WRONLY:只一寫的方式打開文件。
O_EXCL:測試文件受否存在,如果不存在則創建文件。(注:如果同時指定O_CREAT並且文件已經存在則出錯)
O_APPEND:每次寫入都追加到文件末尾。
O_CREAT:如果文件不存在,則創建文件。使用這個選項的時候,需要第三個參數。
        mode:權限位,也就是我們使用ls -l顯示的各種讀寫和執行權限的位。
        S_IRWXU:用戶有讀寫執行權限。
        S_IRUSR:用戶有讀權限。
        S_IWUSR:用戶有寫權限。
        S_IXUSR:用戶有執行權限。
        S_IRWXG:同組用戶有讀寫執行權限。
        S_IRGRP:同組用戶有讀權限。
        S_IWGRP:同組用戶有寫權限。
        S_IXGRP:同組用戶有執行權限。
        S_IRWXO:其他用戶有讀寫執行權限。
        S_IROTH:其他用戶有讀權限。
        S_IWOTH:其他用戶有寫權限。
        S_IXOTH:其他用戶有執行權限。
O_TRUNC:如果文件存在的話,並且我們只爲讀寫打開文件的話,那麼文件長度將會截斷爲0。也就是文件長度會變爲0,相當於我們將文件內容全部刪去再重新寫入。
O_NOBLOCK:指定文件爲非阻塞IO,正常情況下在我們寫入文件的時候,我們都等待寫入完成之後再返回。如果指定此flag,那麼將直接返回,而不管是否寫入完成。
O_SYNC:每一次寫操作完成之前都會更新文件的屬性。
O_RSYNC:等待任何對文件同一部分未決寫操作完成。(這個標誌和下面一個標誌不是很理解,如果理解的話,請在評論區討論。最好是能有一個實際的例子)。
O_DSYNC:每次寫等待物理操作完成。僅當文件屬性根棍以反映文件數據變化時,此標誌纔會影響文件屬性。

open函數返回的文件描述符一定是當前可用的最小文件描述符。

文件名和路徑名的截斷:
這個概念我簡單介紹一下,就是我們輸入打開文件的路徑名的時候,由於我們輸入的太長了,我們無法識別那麼長的路徑名,就會保留其中的一部分。這樣就無法識別出具體
的打開的文件了(我們保存這部分路徑名可能與其他文件的路徑名重名)。
我們這裏可以使用pathconf獲取一些路徑名長度,我發現我的機器長度限制是4096。

creat:創建文件
函數原型:create(const char *pahtname, mode_t mode);
此函數和open函數是有雷同的部分,不做了解。

close:關閉打開的文件描述符(就是我們打開一個文件一定會用到一些資源去管理這個打開的文件,那麼這裏關閉就是釋放這些被棧用的資源)
函數原型:int close(int filedes);

lseek函數:更改當前文件的偏移量,也就是我們從文件哪裏開始讀寫文件。
 當我們打開一個文件或者創建一個新的文件,文件偏移量默認爲0。也就是說從文件開始位置讀寫文件。同時我們也可以用lseek函數將當前文件偏移量定位到文件指定的置爲。
 函數原型:lseek(int filedes, off_t offset, int whence);
                                            lseek_64(int filedes, off64_t offset, int whence);
 filedes:是文件描述符。
 offset:是偏移的大小。(可以爲正<增加文件偏移>,也可以爲負<減少文件偏移>)
 關於off_t是否夠用的問題,我在這裏簡單講解一下。 但是我們看函數原型的時候,有32位和64位兩種文件偏移量。如果是32位那麼文件偏移量最大時2的32次方減一,64位也是如此。
 32位的文件偏移量最大時2TB(2^31-1字節),64位文件最大偏移量2^33TB,應該足夠使用了。如果是不同的平臺會用不同的大小,這裏只列舉了32位和64位。具體實現還是靠硬件,
 這裏就不逐一列舉了。
 whence:是指定位置開始進行便宜。
             SEEK_SET:即從文件開頭爲只進行便宜。
             SEEK_CUR:從當前文件位置進行便宜。
             SEEK_END:從文件末尾位置開始便宜。
同時lseek也可以測試文件是否設置偏移量,如果文件描述符引用的是一個管道、套接字,則lseek返回-1.

read函數:從文件中讀取指定字節數目的數據。
函數原型:ssize_t read(int filedes, void *buf, size_t nbytes);
filedes:文件描述符。
buf:讀取到字節存放的緩衝區。
nbytes:讀取多少個字節。
返回值:實際讀取到的字節數。
實際讀取到字節數可能小於我們要求讀取的字節數。(eg,當前文件位置到文件末尾還有三十個可讀的字節,但是我們要求讀取200個字節,這就返回30個字節。但是在下次讀取的時候,
返回30個字節。還有從網絡中讀取數據,有些時候可能由於網絡延遲讀取小於我們讀取到的字節數)。

write函數:向文件中寫入指定字節數的數據。
函數原型:ssize_t write(int filedes, const void* buf, size_t nbytes);
同上,buf是寫入字節存儲位置。nbytes是寫入的字節數。返回值是實際寫入的字節數。
同時,如果我們指定了O_APPEND,每次我們寫入之前都將文件偏移量設置爲文件末尾的位置。

文件共享:
在介紹文件共享之前,我們先介紹一下內核是使用什麼結構表示打開的文件的。
內核使用三種數據結構表示打開的文件。
(1)每個進程都有一個記錄項,記錄項抱哈一張打開的文件描述符表,每個文件描述符佔用一項。我們在進程中使用一個整型記錄這個文件項的位置。
        每個文件描述符相關聯的有兩部分:文件標誌,指向文件表的指針。
(2)對於打開的文件內核維持一張文件表。
    文件表中有文件狀態標誌、當前文件的偏移量和v節點指針。
(3)每個打開的文件都有一個v節點。
    v點包含了文件類型和對該文件各種操作的函數指針。同時還有i節點信息,當前文件的長度。
關於這些內容瞭解即可。如下圖所示,可以很好的理解這三者之間的關係。

file

同時,當我們兩個進程同時打開一個文件的時候,其結構是下圖這樣的。

file

根據上述內容可以理解,如果是多個進程同時向一個文件中寫入數據的時候,可能產生預期不到的結果。如果想要避免這種問題,就得理解原子操作相關的概念。

原子操作:
        概念:不受其他影響的獨立完成一個操作的全過程。如下程序,我們向一個文件尾部添加內容。
        lseek(fd, SEEK_END, 0);
        write(fd, buf, size);
        如果是一個程序,那麼我們這樣寫沒什麼問題。但是如果是兩個獨立的進程,那麼這樣寫可能就麻煩了(如兩個進程分別是進程A和進程B,
        如果進程A需要寫100字節,進程B需要寫入1000字節。那麼可能在進程A寫入50字節的時候進程B開始寫入,並且進程B寫入1000字節。那麼這是進程A寫入就會覆蓋
        進程B寫入開頭的50字節,這樣記錄的信息可能就出現了損壞)。如下圖所示,兩個文件向一個文件寫入內容。

file

dup和dup2函數:複製文件描述符。
函數原型: int dup(int filedes);
                                            int dup2(int filedes, int filedes2);
前一個函數返回一個新的文件描述符,這個新的文件描述符指向的文件表項和filedes文件描述符指向的文件表項是同一個(返回的文件描述符一定是當前進程表項中最小的文件描述符)。
第二個函數使用指定的文件描述符去複製filedes文件描述符,如果filedes2已經使用,則先關閉filedes2,然後在複製filedes。
參考下述圖片,去理解複製一個文件描述符的意義。

file

前面曾經提到過IO緩衝區,當我們使用緩衝區進行向磁盤寫入數據的時候,有些事後我們可能無法及時將數據寫入到磁盤中。當我們的機器意外出現事故的時候,我們可能丟失數據。
還有就是一些數據庫需要及時的保存,因此、我們需要讓寫這個操作及時進行,下面有三個函數會將數據及時寫入到磁盤中。
函數原型:int fsync(int filedes);將指定文件描述符數據寫入到磁盤中,這裏是等待寫操作結束返回。
                                         int sync();將所有修改過的緩衝區排入寫操作隊列。
                                         int fdatasync(int filedes);將數據寫入到磁盤的同時還將文件屬性更新。

前面曾經文件的一些性質,這裏呢有個函數可以通過文件描述符改變打開文件的一些性質。
函數原型:int fcntl(int filedes, int cmd, ...);
                (1):複製現有的描述符。
                (2):獲得/設置文件描述符標誌。
                (3):獲得/設置文件狀態標誌。
                (4):獲得/設置異步I/O所有權(異步IO是通過信號的方式實現的,在講解套接字部分會有所講解)。
                (5):獲得/設置記錄所
參看man手冊,我們會發現如下許多cmd。
        F_DUPFD:複製一個新的文件描述符。

        F_GETFD/F_SETFD:獲得和設置文件描述符標記。當前的標記只有一種,就是FD_CLOEXEC(在執行exec函數的時候關閉該文件描述符)。

        F_GETFL/F_SETFL:獲得和設置文件狀態標記。文件狀態標記在前面除了O_DSYNC和O_SYNC都可以改變。(eg:讀,寫,追加等等)我這裏參考的是我這個版本的man手冊,
        如果想要了解請參考你的版本man手冊。

        F_GETOWN/F_SETOWN:獲得和設置異步IO所有權。在學習套接字部分將會了解到這裏。

        關於記錄鎖,使用如下圖的結構。
        F_SETLK/F_SETLKW/F_GETLK:請求或者釋放一個鎖/設置一個文件鎖/返回一個鎖(如果沒有鎖,那麼在l_type字段設置爲F_UNLCK)。
        在學習多進程的時候,我會寫一個實驗關於文件鎖的,就是多個進程讀寫文件,使用文件的記錄鎖實現文件共享。

![file](http://jeff.spring4all.com/FhnY51hz_j5wXkeBMnrJlv0Qn7_Y)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章