Unix環境高階程式設計---檔案I/O
學習了一段時間Unix環境程式設計了,看到程序和執行緒這部分感覺之後,就感覺進步比較緩慢,所以在緩慢的過程中,希望可以多前面的知識進行一定的複習和鞏固,希望以寫的方式,加深對前面知識的理解。從第三章到第五章都是和檔案和I/O相關的。這章總結的是第三章的相關內容。
- 檔案描述符
在Unix系統中,或者對於更加屬性的Linux系統中,所有的裝置都是用檔案來統一定義的。檔案描述符就是對檔案的一種標識,所有的開啟檔案都通過檔案描述符引用。檔案描述符是一個非負整數。所有對檔案的操作,比如讀寫,都可以通過檔案描述符來對檔案實施操作。
在Unix系統中,或者對於大多數作業系統中,定義了三個常用的檔案描述符。
- 檔案描述符0與標準輸入相關聯
- 檔案描述符1與標準輸出相關聯
- 檔案描述符2與標準錯誤相關聯
- 在<unistd.h>中,將0,1,2定義了更具有可讀性之的符號常量,STDIN_FILENO,STDOUT_FILENO,STDERR_FILENO
- 檔案的開啟,建立以及關閉
檔案的開啟、建立和關閉有以下函式原型
#include<fcntl.h>
int open(const char *path, int oflags, mode_t mode)
int openat(int fd, const char *path,int oflags, mode_t mode)
檔案的開啟、建立是在標頭檔案fcntl.h中定義的。其中對
- 第一個引數為路徑名
- 第二個引數為檔案狀態標誌:
O_RDONLY或0:可讀
O_WRONLY或1:可寫
O_RDWR或2:可執行
O_APPEND:每次寫時都加到檔案的尾部
O_CREAT:若此檔案不存在則建立它,使用此選項時,需同時說明第三個引數mode,用其說明該新檔案的存取許可權位
O_EXCL:如果同時指定了O_CREAT,而檔案已經存在,則出錯,這可測試一個檔案是否存在,如果不存在則建立此檔案成為一個原子操作
O_TRUNC:如果此檔案存在,而且為只讀或只寫成功開啟,則將其長度截短為0
O_NOCTTY:如果pathname指的是終端裝置,則不將此裝置分配作為此程序的控制終端
O_NONBLOCK:如果pathname指的是一個FIFO,一個塊特殊檔案或一個字元特殊檔案,則此選擇項為此檔案的本次開啟操作和後續的I/O操作設定非阻塞方式
O_SYNC:使每次write都等到物理I/O操作完成
- 第三個引數一般為檔案許可權,第三個引數一般在建立新檔案才會使用這個引數。即第二個引數選擇為O_CREAT
S_IRWXU,0700:代表該檔案所有者具有可讀,可寫,可指向的許可權
S_IRUSR或S_IREAD,0400:代表該檔案所有者具有可讀取的許可權
S_IWUSR或S_IWRITE,0200:代表該檔案所有者具有可寫入的許可權
S_IXUSR或S_IEXEC,0100:代表該檔案所有者具有可指向的許可權
S_IRWXG,0070:代表該檔案使用者組具有可讀,可寫,可執行的許可權
S_IRGRP,0040:代表該檔案使用者組具有可讀的許可權
S_IWGRP,0020:代表該檔案使用者組具有可寫的許可權
S_IXGRP,0010:代表該檔案使用者組具有可執行的許可權
S_IRWXO,0007:代表其他使用者具有可讀,可寫,可執行的許可權
S_IROTH,0004:代表其他使用者具有可讀的許可權
S_IWOTH,0002:代表其他使用者具有可寫的許可權
S_IXOTH,0001:代表其他使用者具有可執行的許可權
open函式和openat函式的區別在於:
- path函式指定的是絕對路徑名的時候,fd引數可以被忽略,openat函式相當於open函式
- path函式指定的是相對路徑名的時候,fd引數指出了相對路徑名在檔案系統中的開始地址,fd引數是通過開啟相對路徑名所在目錄來獲取。
- path引數指定了相對路徑名,fd引數具有特殊值AT_FDWD,這種情況下,路徑名在當前目錄中獲取,這個時候兩個函式功能類似。
看到這裡的時候,個人本能的是矇蔽的,主要是書上沒有給出具體的例子,加上還沒有接觸第四章內容於是乎就去查閱了相關的資料。比如對於一個檔案,其絕對路徑名是home/effort/桌面/apue.3e/fileio/output.log。其目錄路徑為home/effort/桌面/apue.3e,那麼fd指的就是目錄的路徑,path指代的就是output.log,簡單的使用如下:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <stdio.h>
#include <unistd.h>
int main()
{
DIR *dir;
int dirfd2;
int fd;
int n;
dir = opendir("../fileio");
if(NULL == dir)
{
perror("open dir error");
return -1;
}
dirfd2 = dirfd(dir);
if(-1 == dirfd2)
{
perror("dirfd error");
return -1;
}
fd = openat(dirfd2,"output.log",O_CREAT|O_RDWR|O_TRUNC);
if(-1 == fd)
{
perror("opeat error");
return -1;
}
n = write(fd,"Hello world!\n",15);
close(fd);
closedir(dir);
return 0;
}
執行程式之前,檔案內容如下:
執行之後:
- 檔案的關閉
檔案的關閉的函式是在標頭檔案unistd.h(很想知道,問啥不在一個統一的標頭檔案裡面),函式原型為:
#include<unistd.h>
int close(fd)
當一個程序終止時,核心會自動關閉它所有開啟的檔案,很多時候都會利用這種功能而不顯式地呼叫close關閉檔案- 檔案的讀寫和定位
當開啟檔案後,擁有對檔案的讀寫的許可權時,我們就可以對於檔案進行讀寫了。很多時候,我們還面臨著在檔案不同的位置進行操作的需求,因此還需要對檔案進行定位操作。檔案的定位意思是將使檔案讀寫從當前檔案偏移量處開始。一般開啟檔案時,預設的情況下為0。
所謂的當前檔案偏移量是用於度量從該檔案開始處計算的位元組數。可以呼叫lseek顯式地開啟一個檔案設定偏移量。函式原型為:
#include<unistd.h>
off_t lseek(int fd, off_t offset, int wherence)
其中offset和第三個引數wherence緊密相關,具體關係如下:
若wherence是SEEK_SET,則檔案偏移量設定為距當前檔案開始處offset個位元組
若wherence是SEEK_CUR,則檔案偏移量設定為距當前值加上offset,offset可負可正
若wherence是SEEK_END,則檔案偏移量設定為檔案長度加offset,同樣可負可正
若lseek呼叫成功,則返回新的檔案偏移量。
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main()
{
int fd;
fd=open("./output.log",O_RDONLY);
if(fd<0)
{printf("can not open file");return -1;}
off_t offset;
offset=lseek(fd,0,SEEK_CUR);
printf("%d\n",offset);
offset=lseek(fd,0,SEEK_END);
printf("%d\n",offset);
}
其中output.log的內容為:hello
輸出結果為:
從檔案中讀取資料,需要呼叫read函式,read函式模型為:
#include<unistd.h>
ssize_t read(int fd, void *buf, size_t nbytes)
若read成功,則返回讀到的位元組數,如已到達檔案末尾,則返回0. 讀操作從當前偏移量開始,在成功返回之前,該偏移量將會增加實際讀到的位元組數。
向檔案中寫資料這需要write函式。
#include<unistd.h>
ssize_t write(int fd, const void* buf, size_t nbytes)
若寫成功,則返回實際寫入的位元組數,否則返回-1.所以一般判定寫資料成功,不能單單隻從-1來判斷,而應該從返回實際的寫入位元組數和nbytes比較,相等則表示寫入資料成功。
對於讀寫資料而言,都是從當前檔案偏移量開始的,所以要鎖定任意位置,lseek就可以發揮作用了。
此外還有一個問題,就是對於buf大小的選定。在APUE第三章對此內容進行了詳細的解釋:檔案系統為ext4的磁碟長度為4096位元組。經過測試,系統CPU時間最小為4096左右及以後的位置,因此很多時候,我們都看點BUFFSIZE為4096的原因了。