1. 程式人生 > >【Linux】基礎I/O

【Linux】基礎I/O

系統檔案I/O

linux下所有裝置都是以檔案存在的,,可以說是一切皆檔案,所以當我們需要用到這些裝置的時候,首先就需要開啟它們,下面我們來詳細瞭解一下檔案I/O操作。
用到的檔案I/O有以下幾個操作:開啟檔案、讀檔案、寫檔案、關閉檔案等,對應用到的函式有:open、read、write、close、lseek(檔案指標偏移)

  1. 開啟檔案
int open(const char *pathname, int flags);
int open(const char *pathname,int flags,mode_t mode);

 引數:
 a、pathname:開啟或者建立的檔名字,如"text"
b、 flags: O_RDONLY:只讀開啟 O_WRONLY:只寫開啟 O_RDWR:讀、寫開啟 O_CREAT:若此檔案不存在則建立它,使用O_CREAT時後面要跟檔案的訪問許可權位,如O_CREAT,0777 O_APPEND:每次寫時都追加到檔案的尾端 O_EXCL :如果同時指定了O_CREAT,而檔案已經存在,則出錯,用此可以測試一個檔案是否存在,如果不存在,則建立此檔案,這使測試和建立兩者成為一個原子操作 O_DSYNC:使每次write等待物理I/O操作完成,但是如果該寫操作並不影響讀取剛寫入的資料,則不需等待檔案屬性被更新 O_NONBLOCK :如果path引用的是一個FIFO、一個塊特殊檔案或一個字元特殊檔案,則此選項為檔案的本次開啟操作和後續的I/O操作設定非阻塞方式 O_NOCTTY:如果path引用的是 終端裝置,則將該裝置分配為此程序的控制終端 O_SYNC:使每次write
要等待物理I/O操作完成,包括有該write引起的檔案屬性更新所需的I/O O_TRUNC:如果檔案存在,並且是常規檔案而且以讀寫或者只寫開啟,則將其長度截斷為0,如果檔案是FIFO或終端裝置檔案,O_TRUNC標誌被忽略,否則O_TRUNC不明確 O_DIRECTORY:如果pathname引用的不是目錄,則出錯 返回值: 成功:檔案描述符 失敗:-1
  1. 讀檔案
函式原型   ssize_t read(int fd, void *buf, size_t count);
  引數:
     a、fd:呼叫open後返回的檔案描述符
     b、buf:用來存放從檔案中讀到的資料的緩衝區
     c、count
:讀取的位元組數 返回值: 成功:讀到的位元組數,如果讀到檔案尾端,則返回0 失敗:-1
  1. 寫檔案
函式原型   ssize_t write(int fd, const void *buf, size_t count);
     引數:
     a、fd:呼叫open後返回的檔案描述符
     b、buf:從來存放資料的緩衝區
     c、count:寫入資料的位元組數

返回值:
     成功:返回已寫的位元組數
     失敗:-1
  1. 關閉檔案
函式原型  int close(int fd);
     引數:
     a、fd:呼叫open後返回的檔案描述符
  1. 檔案偏移
函式原型   off_t lseek(int fd, off_t offset, int whence);
      引數:
      a、fd:呼叫open後返回的檔案描述符
      b、offset  和引數whence有關,通常設定為0 (according  to thedirective whence as follows)
      c、whence:
         SEEK_SET: 將檔案的偏移量設定為距檔案開始處offset個位元組
         SEEK_CUR:將檔案的偏移量設定為其當前值加offset個位元組,offset可為正或負
         SEEK_END: 將檔案的偏移量設定為檔案長度加offsetoffset可為正或負

返回值:
     成功:返回新的檔案偏移量
     失敗:-1

檔案描述符 fd

對於核心而言,所有開啟的檔案都通過檔案按描述符引用。檔案描述符是一個非負整數。當開啟一個現有檔案或者建立一個新檔案時,核心向程序返回一個檔案描述符。當讀、寫一個檔案時,使用open/creat返回的檔案描述符標識該檔案,將其作為引數傳送給read或write。

