【Linux】基礎I/O
系統檔案I/O
linux下所有裝置都是以檔案存在的,,可以說是一切皆檔案,所以當我們需要用到這些裝置的時候,首先就需要開啟它們,下面我們來詳細瞭解一下檔案I/O操作。
用到的檔案I/O有以下幾個操作:開啟檔案、讀檔案、寫檔案、關閉檔案等,對應用到的函式有:open、read、write、close、lseek(檔案指標偏移)
- 開啟檔案
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
- 讀檔案
函式原型 ssize_t read(int fd, void *buf, size_t count);
引數:
a、fd:呼叫open後返回的檔案描述符
b、buf:用來存放從檔案中讀到的資料的緩衝區
c、count :讀取的位元組數
返回值:
成功:讀到的位元組數,如果讀到檔案尾端,則返回0
失敗:-1
- 寫檔案
函式原型 ssize_t write(int fd, const void *buf, size_t count);
引數:
a、fd:呼叫open後返回的檔案描述符
b、buf:從來存放資料的緩衝區
c、count:寫入資料的位元組數
返回值:
成功:返回已寫的位元組數
失敗:-1
- 關閉檔案
函式原型 int close(int fd);
引數:
a、fd:呼叫open後返回的檔案描述符
- 檔案偏移
函式原型 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: 將檔案的偏移量設定為檔案長度加offset,offset可為正或負
返回值:
成功:返回新的檔案偏移量
失敗:-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檔案無效.
動態庫和靜態庫
靜態函式庫
這類庫的名字一般是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 }
動態函式庫
這類庫的名字一般是libxxx.so;相對於靜態函式庫,動態函式庫在編譯的時候 並沒有被編譯進目的碼中,你的程式執行到相關函式時才呼叫該函式庫裡的相應函式,因此動態函式庫所產生的可執行檔案比較小。由於函式庫沒有被整合進你的程式,而是程式執行時動態的申請並呼叫,所以程式的執行環境中必須提供相應的庫。動態函式庫的改變並不影響你的程式,所以動態函式庫的升級比較方便。
生成動態庫:
其中執行動態庫有三種方法;
方法一:(如圖黃框)更改LD_LIBRARY_PATH。
方法二:拷貝.so檔案到系統共享庫路徑下:/usr/lib。
方法三:ldconfig 配置 /etc/ld.so.conf.d/ ,ldconfig更新。