檔案輸入與輸出
檔案在Linux內是什麼樣子
檔案(file) 檔案是一堆數據的有序集合 對作業系統而言,可以由「目錄系統」找到一個檔案在硬碟上的 位置 對程式而言,必須先告訴作業系統,準備「使用」哪些檔案,作 業系統會「開啟」該檔案,並給該檔案一個代碼(file descriptor),隨後該程式使用該「代碼」操作該檔案
檔案(file) 對每一個行程,每一個開啟的檔案都會有一個檔案指標 檔案指標代表目前正在對「該位置」做操作 read、write、lseek會改變檔案指標的位置 檔案指標 檔案
檔案(file) 對每一個行程,每一個開啟的檔案都會有一個檔案指標 檔案指標代表目前正在對「該位置」做操作 read、write、lseek會改變檔案指標的位置 檔案內部可能有空洞 這些空洞在邏輯意義上都是0 某些檔案系統不支援有空洞的檔案 檔案指標 檔案 空洞 空洞
為什麼檔案系統需要支援「空洞」 例如一間公司,員工編號共五碼,第1XXXX代表製造部、2XXXX代 表研發部、3XXXX代表行銷部 如果檔案系統支援「洞」,那麼可以直接使用員工編號當index, 而不需要擔心浪費磁碟空間的問題,如: 資料 洞 資料 洞 資料 洞 研發部 行銷部 製造部
用一個例子開始:mycp
mycp.c
一堆的#include <xxx.h> 問題 舉例 記得函數的名稱就好 如果忘記或者不知道include某 個.h檔案,編譯器會告訴你某 函數未定義 針對該函數使用man查詢他需 要include哪些 $man perror NAME perror - print a system error message SYNOPSIS #include <stdio.h> void perror(const char *s);
mycp.c
open的傳回值 int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode); open的傳回值是file descriptor(檔案描述子),在系統中從0開始編號 如果前妙的號碼有缺號,open會優先使用最小的號碼當file descriptor 如:系統已經使用了0, 2, 3, 4,當使用open再開啟一個檔案時,file descriptor會 是「1」 一個行程能夠開啟的檔案是有限的 可以使用getrlimit()的RLIMIT_FSIZE查看 當回傳值為-1代表發生了錯誤,例如:超出RLIMIT_FSIZE
open() open (argv [1], O_RDONLY); 第一個參數是“路徑名” 為了讀 為了寫 open (argv [1], O_RDONLY); 第一個參數是“路徑名” 第二個參數告訴OS開啟這個檔 案的目的是「只讀取」 open(argv[2], O_WRONLY | O_CREAT, S_IRUSR| S_IWUSR); 第一個參數是“路徑名” 第二個參數告訴OS這個檔案只 用來寫入(O_WRONLY),如 果檔案不存在,就建立檔案 (O_CREAT) 第三個參數代表新建立的檔案 的讀寫屬性(owner可讀寫)
mycp.c
read() ssize_t read(int fd, void *buf, size_t count); 會從fd所代表的檔案讀取「最多」count個byte到指標buf所指向的記憶體 當回傳值大於1,代表讀取了多少個byte 回傳值等於0代表讀到了EOF(檔案結尾) 回傳值-1,代表讀取發生了錯誤
write() ssize_t write(int fd, const void *buf, size_t count); 將buf指向的資料共count個byte,寫入fd所代表的檔案 傳回值代表總共寫入了多少個byte 當傳回值為-1,代表發生了錯誤
mycp.c
perror void perror(const char *s); 依照1. 依照errno印出訊息 2. 字串S 假設errno是1,perror(“the error is”)會印出「the error is: Operation not permitted」
什麼是errno errno是系統內的錯誤訊息代碼 如果呼叫一個C函數時發生了錯誤,則errno會被設定為該錯誤所 代表的號碼 所有errno對應的錯誤訊息在sys_errlist
mycp.c
close() int close(int fd); 使用完一個檔案,使用close告訴作業系統使用完畢 作業系統會依照當時的狀況(最後一個存取該檔案的行程),決 定是否釋放相關資源 成功回傳0,失敗回傳-1
lseek & file holes
hole.c
lseek() off_t lseek(int fd, off_t offset, int whence); 將檔案fd的檔案指標移動到從whence起算,偏移offset的位置 傳統上UNIX支援的whence有三種選擇 SEEK_SET:絕對位置 SEEK_CUR:從現在位置起算 SEEK_END:從結束位置起算 傳回值為從檔案開始的偏移值
hole.c 因此hole.c會產生一個名為 myHole的檔案,在開始位置寫 入1,往後移動10000 byte在寫 入2,往後移動10000 byte在寫 入3 $ls myHole -lhs 12K -rw------- 1 shiwulo shiwulo 196K Jan 13 04:24 myHole /*檔案大小為196K,佔據磁碟空間12K*/
使用mycp複製myhole $ ./mycp myHole myHole2 $ ls myH* -lhs 12K -rw------- 1 shiwulo shiwulo 196K Jan 13 04:24 myHole 196K -rw------- 1 shiwulo shiwulo 196K Jan 13 04:31 myHole2 /*檔案大小都是196K,但是myHole2佔據磁碟空間196K而非12K*/ $cmp myHole myHole2 /*使用cmp比較二者無差異*/
myHole內部構造 10000個0 10000個0 1 0000…0000 2 3
進階版的mycp.c,mycp2.c(第一部分) 一開始要宣告_GNU_SOURCE才可以使用進階版的lseek()
man lseek
SEEK_HOLE & SEEK_DATA 在新版的UNIX提供這二個新的選項,但必須手動打開,即#define _GNU_SOURCE 洞的最前面 資料的最前面 1 0000…0000 2 3
進階版的mycp.c,mycp2.c(第二部分) 取得每個資料區段的位置及大小 移動到該區段的開頭位置 進行該區段的複製
結果 $./mycp myHole myHole2 $ ./mycp2 myHole myHole3 $ ls myH* -lhs 12K -rw------- 1 shiwulo shiwulo 196K Jan 13 04:24 myHole 196K -rw------- 1 shiwulo shiwulo 196K Jan 13 05:08 myHole2 12K -rw------- 1 shiwulo shiwulo 196K Jan 13 05:09 myHole3
mycp2有錯誤,試著修改這個錯誤
協調式鎖定檔案flock
lock.c
執行結果 $ ./lock myHole e fd = 3 is opened end 先執行 後執行 $ ./lock myHole e fd = 3 is opened end $ ./lock myHole e fd = 3 is opened /*被鎖住了,除非另外一個行程unlock或者結束*/
flock() int flock(int fd, int operation); LOCK_SH:分享鎖,除了互斥鎖,可以多個人同時編譯 LOCK_EX:互斥鎖,只可以這個行程進行編譯 LOCK_UN:解開這個鎖 請注意,如果另外一個行程並未使用flock,那麼另一個行程就不需要遵照這些「鎖」
強制鎖
強制鎖 在Solaris, HP-UX, and Linux上可以使用set-grup-id,讓一個檔案只 可以由一個行程開啟 具體的做法是在chmod前加上2,例如: chmod 2644 test
確保寫入 sync & fsync & fdatasync
三個類似的函數 void sync(void); int fsync(int fd); int fdatasync(int fd); 將所有的資料(包含meta-data)寫回磁碟 int fsync(int fd); 將fd代表的檔案的所有的資料(包含meta-data)寫回磁碟 int fdatasync(int fd); 將fd代表的檔案的所有的資料(「不」包含meta-data)寫回磁 碟
sync.c int main() { int fd; int num; fd = open("./hello1",O_WRONLY | O_CREAT, 0644); for(num=0; num <=100000; num++) { write(fd, "1234", sizeof("1234")); fsync(fd); if (num%10000==1) { write(1, "*", sizeof("*")); fsync(1); } return 0;
datasync.c int main() { int fd; int num; fd = open("./hello3",O_WRONLY | O_CREAT, 0644); for(num=0; num <=100000; num++) { write(fd, "1234", sizeof("1234")); fdatasync(fd); if (num%10000==1) { write(1, "*", sizeof("*")); fsync(1); } return 0;
nsync.c int main() { int fd; int num; fd = open("./hello2",O_WRONLY | O_CREAT, 0644); for(num=0; num <=100000; num++) { write(fd, "1234", sizeof("1234")); if (num%10000==1) { write(1, "*", sizeof("*")); fsync(1); } return 0;
sync.c的執行結果 $ time ./sync ********** real 0m21.215s user 0m0.136s sys 0m5.272s
比較 sync fdatasync no sync real 0m21.215s 0m17.545s 0m0.076s user sys 0m5.272s 0m3.980s 0m0.068s