linux系統下檔案描述符0是標準輸入,1是標準輸出,2是標準出錯,所以一般開啟檔案的時候檔案描述符都是從3開始。 這裡read和write是不能格式化讀取和寫入。
當開啟檔案時,作業系統在記憶體中要建立相應的資料結構來描述目標檔案。於是就有了file結構體。表示一個已經開啟的檔案物件。而程序執行open系統呼叫,所以讓程序與檔案關聯起來。每個程序都有一個指標*files,指向一張表fiiles_struct,該表包含一個指標陣列,每個元素都是一個指向開啟檔案的指標。所以,本質上,檔案描述符就是該陣列的下標。
這裡寫圖片描述

檔案描述符的分配規則:

在file_struct 陣列中,找到當前沒有被使用的最小的一個下標,作為新的檔案描述符。

重定向

1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<sys/stat.h>
  4 #include<string.h>
  5 #include<sys/types.h>
  6 #include<fcntl.h>
  7 int main()
  8 {
  9         close(1);
 10         int fd=open("myfile",O_WRONLY|O_CREAT,0644);
 11         if(fd<0)
 12         {
 13                 perror("open");
 14                 return 1;
 15         }
 16         printf("fd: %d\n",fd);
 17         fflush(stdout);
 18         close(fd);
 19         exit(0);
 20 }

執行程式後,發現本來輸出到顯示器上的內容,輸出到了檔案myfile中,這就叫輸出重定向。
常見的重定向有:>,>>,<.
重定向的本質如圖:
這裡寫圖片描述
printf一般往stdout中輸出,但是stdout底層訪問檔案時,找的還是fd:1,但此時,fd:1下標所表示的內容已經變成了myfile的地址,不再是顯示器檔案的地址,所以,輸出的任何訊息都會往檔案中寫入,進而實現輸出重定向。
函式dup和dup2
這兩個函式都可用來複制一個現存的檔案描述符,返回的新檔案描述符與引數fieldes共享同一個檔案表項.

      #include <unistd.h>

       int dup(int oldfd);
       int dup2(int oldfd, int newfd);

由dup返回的新檔案描述符一定是當前可用檔案描述符中的最小數值。用dup2則可以用fnewfd引數指定新描述符的數值。如果newfd已經開啟,則先將其關閉。如若oldfd等於newfd,則dup2返回newfd,而不關閉它。

檔案系統

我們用ls -l可以檢視檔案資訊:
這裡寫圖片描述
這些資訊都有:模式,硬連結數,檔案所有者,組,大小,最後修改時間,檔名。
相應的,我們用stat命令也可以檢視資訊:
這裡寫圖片描述

因此在檔案系統中它們相對應的如圖:
這裡寫圖片描述

建立一個新檔案有以下步驟:

  • 儲存屬性
    核心先找到一個空閒的i節點(664231),核心把檔案資訊記錄在其中。
  • 儲存資料
    核心找到三個空閒塊:例如:300,500,800。將核心緩衝區的第一塊資料複製到300,下一塊複製到500,以此類推。
  • 記錄分配情況
    檔案內容按順序存放。核心在inode上的磁碟分佈區記錄了上述塊列表。
  • 新增檔名到目錄
    新的檔名abc,核心將入口(例664231,abc)新增到目錄檔案。檔名和inode之間的對應關係將檔名和檔案內容及屬性連線起來。

由以上可知:真正找到磁碟上檔案的並不是檔名,而是inode。

硬連結

硬連線指通過索引節點來進行連線。在Linux的檔案系統中,儲存在磁碟分割槽中的檔案不管是什麼型別都給它分配一個編號,稱為索引節點號(Inode Index)。在Linux中,多個檔名指向同一索引節點是存在的。一般這種連線就是硬連線。硬連線的作用是允許一個檔案擁有多個有效路徑名,這樣使用者就可以建立硬連線到重要檔案,以防止“誤刪”的功能。其原因如上所述,因為對應該目錄的索引節點有一個以上的連線。只刪除一個連線並不影響索引節點本身和其它的連線,只有當最後一個連線被刪除後,檔案的資料塊及目錄的連線才會被釋放。也就是說,檔案真正刪除的條件是與之相關的所有硬連線檔案均被刪除。

