1. 程式人生 > >檔案描述符 檔案控制代碼 重定向

檔案描述符 檔案控制代碼 重定向

一檔案相關的系統呼叫

C語言標準庫函式中有對檔案的操作

fopen(),fwrite(),fread(),fclose();等

FILE *fd=fopen("./test.txt","w");                                                                                                                      
      if(fd==NULL)
      {   
          perror("fopen");
          return 1;
      }   
      char * str=(char *)"hello world";
      fwrite(str,strlen(str),1,fd);
      fclose(fd);

作業系統中對檔案的操作有系統呼叫

open(),write(),read(),cloes()等

事實上C語言中的檔案操作函式是通過系統呼叫來實現的。

當我們開啟一個檔案時,系統會為該檔案分配一個檔案描述符,這個檔案描述符是一個比較小得整數

當作業系統建立一個程序時,會先對這個程序先進行描述,在進行組織

對程序進行描述是用一個結構體,稱為程序控制塊(PCB),對程序進行組織是用一個連結串列將這些程序控制塊鏈在一起

一個程序中可以開啟多個檔案,同樣,作業系統對檔案也是先進行描述,再進行組織

檔案描述也是用一個結構體來,對其進行組織也是用一個連結串列將其鏈上去

下面是一個簡單的例子

int fd=open("test.txt",O_WRONLY | O_CREAT,0644);                                                                                                         
    if(fd<0)
    {   
        perror("open");exit(1);
    }   
    char * str=(char *)"hello world";
    printf("fd:%d\n",fd);
    write(fd,str,strlen(str));
    close(fd);

這裡的檔案描述符打印出來是3

這是因為作業系統會為程序預設開啟三個檔案,分配三個檔案描述符0,1,2,分別是標準輸入,標準輸出,標準錯誤。

檔案描述符預設從空閒的檔案描述符中最小的開始分配

close(0);
    close(2);
    int fd_1=open("test1.txt",O_WRONLY|O_CREAT,0644);
    int fd_2=open("test2.txt",O_WRONLY|O_CREAT,0644);
    if(fd_1<0||fd_2<0)
    {   
        perror("open");exit(1);
    }   
    //這裡預設從檔案描述符中找最小的                                                                                                                         
    printf("fd_1:%d\n",fd_1);
    printf("fd_2:%d\n",fd_2);

作業系統中關於檔案的相關係統呼叫

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

ssize_t read(int fd, void *buf, size_t count);

ssize_t write(int fd, const void *buf, size_t count);

int open(const char *pathname, int flags, mode_t mode);

ssize_t read(int fd, void *buf, size_t count);

ssize_t write(int fd, const void *buf, size_t count);

這裡注意read()函式

返回值sszie_t 是一個有符號長整型,返回值大於0時,表示本次實際讀的字元個數,小於0時表示讀取失敗,等於0時表示讀到檔案的結束標誌EOF(在Linux下輸入時按下Ctrl+D可以獲取,windows下按下Ctrl+Z可以獲取)

說了這麼多次檔案描述符,那麼檔案描述符到底是什麼呢

files_struct結構體裡面的內容:

files_struct結構儲存了程序開啟的所有檔案表資料,描述一個正被開啟的檔案。Linux中一個程序最多隻能同時開啟NR_OPEN_DEFAULT個檔案,而且前三項分別設為標準輸入、標準輸出和出錯資訊輸出檔案,定義如下:

struct files_struct {  
    atomic_t        count;              //自動增量  
    struct fdtable  *fdt;  
    struct fdtable  fdtab;  
    fd_set      close_on_exec_init;     //執行exec時 需要關閉的檔案描述符初值集合  
    fd_set      open_fds_init;          //當前開啟檔案 的檔案描述符遮蔽字  
    struct file         * fd_array[NR_OPEN_DEFAULT];  
    spinlock_t      file_lock;  /* Protects concurrent writers.  Nests inside tsk->alloc_lock */  
}; 

file結構體:

檔案結構體代表一個開啟的檔案,系統中的每個開啟的檔案在核心空間都有一個關聯的struct file。它由核心在開啟檔案時建立,並傳遞給在檔案上進行操作的任何函式。在檔案的所有例項都關閉後,核心釋放這個資料結構。在核心建立和驅動原始碼中,struct file的指標通常被命名為file或filp

二、重定向

