一、檔案系統
檔案系統的作用就是將檔案組織成包含目錄、串連等存在於物理塊裝置中的邏輯階層。它不關心底層的物理塊裝置的結構,當對檔案進行操作時,由塊裝置驅動程式將對某個特定塊的請求映射到正確的裝置上去。
Ext3檔案系統將整個檔案系統表示成一個單一的層次樹,將檔案儲存在大小相同的資料區塊中。資料區塊越小,磁碟空間利用率越高,但由於核心需要進行的I/O操作次數增多,運行速度有所降低;資料區塊越大,速度有所提高,但磁碟利用率又降低了。
Ext3通過一個inode結構來描述檔案,inode結構中包含檔案的存取許可權、修改時間、檔案類型等資訊。所有inode都儲存在inode表中,目錄檔案僅僅是儲存指向inode表項索引號的一個鏈表,前兩個入口總是.和..,表示目前的目錄和父目錄。
二、檔案描述符和流
檔案描述符是開啟或建立檔案時Linux核心分配的一個非負整數,用來跟蹤開啟的檔案。有3個檔案描述符有特定意義:0-標準輸入,1-標準輸出,2-標準錯誤,在POSIX標準中定義了3個常量來表示這3個檔案描述符:STDIN_FILENO,STDOUT_FILENO,STDERR_FILENO。要使用這些常量,必須包含標頭檔<unistd.h>
流是標準C語言I/O庫提出來的概念,隱藏了檔案描述符,以更抽象的概念代替。用標準I/O庫函數開啟或建立檔案時,返回一個FILE結構的指標,該結構包含了檔案描述符、緩衝區地址、緩衝區大小等資訊。有3個預定義的流於3個預定義的檔案描述符對應:stdin,stdout,stderr,要使用這3個流必須包含標頭檔<stdio.h>
三、檔案操作
檔案操作分為系統調用和標準C庫函數兩種。系統調用就是作業系統提供的一些調用介面,因此與作業系統相關,而標準C庫是與作業系統無關的。這裡主要介紹系統調用。
1.開啟/關閉檔案
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flag);
int open(const char *pathname, int flag, mode_t mode);
int creat(const char *pathname, mode_t mode);
int close(int fd);
其中flag參數為以下值:
O_RDONLY:唯讀
O_WRONLY:唯寫
O_RDWR:讀寫
O_APPEND:追加
O_CREAT:檔案不存在則建立,需要制定mode參數
O_EXCL:如果指定了O_CREAT並且檔案已存在則返回錯誤
O_TRUNC:如果檔案存在並且以唯寫方式開啟,則把檔案內容清空
O_NDELAY:以非阻塞方式開啟。正常檔案位於本地磁碟,完成讀寫操作的時間是確定的,因此總是阻塞的。但是當進程對某些特殊檔案如FIFO進行操作時,資料到達的時間是不確定的,因此讀操作需要立即返回而不是阻塞。
O_SYNC:將緩衝區的資料同步到磁碟上
O_NOFOLLOW:如果pathname是一個符號串連檔案,open()函數返回一個錯誤
O_DIRECTORY:如果pathname不是一個目錄,open()函數返回一個錯誤
其中mode只有在指定了O_CREAT標誌時才需要,指定新建立的檔案的存取許可權(mode & ~umask)
S_IRUSR:擁有者讀許可權
S_IWUSR:擁有者寫入權限
S_IXUSR:擁有者執行許可權
S_IRWXU:擁有者讀、寫、執行許可權
S_IRGRP:組讀許可權
S_IWGRP:組寫入權限
S_IXGRP:組執行許可權
S_IRWXG:組讀、寫、執行許可權
S_IROTH:其他使用者讀許可權
S_IWOTH:其他使用者寫入權限
S_IXOTH:其他使用者執行許可權
S_IRWXO:其他使用者讀、寫、執行許可權
2.讀/寫檔案
#include <unistd.h>
size_t write(int fildes, const void *buf, size_t nbytes);
size_t read(int fildes, void *buf, size_t nbytes);
3.檔案定位
#include <unistd.h>
#include <sys/types.h>
off_t lseek(int fildes, off_t offset, int whence);
其中whence可以有以下3個值,與標準C庫是一致的:
SEEK_SET:相對於檔案開頭
SEEK_CUR:相對於當前位置
SEEK_END:相對於檔案結尾
4.查詢檔案狀態資訊
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
int fstat(int fildes, struct stat *buf);
int stat(const char *path, struct stat *buf);
int lstat(const char *path, struct stat *buf);
其中fstat()與stat()功能是相同的,只是參數不同。當檔案是一個符號連結時,lstat()將返回這個連結的資訊,而stat()將返回這個連結指向的檔案的資訊。
stat是一個結構體,作為參數傳遞,包含以下成員:
st_mode 檔案許可權以及檔案類型資訊
st_ino 檔案I節點資訊
st_dev 檔案所在的裝置資訊
st_uid 檔案所有者的使用者標識
st_gid 檔案所有者的組標識
st_atime 上一次訪問的時間
st_ctime 上一次對許可權,所有者,組,或是內容的修改時間
st_mtime 上一次對內容的修改時間
st_nlink 到這個檔案的永久連結數目
其中st_mode成員包含了檔案許可權和檔案類型資訊。
檔案許可權和open()函數中的mode參數的取值相同
檔案類型可以取以下值:
S_IFBLK:實體是一個特殊的塊裝置
S_IFDIR:實體是一個目錄
S_IFCHR:實體是一個特殊的字元裝置
S_IFIFO:實體是一個IFIFO(命令管道)
S_IFREG:實體是一個常規檔案
S_IFLNK:實體是一個符號連結
要測試這些標誌,首先要將它們取出來,就需要用到掩碼(mask):
S_IFMT:檔案類型
S_IRWXU:使用者讀/寫/執行許可權
S_IRWXG:組讀/寫/執行許可權
S_IRWXO:其他使用者讀/寫/執行許可權
這些掩碼的作用就是把其他位都屏蔽掉,只留下要測試的標誌位。
除了通過測試檔案類型標誌來獲得檔案類型,還有一些宏可以協助我們來進行判斷,使用也非常方便,把st_mode作為參數傳進去就可以了:
S_ISBLK():為特殊的塊檔案測試
S_ISCHR():為特殊的字元檔案測試
S_ISDIR():為目錄測試
S_ISFIFO():為FIFO測試
S_ISREG():為常規檔案測試
S_ISLNK():為符號連結測試
舉例:
struct stat statbuf;
mode_t modes;
stat("filename", &statbuf);
modes = statbuf.st_mode;
用宏來測試:
if(!S_ISDIR(modes))//判斷是否不是目錄
用標誌位來測試:
if((modes & S_IRWXU) & S_IXUSR)//判斷是否有使用者執行許可權
5.檔案描述符與流之間的轉換
#include <stdio.h>
FILE *fdopen(int filedes, const char *mode);
mode參數與fopen()函數相同
6.進階主題
#include <fcntl.h>
int fcntl(int fildes, int cmd);
int fcntl(int fildes, int cmd, long arg);
fcntl()函數可以用來查詢和改變一個已被開啟的檔案的屬性。
cmd參數可以取以下值:
F_DUPFD:複製檔案描述符,與dup()/dup2()函數功能相同
F_GETFD/F_SETFD:查詢/設定檔案描述符標誌,通常只是FD_CLOEXEC標誌
F_GETFL/F_SETFL:查詢/設定檔案狀態標誌,與open()函數的第二個參數取值相同
注意:由於O_RDONLY、O_WRONLY、O_RDWR標誌並不是各佔一位(O_RDWR=O_RDONLY|O_WRONLY),如果要測試檔案的讀寫權限,需要先用O_ACCMODE與傳回值相與,然後再與這3個標誌進行比較
另外,檔案的讀寫權限是不能被改變的,只能改變O_APPEND、O_NONBLOCK、O_SYNC、O_ASYNC這4個標誌
#include <unistd.h>
#include <sys/ioctl.h>
int ioctl(int filedes, int request, ...);
凡是其他函數不能完成的I/O操作一般都由它來完成,request參數指定要對檔案進行何種操作,操作不同,第三個參數也不同:
DIOxxx:磁碟I/O
FIOxxx:檔案I/O
MTIOxxx:磁帶I/O
SIOxxx:通訊端I/O
TIOxxx:終端I/O
7.緩衝與無緩衝
(1)完全緩衝。標準C庫函數對檔案的操作一般是完全緩衝。只有當I/O緩衝區被填滿(寫操作)或者為空白(讀操作)時,核心才會進行真正的I/O操作。核心一般在對第一個流進行第一次I/O操作時調用malloc()一類的函數分配緩衝區。
(2)行緩衝。當檔案實際是一個終端時,一般使用行緩衝。採用行緩衝後,只有遇到分行符號時才進行真正的I/O操作。注意的是,由於I/O庫中的緩衝區大小有限,因此當一行很長時有可能在沒有遇到分行符號時就已經進行I/O操作了。
(3)無緩衝。一般標準錯誤都是無緩衝的,輸出的任何資訊都會立即反應到檔案中。
設定流的緩衝類型:
#include <stdio.h>
void setbuf(FILE *fp, char *buf);
void setvbuf(FILE *fp, char *buf, int mode, size_t size);
把setbuf()函數中的buf參數設定為NULL,就是無緩衝類型。要允許緩衝,則buf必須指向一個長度至少為BUFSIZE(定義在<stdio.h>中)的緩衝區。如果該流是一個終端,則被設定為行緩衝,否則為完全緩衝。
把setvbuf()中的mode參數設定為_IONBF,則為無緩衝類型,buf和size參數被忽略。把mode參數設定為_IOFBF(完全緩衝)或_IOLBF(行緩衝)時,size指定buf所指向的緩衝區的大小。如果buf是NULL則I/O庫函數會根據stat結構中的st_blksize成員自動分配一個緩衝區。如果系統無法決定該值,如該檔案是一個裝置或管道時,緩衝區大小就是BUFSIZE。
強制輸出緩衝區內容:int fflush(FILE *fp); 如果fp為NULL,則flush所有被開啟的流。
8.錯誤處理
出錯時通過設定全域變數errno的值來指示錯誤原因
將錯誤號碼映射為一個字串
#include <string.h>
char *strerror(int errnum);
將錯誤資訊輸出到標準錯誤流,以“s: ”為首碼
#include <stdio.h>
void perror(const char *s);