Unix/Linux编程:fcntl函数总结

fcntl VS ioctl

fcntl函数,也就是file control,提供了对文件描述符的各种操作。另一个常见的控制文件描述符的属性和行为的系统调用是ioctl,而且ioctlfcntl能够执行更多的控制。但是,对于控制文件描述符常见的属性和行为,fcntl函数是由POSIX规范指定的首选方法

  • ioctl()是底层的系统调用(system call),所以跨平台特性不好。
  • 而fcntl则是被封装的函数,各个OS都是支持的。

fcntl

NAME
       fcntl - 对一个打开的文件描述符执行一系列控制操作。

SYNOPSIS
       #include <unistd.h>
       #include <fcntl.h>

       int fcntl(int fd, int cmd, ... /* arg */ );

DESCRIPTION
		fcntl() 对打开的文件描述符fd执行下面操作之一。操作由cmd决定
		
		fcntl() 可以接受可选的第三个参数。是否需要此参数由cmd确定。所需要的
			    参数类型在每个cmd名称后面的括号中表示(在大多数情况下,所需
			    的类型是int,我们使用名称arg标识参数),如果参数不是必需的,
			    则指定void。

				第三个参数以省略号来表示,这意味着可以将其设置为不同的类型,
				或者加以省略。内核会依据 cmd 参数(如果有的话)的值来确定该
				参数的数据类型
			    
		仅从特定的Linux内核版本开始,才支持以下某些操作。 检查主机内核是否
		支持特定操作的首选方法是使用所需的cmd值调用fcntl(),然后使用EINVAL
		测试调用是否失败,这表明内核无法识别该值。 
			
			。。。(待补)

返回值: 失败返回-1并设置errno


/*
* 功能: 复制一个已经有的描述符(cmd=F_DUPFD或者F_DUPFD_CLOEXEC)
* 		获取/设置文件描述符标志(cmd=F_GETFD或者F_SETFD)
* 		获取/设置文件状态标志(cmd=F_GETFL或者F_SETFL)
* 		获取/设置异步IO所有权(cmd=F_GETOWN或者F_SETOWN)
*       获取/设置记录锁(cmd=F_GETLK或者F_SETLK或者F_SETLKW)
*/

获取文件标志

fcntl()的用途之一是针对一个打开的文件,获取或者修改器访问模式和状态标志(这些值是通过指定open()调用的flag参数来设置的)。要获取这些设置,应将 fcntl()的 cmd 参数设置为F_GETFL:

if ((flags = fcntl(atoi(argv[1]), F_GETFL)) == -1){
        printf("fcntl error for fd %d", atoi(argv[1]));
        exit(EXIT_FAILURE);
    }

在上述代码之后,可以以如下代码测试文件是否以同步写方式打开:

 if (flags & O_SYNC)
        printf(", synchronous writes");

SUSv3 规定:针对一个打开的文件,只有通过 open()或后续 fcntl()的 F_SETFL 操作,才能对该文件的状态标志进行设置。然而在如下方面,Linux 实现与标准有所偏离:如果一个程序编译时采用了 5.10 节所提及的打开大文件技术,那么当使用 F_GETFL 命令获取文件状态标志时,标志中将总是包含 O_LARGEFILE 标志。

判定文件的访问模式有一点复杂,这是因为 O_RDONLY(0)、O_WRONLY(1)和 O_RDWR(2)这 3 个常量并不与打开文件状态标志中的单个比特位对应。因此,要判定访问模式,需使用掩码 O_ACCMODE 与 flag 相与,将结果与 3 个常量进行比对:

int accessMode ;
accessMode = flags & O_ACCMODE;
if(accessMode  == O_RDONLY){
	printf("read only");
}

综上所示,完整代码如下:

#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    int		flags;

    if (argc != 2){
        printf("usage: a.out <descriptor#>");
        exit(EXIT_FAILURE);
    }


