- 所有執(zhí)行I/O操作的系統(tǒng)調(diào)用都以文件描述符(一個(gè)非負(fù)整數(shù))來(lái)指代打開的文件。包括pipe涵紊,F(xiàn)IFO傍妒,socket,終端摸柄,設(shè)備和普通文件颤练。
- 針對(duì)每一個(gè)進(jìn)程,內(nèi)核都會(huì)為其維護(hù)一個(gè)文件描述符表塘幅,每一個(gè)表項(xiàng)都是對(duì)打開文件句柄的引用+文件描述符標(biāo)志二元組(不是文件狀態(tài)標(biāo)志昔案,且目前為止只定義了一個(gè)標(biāo)識(shí)就是close-on-exec)
- 通過(guò)文件描述符在文件描述符表中找到相對(duì)應(yīng)的引用,然后可以找到對(duì)應(yīng)的文件表項(xiàng)电媳;在文件表中記錄了以下信息
1. 當(dāng)前文件偏移量
2. 文件狀態(tài)標(biāo)志
3. 文件訪問(wèn)模式
4. 信號(hào)驅(qū)動(dòng)IO相關(guān)
5. inode引用
6. v節(jié)點(diǎn)引用
1. 文件訪問(wèn)權(quán)限和文件類型
2. 文件鎖列表
3. 文件的各種屬性(包括文件大小以及相關(guān)時(shí)間屬性)
4. 數(shù)據(jù)塊編號(hào)
- v節(jié)點(diǎn)是用來(lái)支持虛擬文件系統(tǒng)的(VFS)踏揣,能夠?yàn)椴煌奈募到y(tǒng)提供統(tǒng)一的API,v節(jié)點(diǎn)中保存了各種底層函數(shù)調(diào)用的函數(shù)指針匾乓;此外捞稿,linux上沒(méi)有特定的v節(jié)點(diǎn)的概念,但是本質(zhì)上都是一樣的拼缝,在linux下只有inode節(jié)點(diǎn)娱局,所以相應(yīng)的函數(shù)指針也都放在了i節(jié)點(diǎn)中
- 兩個(gè)不同的文件描述符,指向同一個(gè)文件表項(xiàng)咧七,那么會(huì)共享同一個(gè)文件偏移量衰齐,這種情況通常是調(diào)用fork以后出現(xiàn);通過(guò)dup或者fcntl復(fù)制文件描述符可以使兩個(gè)fd指向相同的文件表項(xiàng)继阻;通過(guò)兩次open調(diào)用會(huì)使得兩個(gè)不同的文件描述符指向不同的文件表項(xiàng)耻涛,但文件表項(xiàng)中的inode是相同的
- 在打開文件時(shí)指定O_NONBLOCK或者調(diào)用fcntl設(shè)置非阻塞后,會(huì)出現(xiàn)以下兩類情況
1. 若open調(diào)用未能立即打開文件瘟檩,則返回錯(cuò)誤而非陷入阻塞抹缕;但對(duì)于FIFO文件例外
2. 調(diào)用open成功后,對(duì)于后續(xù)的IO操作也都是非阻塞的墨辛,若IO操作不能立即完成卓研,只能傳輸部分?jǐn)?shù)據(jù),調(diào)用失敗睹簇,返回EAGAIN或者EWOULDBLCOK錯(cuò)誤奏赘,兩者等價(jià)
open
- open函數(shù)調(diào)用的原型為
int open(const char* pathname,int flags,.../*mode_t mode*/)
,如果pathname是一個(gè)符號(hào)鏈接(軟鏈接)太惠,那么會(huì)對(duì)其解引用志珍,即返回指向其實(shí)際文件的文件描述符。當(dāng)flags沒(méi)有指定O_CREAT標(biāo)志垛叨,則忽略mode參數(shù)。如果open調(diào)用成功,返回當(dāng)前進(jìn)程文件描述符表中最小的未用的文件描述符嗽元;如果失敗敛纲,返回-1并設(shè)置errno
- flags表示文件狀態(tài)標(biāo)志,常見的關(guān)于文件讀寫權(quán)限的O_RDONLY\O_WRONLY\O_RDWR(三者互斥)
- O_ASYNC標(biāo)志表示對(duì)于返回的fd所指向的文件上有IO操作時(shí)剂癌,系統(tǒng)會(huì)產(chǎn)生一個(gè)信號(hào)通知應(yīng)用程序淤翔,這僅對(duì)一些特殊的文件有效果,比如FIFO文件佩谷,socket文件以及終端設(shè)備文件旁壮。另外在linux中,需要調(diào)用fcntl的F_SETFL來(lái)設(shè)置此標(biāo)志才起作用
- O_NONBLOCK以非阻塞模式打開文件
- O_TRUNC表示當(dāng)文件存在且為普通文件谐檀,將文件長(zhǎng)度截?cái)酁?
- O_CLOEXEC為文件描述符啟用close_on_exec抡谐,即調(diào)用exec函數(shù)族時(shí),子進(jìn)程不繼承父進(jìn)程打開的文件描述符桐猬;并且調(diào)用open并設(shè)置此標(biāo)志為原子操作麦撵,另外可以調(diào)用fcntl的F_SETFD設(shè)置,但是此方法非線程安全
- 當(dāng)需要判斷一個(gè)文件是否存在時(shí)溃肪,應(yīng)當(dāng)使用O_CREAT和O_EXCL標(biāo)志來(lái)一次性調(diào)用open來(lái)判斷(作為原子操作)
- 當(dāng)有多個(gè)進(jìn)程向同一個(gè)文件尾部寫入數(shù)據(jù)時(shí)免胃,在沒(méi)有同步的情況下如果使用lseek加write的方式,會(huì)出現(xiàn)競(jìng)爭(zhēng)狀態(tài)進(jìn)而導(dǎo)致寫入錯(cuò)誤惫撰;正確的做法是使用O_APPEND標(biāo)志的open羔沙,然后再write
read
- read函數(shù)原型為
ssize_t read(int fd,void* buffer,size_t count)
,返回值為一個(gè)有符號(hào)整形數(shù)厨钻,正常讀返回讀到的字節(jié)數(shù)扼雏,如果讀到文件末尾返回0(EOF),如果調(diào)用出錯(cuò)返回-1莉撇;count表示最大讀取的字節(jié)數(shù)
- 如果讀寫位置位于文件尾部或者讀取的文件為終端設(shè)備呢蛤,管道,socket和FIFO棍郎,會(huì)出現(xiàn)讀取的字節(jié)數(shù)小于請(qǐng)求字節(jié)數(shù)的情況其障;由于表示字符串終止的空字符的影響,緩沖區(qū)的大小至少要比最大讀取字節(jié)數(shù)多一個(gè)字節(jié)
write
- write函數(shù)原型為
ssize_t write(int fd,void* buffer,size_t count)
write調(diào)用可能會(huì)出現(xiàn)實(shí)際寫入的字節(jié)小于指定的從buf寫入文件的字節(jié)數(shù)涂佃,對(duì)于磁盤文件來(lái)說(shuō)励翼,可能是因?yàn)榇疟P已經(jīng)滿了
- write函數(shù)的參數(shù)與read基本一致,count表示要寫入的字節(jié)數(shù)辜荠,成功調(diào)用返回寫入的字節(jié)數(shù)汽抚,調(diào)用失敗返回-1
lseek
- lseek函數(shù)原型為
off_t lseek(int fd,off_t offset,int whence)
- offset表示偏移量,是一個(gè)有符號(hào)整數(shù)伯病;分別表示正/負(fù)偏移
- whence表示偏移地址造烁,當(dāng)設(shè)置為SEEK_SET時(shí),偏移量必須為正數(shù)
- lseek只是修改了fd對(duì)應(yīng)文件表項(xiàng)中的文件偏移量的值,并沒(méi)有去訪問(wèn)物理設(shè)備
- 不允許將lseek應(yīng)用于管道惭蟋,socket苗桂,終端以及FIFO,如果這樣調(diào)用了告组,調(diào)用將失敗并將errno置為ESPIPE
- 如果文件的文件偏移量已經(jīng)超過(guò)了文件末尾煤伟,那么內(nèi)核將以空字節(jié)去填充,不占用實(shí)際磁盤空間木缝,這稱為文件空洞便锨。直到向文件空洞中去寫入數(shù)據(jù),內(nèi)核才會(huì)真正去方位磁盤設(shè)備我碟,申請(qǐng)磁盤空間
fcntl
- 函數(shù)原型為
int fcntl(int fd,int cmd,......)
- 通過(guò)將cmd設(shè)置為F_GETFL可以獲得fd所指向文件的文件狀態(tài)標(biāo)志放案,進(jìn)而可以通過(guò)將其和各種狀態(tài)標(biāo)志做&操作,判斷是否設(shè)置了該狀態(tài)標(biāo)志
- 對(duì)于文件的訪問(wèn)權(quán)限來(lái)說(shuō)怎囚,在判斷的時(shí)候比較特殊卿叽;需要先將返回值與O_ACCMODE做&操作,然后才能進(jìn)行判斷恳守,如下所示
int flags = fcntl(fd,F_GETFL);
accessmode = flags & O_ACCMODE;
switch(accessmode)
{
case O_WRONLY:{...}
case O_RDONLY:{...}
case O_RDWR:{...}
}
- 相反考婴,可以使用F_SETFL來(lái)設(shè)置相應(yīng)fd的文件狀態(tài)標(biāo)志;一般催烘,都是先調(diào)用F_GETFL獲取舊 的標(biāo)志位沥阱,然后在舊的標(biāo)志位的基礎(chǔ)上對(duì)其進(jìn)行修改;可以修改的狀態(tài)標(biāo)志為O_APPEND\O_NONBLOCK\O_NOATIME\O_ASYNC\O_DIRECT伊群。不能通過(guò)F_SETFL修改文件的訪問(wèn)模式(O_RDONLY\O_WRONLY\O_RDWR)
- 可以通過(guò)F_DUPFD復(fù)制文件描述符考杉,格式為
newfd = fcntl(oldfd,F_DUPFD,startfd)
,其中新創(chuàng)建的文件描述符將使用大于startfd的最小的且未用的文件描述符舰始;如果想要為新fd設(shè)置close-on-exec標(biāo)志崇棠,就附加F_DUPFD_CLOEXEC命令
dup
- dup函數(shù)族中一共有三個(gè)函數(shù),分別為dup,dup2,dup3丸卷。三個(gè)函數(shù)的原型如下
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd,int newfd)
int dup3(int oldfd,int newfd,int flags)
return new fd on success,or -1 on error
- dup僅僅復(fù)制一個(gè)已經(jīng)打開的文件描述符枕稀,并返回新的文件描述符;dup2創(chuàng)建的新的fd是由newfd參數(shù)所指定的谜嫉,如果newfd所指向的fd已經(jīng)打開萎坷,那么dup2首先會(huì)將其關(guān)閉然后再完成復(fù)制;
- dup和dup2所創(chuàng)建的文件描述符的fd標(biāo)志(close-on-exec)都是處于關(guān)閉狀態(tài)沐兰,而dup3在dup2的基礎(chǔ)上可以控制該標(biāo)志的開關(guān)
pread和pwrite
#include <unistd.h>
ssize_t pread(int fd,void* buf,size_t count,off_t offset);
return nums of bytes read,0 on EOF,or -1 on error
ssize_t pwrite(int fd,const void* buf,size_t count,off_t offset);
return nums of bytes written,or -1 on error
- 為了避免競(jìng)爭(zhēng)狀態(tài)的產(chǎn)生哆档,保證多進(jìn)程或者多線程之間能夠正確的在指定偏移處進(jìn)行IO操作,可以使用這一組系統(tǒng)調(diào)用住闯;在單線程環(huán)境下瓜浸,pread調(diào)用等同于下面的調(diào)用過(guò)程(pwrite同理)澳淑。先保存了當(dāng)前的文件偏移,然后對(duì)文件偏移進(jìn)行修改并在修改后的偏移處進(jìn)行read斟叼,最后讀取完以后將偏移恢復(fù)
off_t curoffset = lseek(fd,0,SEEK_CUR);
lseek(fd,offset,SEEK_SET);
read(fd,buf,count);
lseek(fd,curoffset,SEEK_SET);
readv和writev
- readv和writev都具有原子性偶惠,不被其他執(zhí)行路徑干擾(進(jìn)程或線程)
#include <sys/uio.h>
ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
- 以上的系統(tǒng)調(diào)用可以一次投遞多塊緩沖區(qū)以供讀寫,iov是指向緩沖區(qū)結(jié)構(gòu)數(shù)組的指針朗涩,iovcnt是數(shù)組中的緩沖區(qū)個(gè)數(shù);struct iovec結(jié)構(gòu)是一個(gè)用來(lái)描述緩沖區(qū)的結(jié)構(gòu)體绑改,定義如下
struct iovec{
void* iov_base;
size_t iov_len;
};
- readv實(shí)現(xiàn)了分散輸入的功能谢床,從fd指定的文件中連續(xù)讀取指定的字節(jié)數(shù),然后按照順序分別放入緩沖區(qū)數(shù)組中(從iov[0]開始)readv函數(shù)應(yīng)用示例如下:
1 #include "../sysHeader.h"
2
3 int main(void)
4 {
5 struct iovec iov[3];
6
7 int fd = open("temp",O_RDWR);
8
9 if(fd == -1)
10 {
11 perror("open");
12 exit(1);
13 }
14
15 char v1[64];
16 char v2[64];
17 char v3[128];
18
19 iov[0].iov_base = v1;
20 iov[0].iov_len = 64;
21
22 iov[1].iov_base = v2;
23 iov[1].iov_len = 64;
24
25 iov[2].iov_base = v3;
26 iov[2].iov_len = 128;
27
28 int totalreq = 256;
29
30 int res = readv(fd,iov,3);
31
32 if(res < totalreq)
33 printf("read fewer bytes than requered\n");
34 else
35 printf("v1 = %s\nv2 = %s\nv3 = %s\n",v1,v2,v3);
36
37 return 0;
38 }
truncate和ftruncate
#include <unistd.h>
#include <sys/types.h>
int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);
return 0 on success,or -1 on error
- 若文件當(dāng)前長(zhǎng)度小于length厘线,則在文件尾部添加空字節(jié)以增長(zhǎng)到指定length识腿;若文件當(dāng)前長(zhǎng)度大于length,則將文件長(zhǎng)度減小至length
文件IO緩沖
- 由于磁盤存取的速度比較緩慢造壮,所以read\write調(diào)用并不等待實(shí)際的磁盤存取操作渡讼。內(nèi)核為read/write準(zhǔn)備了緩存,每當(dāng)調(diào)用write寫數(shù)據(jù)時(shí)耳璧,實(shí)際上是往緩存中寫入成箫,直到緩存滿或者某個(gè)時(shí)機(jī),系統(tǒng)會(huì)自動(dòng)將緩存中的數(shù)據(jù)刷新到磁盤旨枯,這也減少了對(duì)磁盤的存取提高了效率蹬昌。如果還沒(méi)有將寫入的數(shù)據(jù)刷新到磁盤上,且這時(shí)候有另外一個(gè)進(jìn)程來(lái)讀取該數(shù)據(jù)攀隔,內(nèi)核會(huì)直接返回緩存中的數(shù)據(jù)皂贩,大大提高了效率;對(duì)于read調(diào)用也是類似的
- 對(duì)于標(biāo)準(zhǔn)IO庫(kù)stdio來(lái)說(shuō)昆汹,里面的IO函數(shù)都是帶緩沖的明刷,可以使用setvbuf函數(shù)來(lái)控制stdio庫(kù)使用緩沖的方式,參數(shù)stream表示要修改的某個(gè)文件流满粗,mode指示了緩沖的類型辈末,分別有三種控制類型:_IONBF/_IOLBF/_IOFBF。_IONBF表示不緩沖败潦,相當(dāng)于直接調(diào)用write和read本冲;_IOLBF表示行緩沖,在遇到一個(gè)換行符之前緩沖數(shù)據(jù)劫扒,默認(rèn)終端設(shè)備采用行緩沖檬洞;_IOFBF表示全緩沖,以buf和size指定的緩沖區(qū)為緩沖對(duì)象沟饥;參數(shù)buf表示緩沖區(qū)首地址添怔,不能使用棧上的內(nèi)存湾戳,因?yàn)殡S著函數(shù)返回,內(nèi)存不可訪問(wèn)广料,size表示緩沖區(qū)大小砾脑。函數(shù)原型如下
#include <stdio.h>
int setvbuf(FILE *stream, char *buf, int mode, size_t size);
- 無(wú)論使用何種緩沖方式,都可以使用fflush函數(shù)立即刷新stream流的緩沖區(qū)艾杏,如果stream為NULL則刷新所有的緩沖區(qū)韧衣,函數(shù)原型如下
#include <stdio.h>
int fflush(FILE *stream);
- 對(duì)于內(nèi)核緩沖區(qū),我們也可以調(diào)用刷新函數(shù)來(lái)將緩沖區(qū)的內(nèi)容刷新到磁盤购桑,可以調(diào)用sync畅铭,fsync以及fdatasync三種刷新函數(shù);linux實(shí)現(xiàn)的sync會(huì)將所有內(nèi)核緩沖區(qū)中的數(shù)據(jù)排入寫隊(duì)列勃蜘,且等待實(shí)際的寫入完成才返回硕噩,而某些實(shí)現(xiàn)不等待IO完成就返回;fsync將與fd文件描述符相關(guān)的文件的所有信息都刷新到磁盤缭贡,并等待IO完成再返回炉擅;fdatasync只是將與文件描述符fd相關(guān)的文件的數(shù)據(jù)部分刷新到磁盤并等待完成后再返回;相應(yīng)的函數(shù)原型如下
#include <unistd.h>
int fsync(int fd)阳惹;
int fdatasync(int fd);
void sync(void);
- 另外谍失,在調(diào)用open時(shí)如果指定O_SYNC標(biāo)志,會(huì)使得所有后續(xù)的輸出同步穆端,即等待數(shù)據(jù)(包括元數(shù)據(jù))寫入到磁盤上才返回
- 文件描述符和標(biāo)準(zhǔn)庫(kù)的流對(duì)象之間可以互相轉(zhuǎn)換袱贮,函數(shù)原型如下
#include <stdio.h>
int fileno(FILE *stream);
FILE *fdopen(int fd, const char *mode);
文件系統(tǒng)詳述
- 在linux中,一切都是文件体啰,設(shè)備也是以文件的形式存在的攒巍;設(shè)備可分為字符設(shè)備文件和塊設(shè)備文件,兩者的區(qū)別就在于字符型設(shè)備基于每個(gè)字符來(lái)處理數(shù)據(jù)而塊設(shè)備按照一塊數(shù)據(jù)來(lái)處理數(shù)據(jù)荒勇。典型的柒莉,像鼠標(biāo)就是字符設(shè)備而磁盤就屬于塊設(shè)備。應(yīng)用程序?qū)τ诟黝愒O(shè)備的read/write調(diào)用實(shí)際上底層都是去調(diào)用了對(duì)應(yīng)設(shè)備的驅(qū)動(dòng)程序沽翔,最終完成實(shí)際的輸入輸出兢孝。
- 以ext2文件系統(tǒng)為例,首先第一個(gè)部分是引導(dǎo)塊仅偎,包含了引導(dǎo)操作系統(tǒng)的信息跨蟹;然后剩余的空間被劃分為大小相等的塊組,每一個(gè)塊組中包含了超級(jí)塊橘沥,inode位圖窗轩,數(shù)據(jù)塊位圖,inode表以及數(shù)據(jù)塊表座咆;超級(jí)塊中記錄了當(dāng)前這個(gè)塊組中的inode剩余量以及數(shù)據(jù)塊的大小等信息痢艺;inode位圖用來(lái)記錄當(dāng)前塊組中inode節(jié)點(diǎn)的使用情況仓洼;數(shù)據(jù)塊位圖用來(lái)記錄數(shù)據(jù)塊的使用情況;而inode表和數(shù)據(jù)塊表就是實(shí)際存放文件數(shù)據(jù)的地方堤舒,inode用來(lái)存放文件的元數(shù)據(jù)(包括文件大小色建,訪問(wèn)時(shí)間等),數(shù)據(jù)塊用來(lái)存放文件的真正數(shù)據(jù)
- 上面說(shuō)過(guò)inode節(jié)點(diǎn)中存放了文件的元數(shù)據(jù)舌缤,維護(hù)了以下一些信息
- 文件類型
- 文件大小
- 文件所屬用戶及用戶組
- 文件對(duì)應(yīng)屬組的權(quán)限位
- 指向當(dāng)前文件的硬鏈接數(shù)
- 數(shù)據(jù)塊指針箕戳,指向文件存放真正數(shù)據(jù)的數(shù)據(jù)塊
- 三個(gè)時(shí)間信息,對(duì)文件的最后訪問(wèn)時(shí)間友驮,對(duì)文件的最后修改時(shí)間(對(duì)文件內(nèi)容的修改)以及對(duì)文件的元數(shù)據(jù)的最后修改時(shí)間
- 該文件所占有的數(shù)據(jù)塊的數(shù)目
- 對(duì)于inode節(jié)點(diǎn)中的數(shù)據(jù)塊指針又分為四種類型漂羊,直接指針,一級(jí)指針卸留,二級(jí)指針以及三級(jí)指針。在ext2中每個(gè)inode中包含15個(gè)與數(shù)據(jù)塊相關(guān)的指針椭豫,其中前12個(gè)都是直接指針耻瑟;而后面三個(gè)指針?lè)謩e是一級(jí),二級(jí)和三級(jí)指針赏酥。假設(shè)一個(gè)數(shù)據(jù)塊為4096字節(jié)喳整,那么對(duì)于文件大小小于12×4096字節(jié)的文件來(lái)說(shuō),只需要用到前12個(gè)指針裸扶,小文件的數(shù)據(jù)都直接放置于前12個(gè)指針指向的數(shù)據(jù)塊中框都;如果超過(guò)這個(gè)大小,就需要用到一級(jí)指針了呵晨,一級(jí)指針指向的數(shù)據(jù)塊中存放的不是數(shù)據(jù)魏保,而是指向其他數(shù)據(jù)塊的指針,而每個(gè)指針長(zhǎng)度為4字節(jié)摸屠,則一共可以存放1024個(gè)指針谓罗,則在只使用13個(gè)指針的前提下,文件的大小最大可以使(1024+12)×4096字節(jié)季二;二級(jí)指針和三級(jí)指針類似檩咱。
- 為了達(dá)到為所有文件系統(tǒng)提供同樣的API的目的,linux在應(yīng)用程序和具體的文件系統(tǒng)之間提供了一層抽象層胯舷,也就是VFS(虛擬文件系統(tǒng))刻蚯;調(diào)用過(guò)程可能是這樣,應(yīng)用程序調(diào)用write桑嘶,內(nèi)核通過(guò)inode節(jié)點(diǎn)中的對(duì)應(yīng)的函數(shù)指針找到對(duì)應(yīng)文件系統(tǒng)的write調(diào)用炊汹,然后再去調(diào)用驅(qū)動(dòng)程序完成寫入
文件屬性
- 利用以下三種系統(tǒng)調(diào)用可以獲取一個(gè)文件的元數(shù)據(jù),一般都是從文件的inode節(jié)點(diǎn)中獲取不翩。文件屬性保存于結(jié)構(gòu)體stat中兵扬,stat會(huì)返回指定路徑名的文件的文件屬性信息麻裳;fstat返回指定文件描述符fd的文件屬性;lstat與stat類似器钟,區(qū)別在于如果指定路徑是一個(gè)符號(hào)鏈接津坑,那么返回的文件屬性是與該符號(hào)鏈接相關(guān),也就是不追蹤傲霸,而stat會(huì)返回符號(hào)鏈接所指向的文件的信息疆瑰。調(diào)用lstat和stat的進(jìn)程不需要對(duì)指定的文件有任何權(quán)限,但是必須保證對(duì)pathname的父目錄有執(zhí)行權(quán)限昙啄;而fstat只需要提供一個(gè)有效的fd就可以穆役。相關(guān)函數(shù)原型和結(jié)構(gòu)體如下
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* Inode number */
mode_t st_mode; /* File type and mode */
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; /* Block size for filesystem I/O */
blkcnt_t st_blocks; /* Number of 512B blocks allocated */
/* Since Linux 2.6, the kernel supports nanosecond
precision for the following timestamp fields.
For the details before Linux 2.6, see NOTES. */
struct timespec st_atim; /* Time of last access */
struct timespec st_mtim; /* Time of last modification */
struct timespec st_ctim; /* Time of last status change */
#define st_atime st_atim.tv_sec /* Backward compatibility */
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec
};
int stat(const char *pathname, struct stat *statbuf);
int fstat(int fd, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);
- 對(duì)于返回的stat結(jié)構(gòu)體的部分成員進(jìn)行說(shuō)明。st_ino表示該文件對(duì)應(yīng)的inode編號(hào)梳凛;st_mode是一個(gè)16位長(zhǎng)的字段耿币,高4位表示文件類型,低12位表示文件權(quán)限韧拒⊙徒樱可以利用系統(tǒng)提供的宏來(lái)判斷文件類型,例如S_ISREG()測(cè)試該文件是否是常規(guī)文件叛溢,具體參考man手冊(cè)塑悼;st_nlink表示硬鏈接數(shù);st_size表示文件大锌簟厢蒜;st_blocks表示分配給該文件的數(shù)據(jù)塊數(shù)目;st_blksize表示在該文件上進(jìn)行IO操作時(shí)所采用的最優(yōu)緩沖區(qū)大小
- 每一個(gè)文件都有一個(gè)相關(guān)聯(lián)的用戶和組ID烹植,分別用st_uid和st_gid表示斑鸦。在文件創(chuàng)建時(shí),用戶和組ID分別取自進(jìn)程的有效用戶和組ID
- 對(duì)于文件的權(quán)限位來(lái)說(shuō)刊橘,可以用st_mode和各種權(quán)限位相&的方式來(lái)判斷鄙才,具體見man手冊(cè);另外促绵,可以通過(guò)filePermStr調(diào)用將st_mode轉(zhuǎn)換為一個(gè)形如"rwxr-xr--"的字符串攒庵,flags參數(shù)指定為FP_SPECIAL,原型為
char* filePermStr(mode_t perm,int flags)
败晴,位于sys/types.h
頭文件中
- 對(duì)于目錄來(lái)說(shuō)浓冒,讀寫執(zhí)行權(quán)限有不同的意義。目錄具有讀權(quán)限表示只能查看目錄下的文件名尖坤;具有寫權(quán)限可以對(duì)目錄下的文件進(jìn)行修改或者增刪文件稳懒;具有執(zhí)行權(quán)限可以進(jìn)入該目錄
- 當(dāng)進(jìn)程訪問(wèn)一個(gè)文件時(shí),會(huì)依次根據(jù)進(jìn)程的有效用戶ID慢味,有效組ID以及附屬組ID來(lái)分別檢查對(duì)于該文件的權(quán)限场梆;
- 調(diào)用chown可以改變一個(gè)文件的屬主和屬組墅冷。只有對(duì)于特權(quán)級(jí)進(jìn)程來(lái)說(shuō),才能更改文件的屬主或油;非特權(quán)級(jí)進(jìn)程只有在進(jìn)程的有效用戶ID和文件當(dāng)前屬主一致的情況下才能改變文件的屬主寞忿。函數(shù)原型為
int chown(const char* pathname,uid_t owner,gid_t group)
- 若想要更改文件的權(quán)限位,一般先調(diào)用stat獲取文件的原權(quán)限顶岸,然后在此基礎(chǔ)上進(jìn)行修改腔彰,最后調(diào)用chmod。同樣的辖佣,對(duì)于chmod來(lái)說(shuō)有兩種情況霹抛。當(dāng)調(diào)用進(jìn)程是特權(quán)級(jí)進(jìn)程,可以修改文件的權(quán)限位卷谈;當(dāng)調(diào)用進(jìn)程是非特權(quán)進(jìn)程杯拐,其有效用戶ID必須和文件屬主一致。函數(shù)原型為
int chmod(const char* pathname,mode_t mode)
- 注意到文件名并沒(méi)有包括在inode節(jié)點(diǎn)的文件屬性中世蔗。文件名是存放在目錄下的藕施,對(duì)于一個(gè)目錄來(lái)說(shuō),它的inode的文件類型表示一個(gè)目錄凸郑,并且inode中數(shù)據(jù)塊指針?biāo)赶虻臄?shù)據(jù)塊中存放的是關(guān)于文件名和inode的映射。有可能存在多個(gè)不同的文件名指向相同的一個(gè)inode矛市,這就是一個(gè)硬鏈接芙沥。如果一個(gè)inode節(jié)點(diǎn)的硬鏈接計(jì)數(shù)為0,那么該文件就會(huì)被刪除浊吏,即釋放相關(guān)inode和數(shù)據(jù)塊
- 硬鏈接存在兩個(gè)問(wèn)題而昨,其一是只能應(yīng)用于同一個(gè)文件系統(tǒng)下,因?yàn)閕node編號(hào)的唯一性不能跨文件系統(tǒng)找田;其二是不能為目錄創(chuàng)建硬鏈接歌憨,避免出現(xiàn)鏈接環(huán)路
- 為解決上述問(wèn)題,就要用到符號(hào)鏈接墩衙。建立一個(gè)符號(hào)鏈接相當(dāng)于建立一個(gè)新的文件务嫡,也就新建了一個(gè)inode節(jié)點(diǎn),符號(hào)鏈接的文件數(shù)據(jù)只包括所引用文件的絕對(duì)路徑漆改,當(dāng)系統(tǒng)發(fā)現(xiàn)這是一個(gè)符號(hào)鏈接時(shí)心铃,會(huì)自動(dòng)解引用找到真正的文件
- 有關(guān)硬鏈接的函數(shù)原型如下,link和unlink分別是創(chuàng)建和刪除一個(gè)硬鏈接挫剑,需要注意的是link和unlink不會(huì)對(duì)pathname解引用去扣,即如果oldpath或者pathname是符號(hào)鏈接,那么只會(huì)針對(duì)該符號(hào)鏈接去新建或者刪除硬鏈接樊破,而不涉及引用的實(shí)際文件
#include <unistd.h>
int link(const char *oldpath, const char *newpath);
int unlink(const char *pathname);
- rename調(diào)用同shell命令mv愉棱,可以移動(dòng)或者重命名一個(gè)文件唆铐,原型為
int rename(const char* oldpath,const char* newpath)
- symlink調(diào)用用于創(chuàng)建一個(gè)符號(hào)鏈接,如果target所代表的文件不存在奔滑,那么linkpath成為懸空鏈接艾岂,原型為
int symlink(const char *target, const char *linkpath);
另外,解除一個(gè)符號(hào)鏈接也是調(diào)用unlink函數(shù)档押。如果用open去打開一個(gè)符號(hào)鏈接膜赃,系統(tǒng)會(huì)為其解引用募舟,即打開symbol link所引用的文件。所以如果想要查看符號(hào)鏈接本身的內(nèi)容,那么就使用readlink函數(shù)言询,buffer是保存內(nèi)容的緩沖區(qū),bufsiz表示緩沖區(qū)大小揩抡,pathname表示符號(hào)鏈接路徑藻懒,原型為ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);
需要注意的是,這個(gè)函數(shù)的返回值為放入buf中的字節(jié)數(shù)癞松,并且buf中不會(huì)包括空字符爽撒,所以一般會(huì)分配一個(gè)長(zhǎng)度為路徑最大長(zhǎng)度的緩沖區(qū),示例如下(解析一個(gè)符號(hào)鏈接的內(nèi)容)
1 #include <unistd.h>
2 #include <stdio.h>
3 #include <sys/stat.h>
4 #include <limits.h>
5 #include <stdlib.h>
6
7 #define BUFFSIZE PATH_MAX
8
9 int main(void)
10 {
11 char buf[BUFFSIZE];
12 char* path = "lntag";
13 struct stat st;
14
15 int res = lstat(path,&st);
16 if(res == -1)
17 {
18 perror("stat");
19 exit(1);
20 }
21
22 if(!S_ISLNK(st.st_mode))
23 {
24 printf("lntag is not a slink\n");
25 exit(1);
26 }
27
28 int nums = readlink(path,buf,BUFFSIZE - 1);
29 buf[nums] = '\0';
30 printf("lntag:%s\n",buf);
31
32 return 0;
33 }
- 對(duì)于目錄的訪問(wèn)不能使用open函數(shù)响蓉,調(diào)用open會(huì)出錯(cuò)硕勿;對(duì)于目錄的訪問(wèn)需要另外一組API函數(shù),首先需要使用opendir或者fdopendir獲得對(duì)目錄的一個(gè)指針枫甲,該指針是DIR類型的源武。然后循環(huán)調(diào)用readdir,傳入目錄指針想幻,該函數(shù)的返回值是一個(gè)指向dirent結(jié)構(gòu)體的指針粱栖,這個(gè)結(jié)構(gòu)體相當(dāng)于一條目錄項(xiàng),存放文件的inode和文件的名字以及其他信息脏毯;并且每調(diào)用一次readdir闹究,就會(huì)自動(dòng)從指定的目錄指針中獲取下一條目錄項(xiàng),直到返回NULL表示遍歷目錄結(jié)束食店。還可以調(diào)用rewinddir函數(shù)將目錄指針重新指向目錄初始處渣淤,另外遍歷結(jié)束后應(yīng)調(diào)用closedir釋放資源。相關(guān)函數(shù)原型及結(jié)構(gòu)體如下
#include <dirent.h>
#include <sys/types.h>
struct dirent {
ino_t d_ino; /* Inode number */
off_t d_off; /* Not an offset; see below */
unsigned short d_reclen; /* Length of this record */
unsigned char d_type; /* Type of file; not supported
by all filesystem types */
char d_name[256]; /* Null-terminated filename */
};
DIR *opendir(const char *name);
DIR *fdopendir(int fd);
struct dirent *readdir(DIR *dirp);
void rewinddir(DIR* dirp);
int closedir(DIR* dirp);
- 可以通過(guò)dirfd函數(shù)獲得對(duì)應(yīng)于目錄流的文件描述符叛买,原型為
int dirfd(DIR* dirp)
- 關(guān)于進(jìn)程的當(dāng)前工作目錄砂代,可以調(diào)用getcwd函數(shù)來(lái)獲取,該函數(shù)將當(dāng)前工作目錄字符串存放于cwdbuf數(shù)組中率挣,size為函數(shù)寫入cwdbuf數(shù)組的最大長(zhǎng)度刻伊,如果調(diào)用成功返回一個(gè)指向cwdbuf的指針;另外可以調(diào)用chdir和fchdir來(lái)改變當(dāng)前工作目錄,相關(guān)函數(shù)原型如下
#include <unistd.h>
int chdir(const char *path);
int fchdir(int fd);
char *getcwd(char *buf, size_t size);
- dirname和basename函數(shù)分別獲取對(duì)于pathname的目錄和文件名捶箱,例如對(duì)于路徑"/home/pty/file.c"智什,dirname返回"/home/pty",basename返回"pty"丁屎,函數(shù)原型如下
#include <libgen.h>
char* dirname(char* pathname);
char* basename(char* pathname);