回憶檔案表
還記得在第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。