由於硬連結是有著相同 inode 號僅檔名不同的檔案,因此硬連結存在以下幾點特性:

  • 檔案有相同的 inode 及 data block;
  • 只能對已存在的檔案進行建立;
  • 不能交叉檔案系統進行硬連結的建立;
  • 不能對目錄進行建立,只可對檔案建立;
  • 刪除一個硬連結檔案並不影響其他有相同 inode 號的檔案。
軟連結

另外一種連線稱之為符號連線(Symbolic Link),也叫軟連線。軟連結檔案有類似於Windows的快捷方式。它實際上是一個特殊的檔案。在符號連線中,檔案實際上是一個文字檔案,其中包含的有另一檔案的位置資訊。

軟連結與硬連結不同,若檔案使用者資料塊中存放的內容是另一檔案的路徑名的指向,則該檔案就是軟連線。軟連結就是一個普通檔案,只是資料塊內容有點特殊。軟連結有著自己的 inode 號以及使用者資料塊。因此軟連結的建立與使用沒有類似硬連結的諸多限制:

  • 軟連結有自己的檔案屬性及許可權等;
  • 可對不存在的檔案或目錄建立軟連結;
  • 軟連結可交叉檔案系統;
  • 軟連結可對檔案或目錄建立;
  • 建立軟連結時,連結計數 i_nlink 不會增加;
  • 刪除軟連結並不影響被指向的檔案,但若被指向的原檔案被刪除,則相關軟連線被稱為死連結(即 dangling link,若被指向路徑檔案被重新建立,死連結可恢復為正常的軟連結)。

我們用ln 建立一個硬連結檔案;用 ln -s建立一個軟連線檔案:
這裡寫圖片描述
從上面的結果中可以看出,硬連線檔案b.txt與原檔案a.txt的inode節點相同,然而符號連線檔案的inode節點不同。

這裡寫圖片描述
當刪除原始檔案a.txt後,硬連線b.txt不受影響,但是符號連線a.s檔案無效.

動態庫和靜態庫

  1. 靜態函式庫

    這類庫的名字一般是libxxx.a;利用靜態函式庫編譯成的檔案比較大,因為整個 函式庫的所有資料都會被整合進目的碼中,他的優點就顯而易見了,即編譯後的執行程式不需要外部的函式庫支援,因為所有使用的函式都已經被編譯進去了。當然這也會成為他的缺點,因為如果靜態函式庫改變了,那麼你的程式必須重新編譯。

生成靜態庫:

add.h
  1 #ifndef __ADD_H_
  2 #define __ADD_H_
  3 int add(int a,int b);
  4 
  5 #endif // _ADD_H_

add.c
  1 #include"add.h"
  2 
  3 int add(int a,int b)
  4 {
  5 return a+b;
  6 }
sub.h
  1 #ifndef __SUB_H_
  2 #define __SUB_H_
  3 int sub(int a,int b);
  4 
  5 #endif // _SUB_H_

sub.c   
  1 #include"sub.h"
  2 
  3 int sub(int a,int b)
  4 {
  5 return a-b;
  6 }

 main.c
  1 #include<stdio.h>
  2 #include"add.h"
  3 #include"sub.h"
  4 
  5 int main()
  6 {
  7         int a=10;
  8         int b=20;
  9         printf("add(10,20)=%d\n",a,b,add(a,b));
 10         a=100;
 11         b=20;
 12         printf("sub(10,20)=%d\n",a,b,sub(a,b));
 13 
 14 }

這裡寫圖片描述

  1. 動態函式庫

    這類庫的名字一般是libxxx.so;相對於靜態函式庫,動態函式庫在編譯的時候 並沒有被編譯進目的碼中,你的程式執行到相關函式時才呼叫該函式庫裡的相應函式,因此動態函式庫所產生的可執行檔案比較小。由於函式庫沒有被整合進你的程式,而是程式執行時動態的申請並呼叫,所以程式的執行環境中必須提供相應的庫。動態函式庫的改變並不影響你的程式,所以動態函式庫的升級比較方便。

生成動態庫:
其中執行動態庫有三種方法;
方法一:(如圖黃框)更改LD_LIBRARY_PATH。
這裡寫圖片描述
方法二:拷貝.so檔案到系統共享庫路徑下:/usr/lib。
這裡寫圖片描述
方法三:ldconfig 配置 /etc/ld.so.conf.d/ ,ldconfig更新。