// 第一个参数指定文件描述符
    if ((flags = fcntl(atoi(argv[1]), F_GETFL)) < 0){
        printf("fcntl error for fd %d", atoi(argv[1]));
        exit(EXIT_FAILURE);
    }


    switch (flags & O_ACCMODE) {
        case O_RDONLY:
            printf("read only");
            break;

        case O_WRONLY:
            printf("write only");
            break;

        case O_RDWR:
            printf("read write");
            break;

        default:
            printf("unknown access mode");
            exit(EXIT_SUCCESS);
    }

    if (flags & O_APPEND)
        printf(", append");
    if (flags & O_NONBLOCK)
        printf(", nonblocking");
    if (flags & O_SYNC)
        printf(", synchronous writes");

#if !defined(_POSIX_C_SOURCE) && defined(O_FSYNC) && (O_FSYNC != O_SYNC)
    if (val & O_FSYNC)
		printf(", synchronous writes");
#endif

    putchar('\n');
    exit(0);
}

测试

[root@localhost build]$ ./apud 0 < /dev/tty
read only
[root@localhost build]$ ./apud 1 > temp.foo
[root@localhost build]$ ./apud 2 > temp.foo
[root@localhost build]$ ./apud 2 2>>temp.foo
write only, append
[root@localhost build]$ ./apud 5 5<>temp.foo  #5<>temp.foo表示再文件描述符5上以读写的方式打开文件temp.foo
read write

修改文件的属性