看下面程式碼:

    close(1);                                                                                                                                                
    int fd_1=open("test1.txt",O_WRONLY|O_CREAT,0644);
    if(fd_1<0)
    {
        perror("open");
        exit(1);
    }
    //這裡預設從檔案描述符中找最小的,所以這裡分配的檔案描述符為1
    printf("fd_1:%d\n",fd_1);
    //因為printf()函式底層實現還是呼叫了系統呼叫fprintf();
    //fprintf(stdout,"%d\n",fd_1)
    //所以這裡其實是將內容輸出重定向到檔案描述符為1的檔案中、
    //那麼這裡就不會看到標準輸出上與內容輸出
    //而是在我們開啟的檔案中

    int fd_1=open("test1.txt",O_WRONLY|O_CREAT,0644);
    if(fd_1<0)
    {
        perror("open");exit(1);
    }
    //這裡預設從檔案描述符中找最小的,所以這裡分配的檔案描述符為1
    printf("fd_1:%d\n",fd_1);
    //因為printf()函式底層實現還是呼叫了系統呼叫fprintf();
    //fprintf(stdout,"%d\n",fd_1)
    //所以這裡其實是將內容輸出重定向到檔案描述符為1的檔案中、
    //那麼這裡就不會看到標準輸出上與內容輸出
    //而是在我們開啟的檔案中

    //修改檔案控制代碼,完成重定向                                                                                                                                 
    //dup2(int oldfd,int newfd);這個函式的作用是用newfd是覆蓋了oldfd
    //對newfd檔案的操作其實就是oldfd的操作。
    int fd=open("test.txt",O_CREAT | O_WRONLY,0644);
    if(fd<0)
    {   
        perror("open");exit(1);
    }   
    dup2(fd,1);
    printf("fd : %d\n",fd);//就是相當於對檔案描述符為1中的問價寫其實就是對檔案描述符為fd的檔案進行操作
    const char * str="nihao shijie\n";
    write(1,str,strlen(str));
    close(fd);
//修改檔案控制代碼,完成重定向                                                                                                                                 
    //dup2(int oldfd,int newfd);這個函式的作用是用newfd是拷貝覆蓋了oldfd
    //相當於對newfd檔案的操作其實就是oldfd的操作。
    int fd=open("test.txt",O_CREAT | O_WRONLY,0644);
    if(fd<0)
    {   
        perror("open");exit(1);
    }   
    dup2(fd,1);
    printf("fd : %d\n",fd);//就是相當於對檔案描述符為1中的問價寫其實就是對檔案描述符為fd的檔案進行操作
    const char * str="nihao shijie\n";
    write(1,str,strlen(str));
    close(fd);

//三種輸出“hello world”

//多種列印hello world                                                                                                                                       
   const char * str="hello world\n";
   printf("%s",str);
   write(1,str,strlen(str));
   fprintf(stdout,"%s",str);

當我們在函式結束的時候建立一個子程序呢?

    const char * str_printf="str_printf\n";
    const char * str_fprintf="str_fprintf\n";                                                                                                                
    const char * str_write="str_write\n";
                 
    printf("%s",str_printf);
    fprintf(stdout,"%s",str_fprintf);
    write(1,str_write,strlen(str_write));

    fork(); 

緩衝區重新整理格式:

1.無緩衝(系統呼叫,write)

2.行緩衝  (顯示器)按行重新整理換緩衝區

3.全緩衝(檔案)緩衝區滿了才會進行重新整理

當然每一個程序結束會重新整理緩衝區

當我們在標準輸出上進行輸出時是按行重新整理自己的緩衝區的,其實是沒有什麼不同的,因為這裡的字串後面都加了'\n',會自己重新整理緩衝區,建立一個子程序後,子程序的緩衝區中為空

因為輸出到一個檔案中時是一種全緩衝(等到緩衝區滿了才進行重新整理)

所以當將其輸出重定向到一個檔案中的話,是一個全緩衝,父程序將字串輸出後,並沒有進行重新整理,字串仍然在父程序的緩衝區中,建立一個子程序的話,子程序會將父程序發資料進行拷貝,那麼子程序的緩衝區中就會有和父程序同樣的字串,結束時重新整理緩衝區,字串就會被重新整理到目標檔案,所以會出現兩遍printf()和fprintf()的內容列印了兩遍。write()是屬於系統呼叫,沒有緩衝區,直接輸出,所以是一遍。

上面兩個printf()函式和fprintf()函式底層都是呼叫系統呼叫write(),但是系統呼叫在使用者態是沒有緩衝區的,所以上面說的緩衝區緩衝區是C庫提供的