文件概述
Linux中,一切皆文件。文件為操作系統服務和設備提供了一個簡單而一致的接口。這意味着程序完全可以像使用文件那樣使用磁盤文件、串行口、打印機和其他設備。
也就是說,大多數情況下,你只需要使用5個函數: open、close、read、write和ioctl。 例外的情況: 目錄的讀寫,網絡連接等特殊文件
目錄
文件通常由兩部分組成: 內容 + 屬性,即管理信息,包括文件的創建修改日期和訪問權限等。屬性均保存在 inode 節點中。inode - "索引節點",儲存文件的元信息,比如
文件的創建者、文件的創建日期、文件的長度和文件在磁盤上存放的位置等等。每個inode都有一個號碼,操作系統用inode號碼來識別不同的文件。ls -i 查看inode 號。
目錄是用於保存其他文件的節點號和名字的文件,每個數據項為指向文件節點的鏈接。如下圖:
當文件鏈接數變為零,意味文件刪除,磁盤空間變成可用空間。
文件和設備
三個重要的設備文件:
/dev/console - 系統控制台。
/dev/tty - 訪問不同的物理設備。
/dev/null - 空設備,向所有寫這個設備的輸出都將被丟棄。
設備驅動程序:
操作系統的核心部分,即內核,是由一組設備驅動程序組成。他們是一組對系統硬件進行控制的底層接口,為了向用戶提供一個一致的接口,其封裝了所有與硬件相關的特性。
硬件特有功能可通過ioctl(用於I/O控制)系統調用來提供。
/dev 目錄下的設備文件都可以被打開、讀、寫和關閉。
1)open : 打開文件或設備。
2)read : 從打開的文件或設備里讀數據。
3)write: 向文件或設備寫數據。
4)close: 關閉文件或設備。
5) ioctl: 把控制信息傳遞給設備驅動程序,每個驅動都由自己的一組 ioctl 命令。
庫函數
針對輸入輸出操作直接使用底層系統調用效率非常低,原因由如下兩點。
1)使用系統調用會影響系統性能。
2)硬件會對底層系統調用一次所讀寫的數據塊大小做限制。磁盤:至少一個扇區512
字節,磁帶,一次 10K
庫函數給設備和磁盤文件提供了更高層的接口,即標准函數庫。使用它你可以高效讀寫任意長度的數據塊,庫函數則在數據滿足條件后再安排系統調用。這樣極大降低了開銷。
注:庫函數的文檔一般放在手冊的第三頁,每個庫函數有其對應的頭文件。
底層文件訪問
運行中的程序稱為進程,每個進程都有與之關聯的文件描述符。
文件描述符 - 一些小值整數,通過他們訪問打開的文件或設備。開始運行會有三個文件描述符:
1)0: 標准輸入 STDIN_FILENO
2)1: 標准輸出 STDOUT_FILENO
3)2: 標准錯誤 STDERR_FILENO
文件描述符的變化范圍是:0~OPEN_MAX-1 (可通過ulmit -a 查看)
write系統調用
作用:把緩沖區buf 的前count 個字節寫入與文件描述符 fd 相關聯的文件中。
#include <unistd.h> size_t write(int fd,const void *buf, size_t count);
描述符出錯,文件達到進程限制最大值或設備驅動程序對數據塊 長度比較敏感,該返回值可能會小於count,這並不是一個錯誤。 0 表示未寫入數據; -1 表示出錯,錯誤代號在全局變量 errno里。
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h>
int main() { const char * output = "Hello World\n"; const char * errstr = "A Write error has occurred on file descriptior !\n"; if(write(1,output,strlen(output))!=strlen(output)); write(2,errstr,strlen(errstr)); exit(0); }
read系統調用
作用:作用:從與文件描述符 fd 相關聯的文件中讀取前count 個字節到緩沖區buf 中。
#include <unistd.h> size_t write(int fd,const void *buf, size_t count);
它返回實際讀入的字節數,這可能會小於請求的字節數。 0 表示未讀入任何數據,已到達了文件尾部。 -1 表示出錯,錯誤代號在全局變量 errno里。
#include <unistd.h> #include <stdlib.h>
int main() { char buffer[128]; int nread = read(0,buffer,128); if(nread == -1) write(2,"A read error has occurred\n",26); if(write(1,buffer,nread)!= nread) write(2,"A write error has occurred\n",27); }
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);
open 建立一條到文件或設備的訪問路徑。成功后可獲得供 read、write和其他系統調用使用的唯一的文件描述符。此文件描述符進程唯一;如果兩個程序打開同一個文件,那么,他們分別得到不同的文件描述符,並可以單獨對文件進行獨立的操作。我們可以通過文件鎖(O_EXCL或FCNTL)功能來解決這個問題。
參數說明:
pathname - 指示准備打開的文件或設備的名字;
flags - 用於指定打開文件所采取的動作;
mode - 用於指定創建文件的權限,O_CREATE 才使用。
flags 參數通過必需文件訪問模式 與 其他可選模式相結合的方式來指定。 首先必須指定如下文件訪問模式之一:
模 式 |
說 明 |
O_RDONLY |
以只讀方式打開 |
O_WRONLY |
以只寫方式打開 |
O_RDWR |
以讀寫方式打開 |
可選模式組合:
1) O_APPEND: 把寫入數據最佳在文件的末尾。
2) O_TRUNC: 打開文件時把文件長度設置為零,丟棄已有的內容。
3) O_CREAT: 如果需要,就按參數mode 中給出的訪問模式創建文件。
4) O_EXCL: 與O_CREAT一起使用,確保創建文件的 原子操作。如果文件存在,創建 將失敗
訪問權限的初始:
單個權限設置 : S_I R或W或X USR或GRP或OTH
讀寫執行全權限 : S_I RWX U或G或O
如: S_IRUSR 讀權限 文件屬性
S_IRWXO 讀寫執行 其他用戶
最終權限生成還和進程設置的umask權限掩碼有關,執行umask命令或者函數 可以改變權限。
新文件描述符總使用未用文件描述符的最小值。如果一個文件符被關閉再次調用open ,其馬上會被重用。
!Posix 規定了一個 creat 調用: 等同於 O_CREAT|O_WRONLY|O_TRUNC
close系統調用
作用:終止文件描述符fd 和對應文件(文件或設備)的關聯。文件描述符被釋放並能夠重新使用。close 調用成功返回0,出錯返回 -1。
#include <unistd.h>
int close(int fd);
返回值: 檢查 close 調用的返回值很重要。可以檢測某些寫操作錯誤!
ioctl系統調用
ioctl提供了一個用於控制設備及其描述行為和配置底層的服務的接口。終端文件描述符、套接字都可以定義他們的ioctl,具體需要參考特定設備的手冊。
#include <sys/ioctl.h>
int ioctl(int d, int request, ...);
dup和dup2的系統調用
作用:提供了一種復制文件描述符的方法,是我們通過兩個或者更多不同的描述符來訪問同一個文件,主要用於多個進程間進行管道通信。
#include <unistd.h>
int dup(int oldfd); int dup2(int oldfd, int newfd);
lseek系統調用
作用:
作用:lseek 對文件描述符 fd 的讀寫指針進行設置。也就是說,設置文件的下一個讀寫位置。可根據絕對位置和相對位置(當前位置或文件尾部)進行設置。
#include <sys/types.h> #include <unistd.h> off_t lseek(int fd, off_t offset, int whence);
offset 參數用來指定位置,而whence 參數定義該偏移值的用法。Whence 可取值如下:
1)SEEK_SET: offset 是一個絕對位置。
2)SEEK_CUR: offset 是相對於當前位置的一個相對位置。
3)SEEK_END: offset 是相對於文件尾的一個相對位置。
lseek 返回從文件頭到文件指針被設置處的字節偏移值,失敗時返回-1.
fstat、stat和lstat系統調用
作用:獲取文件的狀態信息,該信息將會寫入一個buf中,buf的地址會以參數的形式傳遞給fstat
#include <sys/types.h> #include <sys/stat.h> #include <unistd.h>
int stat(const char *path, struct stat *buf); int fstat(int fd, struct stat *buf); int lstat(const char *path, struct stat *buf);
stat 和 lstat 均通過文件名查詢狀態信息,當文件名是符號鏈接時,lstat返回的時符號鏈接本身的信息,而stat 返回的時改鏈接指向的文件的信息。
struct stat { dev_t st_dev; /* ID of device containing file */ ino_t st_ino; /* inode number */ mode_t st_mode; /* protection */ nlink_t st_nlink; /* number of hard links */ uid_t st_uid; /* user ID of owner */ gid_t st_gid; /* group ID of owner */ dev_t st_rdev; /* device ID (if special file) */ off_t st_size; /* total size, in bytes */ blksize_t st_blksize; /* blocksize for filesystem I/O */ blkcnt_t st_blocks; /* number of 512B blocks allocated */ time_t st_atime; /* time of last access */ time_t st_mtime; /* time of last modification */ time_t st_ctime; /* time of last status change */ };
這里要特別提到的是,以上 st_mode 標志有一系列相關的宏,定義見 sys/stat.h 中
,可用來測試文件類型,如:
錯誤處理
許多系統調用和函數都會因為各種各樣的原因失敗。他們失敗時設置外部變量errno 來知名失敗原因。許多不同函數庫都把這個變量用做報告錯誤的標准方法。
注意: 程序必須在函數報告出錯 之后立刻檢查errno 變量,因為它可能馬上就被下一個函數調用所覆蓋,即使下一個函數沒有出錯,也可能會覆蓋這個變量。
常用錯誤代碼的取值和含義如下:
l EPERM: 操作不允許
l ENOENT: 文件或目錄不存在。
l EINTR: 系統調用被中斷。
l EAGAIN: 重試,下次有可能成功!
l EBADF: 文件描述符失效或本身無效
l EIO: I/O錯誤。
l EBUSY: 設備或資源忙。
l EEXIST: 文件存在。
l EINVL: 無效參數。
l EMFILE: 打開的文件過多。
l ENODEV: 設備不存在。
l EISDIR: 是一個目錄。
l ENOTDIR: 不是一個目錄。
兩個有效函數可報告出現的錯誤: strerror 和 perror。
strerror 函數
作用:把錯誤代號映射成一個字符串,該字符串對發生的錯誤類型進行說明。
#include <string.h>
char *strerror(int errnum); int strerror_r(int errnum, char *buf, size_t buflen);
perror函數
作用:perror 函數也把errno 變量中報告的當前錯誤映射成一個字符串,並把它輸出到標准錯誤輸出流。
perror(“test”);
結果:
Test: Too many open files