天天看點

8-檔案IO-fcntl函數回憶檔案表fcntl 函數總結

回憶檔案表

還記得在第7篇文章提到過,阻塞與非阻塞是檔案本身的屬性嗎?再回想一下,第6篇《檔案IO-lseek》中提到的一些核心資料結構,每個描述符是某個數組的一個索引,這個數組每個元素儲存了一個指向檔案表的指針。這個檔案表的結構如下。

struct file {
    unsigned short f_mode; // 檔案權限位
    unsigned short f_flags; // 檔案狀态位
    unsigned short f_count; // 引用計數
    struct m_inode * f_inode; // 檔案存在磁盤上的哪個位置等等其它資訊由這個字段來解釋
    off_t f_pos; // 目前偏移量
};
           

每次我們通過 open 函數打開一個檔案時,open 函數的第二個參數 flags 都會儲存到到這 f_flags 成員。

在上一節中,為了讓終端檔案具備 O_NONBLOCK 屬性,我們不得不重新 open 一次。有沒有更好的辦法,可以讓我們不用重新 open,直接修改這個 f_flags 的值?

答曰:有。

fcntl 函數

使用 fcntl,可以讓我們直接修改 f_flags 标志。當然了,fcntl 的功能遠遠不止這些,可是為什麼一定要一次性說完呢?不如先來兩個小例子看看,fcntl 到底是如何操縱檔案表中的成員 f_flags 的。

在給出例子前,先看一下 fcntl 擷取和設定已打開檔案的方法。

int fcntl(int fd, int cmd = F_GETFL); // 擷取檔案标志位
int fcntl(int fd, int cmd = F_SETFL, int arg); // 設定檔案标志位
           

注意第二個參數,取不同值的時候,fcntl 有着不同功能。如果失敗,fcntl 傳回 -1.

  • 例1

下面這段小程式,從參數讀取一個數字,這個數字是描述符。然後利用 fcntl 擷取這個描述符對應的檔案的标志位。

// 檔案名:fcntldemo.c
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char* argv[]) {
  if (argc != ) {
    printf("USAGE: fcntldemo [fd]\n");
    exit();
  }

  int fd = atoi(argv[]);

  int flags = fcntl(fd, F_GETFL);

  if (flags == -) {
    perror("fcntl");
    exit();
  }

  if (flags & O_RDONLY) {
    printf("O_RDONLY\n");
  }
  if (flags & O_WRONLY) {
    printf("O_WRONLY\n");
  }
  if (flags & O_RDWR) {
    printf("O_RDWR\n");
  }
  if (flags & O_NONBLOCK) {
    printf("O_NONBLOCK\n");
  }
  if (flags & O_APPEND) {
    printf("O_APPEND\n");
  }       

  return ;   

}       
           

編譯後執行

$ gcc fcntldemo.c -o fcntldemo
$ ./fcntldemo  >>test // 以追加的形式打開 test,并讓描述符 5 指向這個檔案。
           

執行後顯示

O_WRONLY
O_APPEND
           
  • 例2

下面這個例子改寫了前面非阻塞讀終端的代碼,替換了原來使用 open 的方式給終端檔案加上O_NONBLOCK标志的方法。

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h> // errno 變量的頭檔案
#include <stdlib.h>

char MSG_TRY[] =  "try again!\n";

int main() {
  char buffer[];
  int len;
  int fd; 

  // 先擷取原來的 flags 的值
  int flags = fcntl(STDIN_FILENO, F_GETFL);
  if (flags == -) {
    perror("fcntl get");
    exit();
  }

  flags |= O_NONBLOCK;

  // 設定檔案表中的 f_flags 成員的值
  if (fcntl(STDIN_FILENO, F_SETFL, flags) == -) {
    perror("fcntl set");
    exit();
  }




  while() {
    len = read(STDIN_FILENO, buffer, );
    if (len < ) {
      if (errno == EAGAIN) {
        write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));
        sleep(); // 讓出 CPU,避免CPU長時間空轉
      }   
      else {
        perror("read");
        exit();
      }   
    }   
    else {
      break;
    }   
  }

  write(STDOUT_FILENO, buffer, len);
  return ;
}
           

總結

本文并沒有過多的去闡釋 fcntl 其它的功能,隻講解了如何使用 fcntl 函數去擷取已打開檔案的标志位,設定已打開檔案的标志位。是以,本篇隻要大家熟記 fcntl 的兩個指令(fcntl的第2個參數),F_GETFL 和 F_SETFL。

繼續閱讀