1. 程式人生 > >linux檔案IO底層讀寫操作

linux檔案IO底層讀寫操作

1.底層檔案訪問

    執行中的程式成為程序,每個程序都有與之關聯的檔案描述符。

    檔案描述符 - 一些小值整數,通過他們訪問開啟的檔案或裝置。開始執行會有三個檔案描述符:

  1. 0:   標準輸入   STDIN_FILENO
  2. 1:   標準輸出   STDOUT_FILENO
  3. 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_EXCLFCNTL)功能來解決這個問題。

引數說明:

pathname - 指示準備開啟的檔案或裝置的名字;

flags    - 用於指定開啟檔案所採取的動作;

mode    - 用於指定建立檔案的許可權,O_CREATE 才使用。

flags 引數通過必需檔案訪問模式 與 其他可選模式相結合的方式來指定。 首先必須指定如下檔案訪問模式之一:

模     式

說    明

O_RDONLY

以只讀方式開啟

O_WRONLY

以只寫方式開啟

O_RDWR

以讀寫方式開啟

可選模式組合

  1. O_APPEND: 把寫入資料最佳在檔案的末尾。
  2. O_TRUNC:  開啟檔案時把檔案長度設定為零,丟棄已有的內容。
  3. O_CREAT:   如果需要,就按引數mode 中給出的訪問模式建立檔案。
  4. O_EXCL:   與O_CREAT一起使用,確保建立檔案的 原子操作。如果檔案存在,建立

               將失敗

訪問許可權的初始值

單個許可權設定:   S_I  RWX     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 可取值如下:

  1. SEEK_SET:     offset 是一個絕對位置。
  2. SEEK_CUR:    offset 是相對於當前位置的一個相對位置。
  3. 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 變數,因為它可能馬上就被下一個函式呼叫所覆蓋,即使下一個函式沒有出錯,也可能會覆蓋這個變數。

常用錯誤程式碼的取值和含義如下:

  1.  EPERM:     操作不允許
  2.  ENOENT:   檔案或目錄不存在。
  3.  EINTR:     系統呼叫被中斷。
  4.  EAGAIN:    重試,下次有可能成功!
  5.  EBADF:     檔案描述符失效或本身無效
  6.  EIO:        I/O錯誤。
  7.  EBUSY:     裝置或資源忙。
  8.  EEXIST:     檔案存在。
  9.  EINVL:      無效引數。
  10.  EMFILE:     開啟的檔案過多。
  11.  ENODEV:    裝置不存在。
  12.  EISDIR:      是一個目錄。
  13. 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;

}