可以使用 fcntl()的 F_SETFL 命令来修改打开文件的某些状态标志。允许更改的标志有
O_APPEND、O_NONBLOCK、O_NOATIME、O_ASYNC 和 O_DIRECT。系统将忽略对其他标志的修改操作。(有些其他的 UNIX 实现允许 fcntl()修改其他标志,如 O_SYNC。

使用fcntl()修改文件状态标志,尤其适用于如下场景

  • 文件不是由调用程序打开的,所以程序也无法使用open()调用来控制文件的状态标志(比如,文件时3个标准输入输出描述符中的一员,这些描述符在程序启动之前就被打开)
  • 文件描述符的获取是通过 open()之外的系统调用
    • 比如 pipe()调用,该调用创建一个管道,并返回两个文件描述符分别对应管道的两端。
    • 比如 socket()调用,该调用创建一个套接字并返回指向该套接字的文件描述符

为了修改打开文件的状态标志,可以使用 fcntl()的 F_GETFL 命令来获取当前标志的副本,然后修改需要变更的比特位,最后再次调用 fcntl()函数的 F_SETFL 命令来更新此状态标志。

设置文件属性

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
void set_fl(int fd, int flags) /* flags are file status flags to turn on */
{
    int		val;

    if ((val = fcntl(fd, F_GETFL, 0)) < 0){
        perror("fcntl F_GETFL error");
        exit(EXIT_FAILURE);
    }
        

    val |= flags;		/* turn on flags */

    if (fcntl(fd, F_SETFL, val) < 0){
        perror("fcntl F_GETFL error");
        exit(EXIT_FAILURE);
    }
}

清除文件属性

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
void clr_fl(int fd, int flags)
{
    int val;

    if ((val = fcntl(fd, F_GETFL, 0)) == -1) {
        printf("fcntl F_GETFL error\n");
        exit(EXIT_FAILURE);
    }

    val &= ~flags;

    if (fcntl(fd, F_SETFL, val) == -1) {
        printf("fcntl F_SETFL error\n");
        exit(EXIT_FAILURE);
    }
}

设置/清除文件描述符为非阻塞的

int setnonblocking(int fd){
	int old_option = fcntl(fd,  F_GETFL);  // 获取文件描述符旧的状态标志
	int new_option = old_option | O_NONBLOCK; //设置非阻塞标志
	fcntl(fd, F_SETFL, new_option);  
	return old_option;  
}

int clrnonblocking(int fd){
	int old_option = fcntl(fd,  F_GETFL);  // 获取文件描述符旧的状态标志
	int new_option = old_option &  ~O_NONBLOCK; //设置非阻塞标志
	fcntl(fd, F_SETFL, new_option);  
	return old_option;  
}

O_NONBLOCK和O_NDELAY所产生的结果都是使I/O变成非阻塞模式(non-blocking),在读取不到数据或是写入缓冲区已满会马上return,而不会阻塞等待。

它们的差别在于:在读操作时,如果读不到数据,O_NDELAY会使I/O函数马上返回0,但这又衍生出一个问题,因为读取到文件末尾(EOF)时返回的也是0,这样无法区分是哪种情况。因此,O_NONBLOCK就产生出来,它在读取不到数据时会回传-1,并且设置errno为EAGAIN。

O_NDELAY是在System V的早期版本中引入的,在编码时,还是推荐POSIX规定的O_NONBLOCK,O_NONBLOCK可以在open和fcntl时设置。

  1. acl_non_blocking.c

#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>

#define ACL_SOCKET int 
#define acl_msg_error printf
/**
 * 设置套接口为阻塞或非阻塞
 * @param fd {ACL_SOCKET} SOCKET 套接字
 * @param on {int} 是否设置该套接字为非阻塞
 * @return {int} >= 0: 成功, 返回值 > 0 表示设置之前的标志位; -1: 失败
 */
int acl_non_blocking(ACL_SOCKET fd, int on){
    int   flags;

    int   nonb = O_NONBLOCK;

    if ((flags = fcntl(fd, F_GETFL)) == -1) {
        acl_msg_error("%s(%d), %s: fcntl(%d, F_GETFL) error: %s",
                      __FILE__, __LINE__, __FUNCTION__,
                      fd, strerror(errno));
        return -1;
    }
    if (fcntl(fd, F_SETFL, on ? flags | nonb : flags & ~nonb) < 0) {
        acl_msg_error("%s(%d), %s: fcntl(%d, F_SETL, nonb) error: %s",
                      __FILE__, __LINE__, __FUNCTION__,
                      fd, strerror(errno));
        return -1;
    }
    return flags;
}

/**
 * 判断套接口为阻塞或非阻塞
 * @param fd {ACL_SOCKET} SOCKET 套接字
 * @return {int} == 1, 非阻塞; 0阻塞
 */
int acl_is_blocking(ACL_SOCKET fd)
{
    int flags;
    if ((flags = fcntl(fd, F_GETFL, 0)) == -1) {
        acl_msg_error("fcntl(fd, F_GETFL) failed");
        return -1;
    }
    return (flags & O_NONBLOCK) == 0 ? 1 : 0;
}


  1. acl_non_blocking.h
#ifndef ACL_LEARING_ACL_NON_BLOCKING_H
#define ACL_LEARING_ACL_NON_BLOCKING_H


# define	ACL_SOCKET		int

#define ACL_BLOCKING        0  /**< 阻塞读写标志位 */
#define ACL_NON_BLOCKING    1  /**< 非阻塞读写标志位 */

/**
 * 设置套接口为阻塞或非阻塞
 * @param fd {ACL_SOCKET} SOCKET 套接字
 * @param on {int} 是否设置该套接字为非阻塞, ACL_BLOCKING 或 ACL_NON_BLOCKING
 * @return {int} >= 0: 成功, 返回值 > 0 表示设置之前的标志位; -1: 失败
 */
int acl_non_blocking(ACL_SOCKET fd, int on);

/**
 * 判断所给套按口是否为阻塞模式
 * @param fd {ACL_SOCKET}  SOCKET 套接字
 * @return {int} -1 表示出错或所给参数有误或该平台不支持,1 表示所给套接字为阻塞模式
 *  0 表示所给套接字为非阻塞模式
 */
int acl_is_blocking(ACL_SOCKET fd);


#endif //ACL_LEARING_ACL_NON_BLOCKING_H

总结

fcntl函数支持的常用操作以其参数如表所示:

在这里插入图片描述
在这里插入图片描述

SIGIOSIGURG这两个信号与其他Linux信号不同,它们必须和某个文件描述符相关联方可使用:

  • 当被关联的文件描述符可读或者可写时,系统将触发SIGIO信号
  • 当被关联的文件描述符(必须是一个socket)上有带外数据可读时,系统将触发SIGURG信号

将信号和文件描述符关联的方法,就是使用fcntl函数为目标文件描述符指定宿主进程或者进程组,那么被指定的宿主进程或者进程组将会捕获这两个信号

使用SIGIO时,还需要利用fcntl设置其O_ASYNC标志(异步IO标志)

F_GETFL:

  • 返回fd对应的文件状态标志
    在这里插入图片描述
  • 10
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值