fcntl详解

前面一篇以 read 终端设备为例介绍了非阻塞I/O,为什么不直接对 STDIN_FILENO 做非阻塞 read ,而要重新 open 一遍 /dev/tty 呢?因为 STDIN_FILENO 在程序启动时已经被自动打开了,而我们需要在调用 open 时指定 O_NONBLOCK 标志。这里介绍另外一种办法,可以用 fcntl 函数改变一个已打开的文件的属性,可以重新设置读、写、追加、非阻塞等标志(这些标志称为File StatusFlag),而不必重新 open 文件
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *lock);

下面就使用fcntl改变File status flag:

#define MSG_TRY "try again\n"
int main(void)
{
    char buf[10];
    int n;
    int flags;
    flags = fcntl(STDIN_FILENO, F_GETFL); //得到file属性
    flags |= O_NONBLOCK;
    if (fcntl(STDIN_FILENO, F_SETFL, flags) == -1) //设置file属性
    { //设置失败
        perror("fcntl");
        exit(1);

    }
    tryagain:
    n = read(STDIN_FILENO, buf, 10);
    if (n < 0) {
        if (errno == EAGAIN) {
            sleep(1);
            write(STDOUT_FILENO, MSG_TRY,
                 strlen(MSG_TRY));
            goto tryagain;

        }
        perror("read stdin");
        exit(1);

    }
    write(STDOUT_FILENO, buf, n);
    return 0;

}
O_ACCMODE<0003>:读写文件操作时,用于取出flag的低2位
O_RDONLY<00>:只读打开
O_WRONLY<01>:只写打开
O_RDWR<02>:读写打开

下面可以通过fcntl查看标准输入以及输出的属性:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
int main(int argc, char *argv[])
{
    int val;
    if (argc != 2) {
        fputs("usage: a.out <descriptor#>\n", stderr);
        exit(1);

    }
    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:
        fputs("invalid access mode\n", stderr);
        exit(1);

    }
    if (val & O_APPEND)
    printf(", append");
    if (val & O_NONBLOCK)
    printf(", nonblocking");
    putchar('\n');
    return 0;

}
程序分为如下几种情况:

1.标准输入:

Shell在执行 a.out 时将它的标准输入重定向到 /dev/tty ,并且是只读的。 argv[1] 是0,因此取出文件描述符0(也就是标准输入)的File Status Flag,用掩码 O_ACCMODE 取出它的读写位,结果是 O_RDONLY 。注意,Shell的重定向语法不属于程序的命令行参数,这个命行只有两个参数, argv[0] 是"./a.out", argv[1] 是"0",重定向由Shell解释,在启动程序时已经生效,程序在运行时并不知道标准输入被重定向了。

$ ./a.out 0 < /dev/tty
read only
2.标准输出

Shell在执行 a.out 时将它的标准输出重定向到文件 temp.foo ,并且是只写的。程序取出文件描述符1的File Status Flag,发现是只写的,于是打印 write only ,但是打印不到屏幕上而是打印到 temp.foo 这个文件中了。

$ ./a.out 1 > temp.foo
$ cat temp.foo
write only
3.标准错误
$ ./a.out 2 2>>temp.foo
write only, append

Shell在执行 a.out 时将它的标准错误输出重定向到文件 temp.foo ,并且是只写和追加方式。程序取出文件描述符2的File Status Flag,发现是只写和追加方式的。
4.其他

$ ./a.out 5 5<>temp.foo
read write
Shell在执行 a.out 时在它的文件描述符5上打开文件 temp.foo ,并且是可读可写的。程序取出文件描述符5的File Status Flag,发现是可读可写的。

上面用到了一种shell重定向语法:如果在<、>、>>、<>前面添一个数字,该数字就表示在哪个文件描述符上打开文件,例如2>>temp.foo表示将标准错误输出重定向到文件temp.foo并且以追加方式写入文件,注意2和>>之间不能有空格,否则2就被解释成命令行参数了。文件描述符数字还可以出现在重定向符号右边,例如:

$ command > /dev/null 2>&1

首先将某个命令command的标准输出重定向到 /dev/null ,然后将该命令可能产生的错误信息(标准错误输出)也重定向到和标准输出(用&1标识)相同的文件,即 /dev/null ,如下图所示。



你可能感兴趣的:(fcntl详解)