首页 > 技术文章 > fcntl函数介绍

hanerfan 2016-08-18 22:41 原文

  1、fcntl:manipulate file descriptor

  1)简介:fcntl(file control)函数可执行各种描述符控制操作。

#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );

  2)正确的使用方法(以设置“非阻塞”标记为例):

int flags;
// 先获取当前的flags
if ((flags = fcntl(sockfd, F_GETFL, 0)) < 0)
    // 错误处理...
flags |= O_NONBLOCK;  // 加上“非阻塞”flag
if (fcntl(sockfd, F_SETFL, flags) < 0)  // 设置新的flags
    // 错误处理...

  3)常用用法:

  (1)把一个套接字设置为非阻塞型:cmd为F_SETFL,flags“包含”O_NONBLOCK。

  (2)把一个套接字设置成一旦其状态发生变化,内核就产生一个SIGIO:cmd为F_SETFL,flags“包含”O_ASYNC。

  (3)关于套接字的当前属主。

  cmd为F_SETOWN:指定用于接收SIGIO或SIGURG信号的套接字属主(进程ID或进程组ID)。SIGIO是套接字被设置为信号驱动式I/O型后产生的,而SIGURG是在新的带外数据到达套接字时产生的。

  SIGIO与SIGURG与其他信号的不同之处在于,这两个信号仅在已使用F_SETOWN命令给相关套接字指派了属主后才会产生。F_SETOWN命令对应的arg参数是正整数时,指出接收信号的进程ID;为负整数时,其绝对值指出接收信号的进程组ID(此时整个进程组中的所有进程接收信号)。

  cmd为F_GETOWN:返回套接字的当前属主。

  (4)记录上锁。记录上锁的Posix接口是fcntl函数。

  这种情况下fcntl的arg参数为指向flock结构的指针: 

struct flock
{
    short l_type;  // F_RDLCK, F_WRLCK, F_UNLCK
    short l_whence;  // SEEK_SET, SEEK_CUR, SEEK_END
    off_t l_start;  // 相对的起始位置
    off_t l_len;  // 长度(0表示直到文件末尾)
    pid_t l_pid;  // F_GETLK返回的PID
    ...
};

  相关cmd:

  F_SETLK:获取(l_type为F_RDLCK或F_WRLCK)或释放(l_type为F_UNLCK)由arg指向的flock结构所描述的锁。如果锁无法获得,立即返回EACCES或EAGAIN。

  F_SETLKW:与F_SETLK类似。但会阻塞到能够获得锁为止。

  F_GETLK:检查由arg指向的锁以确定是否有某个已存在的锁会妨碍新锁授予调用进程。如果当前没有这样的锁存在,由arg指向的flock结构的l_type被置为F_UNLCK。否则,关于这个已存在锁的信息将在由arg指向的flock结构中返回,其中包括持有该锁的进程ID。

  这里的记录上锁是关联到某个进程的,因此:

  I、锁不能通过fork由子进程继承。

  II、如果进程关闭文件的任意一个描述符,则进程在该文件上的所有(记录)锁都被移除。

  III、进程中的线程共享了锁。因此,一个多线程程序不能使用记录上锁保证线程访问同一文件相同区域的同步性。

  其他特点:

  I、后续成功执行的F_SETLK或F_SETLKW命令会覆盖先前执行的针对同一字节范围的同样两个命令。

  II、记录上锁不应该同标准I/O函数库一起使用,因为该函数库会执行内部缓冲。当某个文件需要上锁时,为避免问题,应对它使用read/write。

  III、Posix记录上锁称为劝告性上锁。因此,一个进程可以无视一个劝告性锁而写一个读锁定文件,或读一个写锁定文件。这种锁只有在协作进程间才有用。

  Posix.1只定义劝告性上锁。有些系统(如System V)提供了另一种记录上锁,称为强制性上锁。使用强制性锁后,内核检查每个read/write,以验证其操作不会干扰由某个进程持有的某个锁,比如与某个强制性锁冲突的(阻塞式)read/write将把调用进程投入睡眠,直到该锁释放为止。

  为对某个特定文件施行强制性上锁,应满足:使用"-o mand"选项挂载文件系统;文件的组成员执行位必须关掉,且SGID位必须打开(打开文件的SUID位而不打开它的用户执行位是没有意义的,同样,打开SGID位而不打开组成员执行位也没有意义。因此,以这种方式加上的强制性上锁不会影响任何现有的用户软件。强制性上锁不需要新的系统调用)。

# 在支持强制性记录上锁的系统上
[root@localhost ~]# chmod +l test
[root@localhost ~]# ls -l test
-rw-r-lr--. 1 root root 0 Aug 29 03:09 test

  4)示例:

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        printf("usage: a.out <fd>\n");
        exit(1);
    }

    int val = 0;
    if ((val = fcntl(atoi(argv[1]), F_GETFL)) < 0)
    {
        printf("fcntl error for fd %d\n", atoi(argv[1]));
        exit(1);
    }

    switch(val & 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("invalid access mode\n");
        exit(1);
    }

    if (val & O_APPEND)
        printf(", append");
    if (val & O_NONBLOCK)
        printf(", nonblocking");
    printf("\n");

    return 0;
}

  编译成a.out,运行:

[root@localhost ~]# ./a.out 0 < /dev/tty  # 将标准输入(fd为0)重定向到/dev/tty
read only
[root@localhost ~]# ./a.out 2 2>>temp.foo  # 将标准出错重定向到temp.foo,且使用追加写模式
write only, append
[root@localhost ~]# ./a.out 5 5<>temp.foo  # 在文件描述符5上以可读可写模式打开temp.foo
read write

 

 

  参考资料:

  《UNIX网络编程 卷1:套接字联网API》

  《UNIX环境高级编程》

  《UNIX网络编程 卷2:进程间通信》

 

 

 

不断学习中。。。

推荐阅读