linux檔案IO底層讀寫操作
1.底層檔案訪問。
執行中的程式成為程序,每個程序都有與之關聯的檔案描述符。
檔案描述符 - 一些小值整數,通過他們訪問開啟的檔案或裝置。開始執行會有三個檔案描述符:
- 0: 標準輸入 STDIN_FILENO
- 1: 標準輸出 STDOUT_FILENO
- 2: 標準錯誤 STDERR_FILENO
檔案描述符的變化範圍是:0~OPEN_MAX-1 (可通過ulmit -a 檢視)
write 系統呼叫
作用:把緩衝區buf 的前count 個位元組寫入與檔案描述符 fd 相關聯的檔案中。
#include <unistd.h>
size_t write(int fd,const void *buf, size_t count);
描述符出錯,檔案達到程序限制最大值或裝置驅動程式對資料塊 長度比較敏感,該返回值可能會小於count,這並不是一個錯誤。 0 表示未寫入資料; -1 表示出錯,錯誤代號在全域性變數 errno裡。
程式碼演示
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(){
const char * output = "Hello world\n";
const char * errstr = "A write error has occurred on file descriptor 1\n";
//if(write(STDOUT_FILENO,output,strlen(output))!=strlen(output))
if(write(1,output,strlen(output))!=strlen(output))
write(2,errstr,strlen(errstr));
exit(0);
read 系統呼叫
作用:從與檔案描述符 fd 相關聯的檔案中讀取前count 個位元組到緩衝區buf 中。
#include <unistd.h>
size_t read(int fd,const void *buf, size_t count);
它返回實際讀入的位元組數,這可能會小於請求的位元組數。 0 表示未讀入任何資料,已到達了檔案尾部。 -1 表示出錯,錯誤代號在全域性變數 errno裡。
#include <unistd.h>
#include <stdlib.h>
int main(){
char buffer[128];
int nread;
nread = read(0,buffer,128);
if(nread == -1)
write(2,"A read error has occurred\n",26);
if((write(1,buffer,nread)) != nread)
write(2,"A write error has occurred\n",27);
exit(0);
}
作用:建立一個新的檔案描述符(檔案或裝置)。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
open 建立一條到檔案或裝置的訪問路徑。成功後可獲得供 read、write和其他系統呼叫使用的唯一的檔案描述符。此檔案描述符程序唯一;如果兩個程式開啟同一個檔案,那麼,他們分別得到不同的檔案描述符,並可以單獨對檔案進行獨立的操作。我們可以通過檔案鎖(O_EXCL或FCNTL)功能來解決這個問題。
引數說明:
pathname - 指示準備開啟的檔案或裝置的名字;
flags - 用於指定開啟檔案所採取的動作;
mode - 用於指定建立檔案的許可權,O_CREATE 才使用。
flags 引數通過必需檔案訪問模式 與 其他可選模式相結合的方式來指定。 首先必須指定如下檔案訪問模式之一:
模 式 |
說 明 |
O_RDONLY |
以只讀方式開啟 |
O_WRONLY |
以只寫方式開啟 |
O_RDWR |
以讀寫方式開啟 |
可選模式組合:
- O_APPEND: 把寫入資料最佳在檔案的末尾。
- O_TRUNC: 開啟檔案時把檔案長度設定為零,丟棄已有的內容。
- O_CREAT: 如果需要,就按引數mode 中給出的訪問模式建立檔案。
- O_EXCL: 與O_CREAT一起使用,確保建立檔案的 原子操作。如果檔案存在,建立
將失敗
訪問許可權的初始值
單個許可權設定: S_I R或W或X USR 或 GRP 或 OTH
讀寫執行全許可權: S_I RWX U 或 G 或 O
如:S_IRUSR 讀許可權 檔案屬主
S_IRWXO 讀寫執行 其他使用者
最終許可權生成還和程序設定的 umask 許可權掩碼有關,執行umask 命令或函式可改變許可權.
新檔案描述符總使用未用檔案描述符的最小值。如果一個檔案符被關閉再次呼叫open ,其馬上會被重用。
Posix 規定了一個 creat 呼叫: 等同於 O_CREAT|O_WRONLY|O_TRUNC
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <limits.h>
#define TOTAL 10
int main(int argc, char ** argv)
{
const char * TEXT = "This is a test.\n";
const char * filename = "./write.txt";
int fd = -1;
int i = 0;
printf("file limit: %d\n",OPEN_MAX);
fd = open(filename, O_RDWR|O_TRUNC|O_CREAT,S_IRWXU|S_IRWXG|S_IRWXO);
if(fd<0)
{
fprintf(stderr, "fopen %s failed, reason: %s. \nexit.\n",filename,strerror(errno));
return -1;;
}
printf("Start to sleep 10 seconds\n");
for( i=0; i<TOTAL; i++)
{
if(writeToFile(fd,TEXT,strlen(TEXT)) < 0)
{
fprintf(stderr, "write to %s failed, reason: %s. \nexit.\n", filename, strerror(errno));
}
printf(" %d\n",i+1);
}
printf(" finished.\n");
close(fd);
return 0;
}
int writeToFile(int fd, char * buf, int len)
{
int wlen=0;
if((wlen = write(fd,buf,len)) < 0)
{
fprintf(stderr, "write to %d failed, reason: %s. \n", fd, strerror(errno));
return -1;
}
return wlen;
}
作用:終止檔案描述符fd 和對應檔案(檔案或裝置)的關聯。檔案描述符被釋放並能夠重新使用。close 呼叫成功返回0,出錯返回 -1。
#include <unistd.h>
int close(int fd);
返回值: 檢查 close 呼叫的返回值很重要。可以檢測某些寫操作錯誤!
ioctl 系統呼叫
ioctl 提供了一個用於控制裝置及其描述行為和配置底層服務的介面。終端、檔案描述符、套接字都可以定義他們的ioctl,具體需要參考特定裝置的手冊。
#include <sys/ioctl.h>
int ioctl(int d, int request, ...);
dup和dup2 的系統呼叫
作用:提供了一種複製檔案描述符的方法,是我們通過兩個或者更多個不同的描述符來訪問同一個檔案,主要用於多個程序間進行管道通訊。
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);
作用:lseek 對檔案描述符 fd 的讀寫指標進行設定。也就是說,設定檔案的下一個讀寫位置。可根據絕對位置和相對位置(當前位置或檔案尾部)進行設定。
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
offset 引數用來指定位置,而whence 引數定義該偏移值的用法。Whence 可取值如下:
- SEEK_SET: offset 是一個絕對位置。
- SEEK_CUR: offset 是相對於當前位置的一個相對位置。
- SEEK_END: offset 是相對於檔案尾的一個相對位置。
lseek 返回從檔案頭到檔案指標被設定處的位元組偏移值,失敗時返回-1
fstat、stat和lstat系統呼叫
作用:獲取檔案的狀態資訊,該資訊將會寫入一個buf 結構中,buf 的地址會以引數形式傳遞給fstat。
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *path, struct stat *buf);
stat 和 lstat 均通過檔名查詢狀態資訊,當檔名是符號連結時,lstat返回的時符號連結本身的資訊,而stat 返回的時改連結指向的檔案的資訊。
struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* inode number */
mode_t st_mode; /* protection */
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; /* blocksize for filesystem I/O */
blkcnt_t st_blocks; /* number of 512B blocks allocated */
time_t st_atime; /* time of last access */
time_t st_mtime; /* time of last modification */
time_t st_ctime; /* time of last status change */
};
這裡要特別提到的是,以上 st_mode 標誌有一系列相關的巨集,定義見 sys/stat.h 中
,可用來測試檔案型別
許多系統呼叫和函式都會因為各種各樣的原因失敗。他們失敗時設定外部變數errno 來知名失敗原因。許多不同函式庫都把這個變數用做報告錯誤的標準方法。
注意: 程式必須在函式報告出錯 之後立刻檢查errno 變數,因為它可能馬上就被下一個函式呼叫所覆蓋,即使下一個函式沒有出錯,也可能會覆蓋這個變數。
常用錯誤程式碼的取值和含義如下:
- EPERM: 操作不允許
- ENOENT: 檔案或目錄不存在。
- EINTR: 系統呼叫被中斷。
- EAGAIN: 重試,下次有可能成功!
- EBADF: 檔案描述符失效或本身無效
- EIO: I/O錯誤。
- EBUSY: 裝置或資源忙。
- EEXIST: 檔案存在。
- EINVL: 無效引數。
- EMFILE: 開啟的檔案過多。
- ENODEV: 裝置不存在。
- EISDIR: 是一個目錄。
- ENOTDIR: 不是一個目錄。
兩個有效函式可報告出現的錯誤: strerror 和 perror。
strerror 函式
作用:把錯誤代號對映成一個字串,該字串對發生的錯誤型別進行說明。
#include <string.h>
char *strerror(int errnum);
int strerror_r(int errnum, char *buf, size_t buflen);
perror函式
作用:perror 函式也把errno 變數中報告的當前錯誤對映成一個字串,並把它輸出到標準錯誤輸出流。
perror(“test”);
結果:
Test: Too many open files
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#define BUFSIZE 1024
#define SUCCESS 0
#define FAILURE -1
int write_to_file (int fd, const char * buf, const size_t nbytes){
size_t left = nbytes;
size_t written_len = 0;
const char * p_tmp = buf;
while(left > 0){
written_len = write(fd, p_tmp,left);
if(written_len<0){
written_len = -errno;
if(written_len == EINTR || written_len == EAGAIN ){
continue;
}
if(written_len == EBADF){
//重新open 這個檔案,對它進行重寫
break;
}
//
fprintf(stderr,"write failed. reason:%s\n",strerror(errno));
return FAILURE;
}else if( 0 == written_len ){
break;
}
left -= written_len;
p_tmp += written_len;
}
if( 0 != left) return FAILURE;
return SUCCESS;
}
int main(int argc,char **argv){
int fd = 0;
fd = open("./demo.txt",O_RDWR|O_CREAT,S_IRWXU|S_IRGRP|S_IROTH);
if(fd == -1){
fprintf(stderr,"open failed.reason: %s\n",strerror(errno));
exit(-1);
}
off_t size = lseek(fd,0,SEEK_END);
char buf[BUFSIZE];
int ret = 0;
int i = 0;
memset(buf,'8',BUFSIZE);
for(i=0;i<10;i++){
ret = write_to_file(fd,buf,BUFSIZE);
if(ret == FAILURE) {
//log
break;
}
}
close(fd);
return 0;
}