- 所有執(zhí)行I/O操作的系統(tǒng)調(diào)用都以文件描述符(一個(gè)非負(fù)整數(shù))來指代打開的文件。包括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)
- 通過文件描述符在文件描述符表中找到相對(duì)應(yīng)的引用,然后可以找到對(duì)應(yīng)的文件表項(xiàng);在文件表中記錄了以下信息
1. 當(dāng)前文件偏移量
2. 文件狀態(tài)標(biāo)志
3. 文件訪問模式
4. 信號(hào)驅(qū)動(dòng)IO相關(guān)
5. inode引用
6. v節(jié)點(diǎn)引用
1. 文件訪問權(quán)限和文件類型
2. 文件鎖列表
3. 文件的各種屬性(包括文件大小以及相關(guān)時(shí)間屬性)
4. 數(shù)據(jù)塊編號(hào)
- v節(jié)點(diǎn)是用來支持虛擬文件系統(tǒng)的(VFS),能夠?yàn)椴煌奈募到y(tǒng)提供統(tǒng)一的API,v節(jié)點(diǎn)中保存了各種底層函數(shù)調(diào)用的函數(shù)指針;此外,linux上沒有特定的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);通過dup或者fcntl復(fù)制文件描述符可以使兩個(gè)fd指向相同的文件表項(xiàng);通過兩次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沒有指定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來設(shè)置此標(biāo)志才起作用
- O_NONBLOCK以非阻塞模式打開文件
- O_TRUNC表示當(dā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)志來一次性調(diào)用open來判斷(作為原子操作)
- 當(dāng)有多個(gè)進(jìn)程向同一個(gè)文件尾部寫入數(shù)據(jù)時(shí),在沒有同步的情況下如果使用lseek加write的方式,會(huì)出現(xiàn)競爭狀態(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ì)于磁盤文件來說,可能是因?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)中的文件偏移量的值,并沒有去訪問物理設(shè)備
- 不允許將lseek應(yīng)用于管道,socket,終端以及FIFO,如果這樣調(diào)用了,調(diào)用將失敗并將errno置為ESPIPE
- 如果文件的文件偏移量已經(jīng)超過了文件末尾,那么內(nèi)核將以空字節(jié)去填充,不占用實(shí)際磁盤空間,這稱為文件空洞。直到向文件空洞中去寫入數(shù)據(jù),內(nèi)核才會(huì)真正去方位磁盤設(shè)備,申請(qǐng)磁盤空間
fcntl
- 函數(shù)原型為
int fcntl(int fd,int cmd,......)
- 通過將cmd設(shè)置為F_GETFL可以獲得fd所指向文件的文件狀態(tài)標(biāo)志,進(jìn)而可以通過將其和各種狀態(tài)標(biāo)志做&操作,判斷是否設(shè)置了該狀態(tài)標(biāo)志
- 對(duì)于文件的訪問權(quán)限來說,在判斷的時(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來設(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。不能通過F_SETFL修改文件的訪問模式(O_RDONLY\O_WRONLY\O_RDWR)
- 可以通過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
- 為了避免競爭狀態(tài)的產(chǎn)生,保證多進(jìn)程或者多線程之間能夠正確的在指定偏移處進(jìn)行IO操作,可以使用這一組系統(tǒng)調(diào)用;在單線程環(huán)境下,pread調(diào)用等同于下面的調(diào)用過程(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è)用來描述緩沖區(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)前長度小于length,則在文件尾部添加空字節(jié)以增長到指定length;若文件當(dāng)前長度大于length,則將文件長度減小至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ì)磁盤的存取提高了效率。如果還沒有將寫入的數(shù)據(jù)刷新到磁盤上,且這時(shí)候有另外一個(gè)進(jìn)程來讀取該數(shù)據(jù),內(nèi)核會(huì)直接返回緩存中的數(shù)據(jù),大大提高了效率;對(duì)于read調(diào)用也是類似的
- 對(duì)于標(biāo)準(zhǔn)IO庫stdio來說,里面的IO函數(shù)都是帶緩沖的,可以使用setvbuf函數(shù)來控制stdio庫使用緩沖的方式,參數(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)存不可訪問,size表示緩沖區(qū)大小。函數(shù)原型如下
#include <stdio.h>
int setvbuf(FILE *stream, char *buf, int mode, size_t size);
- 無論使用何種緩沖方式,都可以使用fflush函數(shù)立即刷新stream流的緩沖區(qū),如果stream為NULL則刷新所有的緩沖區(qū),函數(shù)原型如下
#include <stdio.h>
int fflush(FILE *stream);
- 對(duì)于內(nèi)核緩沖區(qū),我們也可以調(diào)用刷新函數(shù)來將緩沖區(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)庫的流對(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è)字符來處理數(shù)據(jù)而塊設(shè)備按照一塊數(shù)據(jù)來處理數(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位圖用來記錄當(dāng)前塊組中inode節(jié)點(diǎn)的使用情況;數(shù)據(jù)塊位圖用來記錄數(shù)據(jù)塊的使用情況;而inode表和數(shù)據(jù)塊表就是實(shí)際存放文件數(shù)據(jù)的地方,inode用來存放文件的元數(shù)據(jù)(包括文件大小,訪問時(shí)間等),數(shù)據(jù)塊用來存放文件的真正數(shù)據(jù)
- 上面說過inode節(jié)點(diǎn)中存放了文件的元數(shù)據(jù),維護(hù)了以下一些信息
- 文件類型
- 文件大小
- 文件所屬用戶及用戶組
- 文件對(duì)應(yīng)屬組的權(quán)限位
- 指向當(dāng)前文件的硬鏈接數(shù)
- 數(shù)據(jù)塊指針,指向文件存放真正數(shù)據(jù)的數(shù)據(jù)塊
- 三個(gè)時(shí)間信息,對(duì)文件的最后訪問時(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è)指針分別是一級(jí),二級(jí)和三級(jí)指針。假設(shè)一個(gè)數(shù)據(jù)塊為4096字節(jié),那么對(duì)于文件大小小于12×4096字節(jié)的文件來說,只需要用到前12個(gè)指針,小文件的數(shù)據(jù)都直接放置于前12個(gè)指針指向的數(shù)據(jù)塊中;如果超過這個(gè)大小,就需要用到一級(jí)指針了,一級(jí)指針指向的數(shù)據(jù)塊中存放的不是數(shù)據(jù),而是指向其他數(shù)據(jù)塊的指針,而每個(gè)指針長度為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)用過程可能是這樣,應(yīng)用程序調(diào)用write,內(nèi)核通過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)行說明。st_ino表示該文件對(duì)應(yīng)的inode編號(hào);st_mode是一個(gè)16位長的字段,高4位表示文件類型,低12位表示文件權(quán)限。可以利用系統(tǒng)提供的宏來判斷文件類型,例如S_ISREG()測試該文件是否是常規(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)限位來說,可以用st_mode和各種權(quán)限位相&的方式來判斷,具體見man手冊(cè);另外,可以通過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ì)于目錄來說,讀寫執(zhí)行權(quán)限有不同的意義。目錄具有讀權(quán)限表示只能查看目錄下的文件名;具有寫權(quán)限可以對(duì)目錄下的文件進(jìn)行修改或者增刪文件;具有執(zhí)行權(quán)限可以進(jìn)入該目錄
- 當(dāng)進(jìn)程訪問一個(gè)文件時(shí),會(huì)依次根據(jù)進(jìn)程的有效用戶ID,有效組ID以及附屬組ID來分別檢查對(duì)于該文件的權(quán)限;
- 調(diào)用chown可以改變一個(gè)文件的屬主和屬組。只有對(duì)于特權(quán)級(jí)進(jìn)程來說,才能更改文件的屬主;非特權(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來說有兩種情況。當(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)
- 注意到文件名并沒有包括在inode節(jié)點(diǎn)的文件屬性中。文件名是存放在目錄下的,對(duì)于一個(gè)目錄來說,它的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è)問題,其一是只能應(yīng)用于同一個(gè)文件系統(tǒng)下,因?yàn)閕node編號(hào)的唯一性不能跨文件系統(tǒng);其二是不能為目錄創(chuàng)建硬鏈接,避免出現(xiàn)鏈接環(huá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è)長度為路徑最大長度的緩沖區(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ì)于目錄的訪問不能使用open函數(shù),調(diào)用open會(huì)出錯(cuò);對(duì)于目錄的訪問需要另外一組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);
- 可以通過dirfd函數(shù)獲得對(duì)應(yīng)于目錄流的文件描述符,原型為
int dirfd(DIR* dirp)
- 關(guān)于進(jìn)程的當(dāng)前工作目錄,可以調(diào)用getcwd函數(shù)來獲取,該函數(shù)將當(dāng)前工作目錄字符串存放于cwdbuf數(shù)組中,size為函數(shù)寫入cwdbuf數(shù)組的最大長度,如果調(diào)用成功返回一個(gè)指向cwdbuf的指針;另外可以調(diào)用chdir和fchdir來改變當(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);