8-文件IO-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 != 2) {
    printf("USAGE: fcntldemo [fd]\n");
    exit(1);
  }

  int fd = atoi(argv[1]);

  int flags = fcntl(fd, F_GETFL);

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

  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 0;   

}       

编译后执行

$ gcc fcntldemo.c -o fcntldemo
$ ./fcntldemo 5 5>>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[10];
  int len;
  int fd; 

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

  flags |= O_NONBLOCK;

  // 设置文件表中的 f_flags 成员的值
  if (fcntl(STDIN_FILENO, F_SETFL, flags) == -1) {
    perror("fcntl set");
    exit(1);
  }




  while(1) {
    len = read(STDIN_FILENO, buffer, 10);
    if (len < 0) {
      if (errno == EAGAIN) {
        write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));
        sleep(1); // 让出 CPU,避免CPU长时间空转
      }   
      else {
        perror("read");
        exit(1);
      }   
    }   
    else {
      break;
    }   
  }

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

总结

本文并没有过多的去阐释 fcntl 其它的功能,只讲解了如何使用 fcntl 函数去获取已打开文件的标志位,设置已打开文件的标志位。所以,本篇只要大家熟记 fcntl 的两个命令(fcntl的第2个参数),F_GETFL 和 F_SETFL。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值