linux c管道和FIFO

2018-06-15 23:32:32

每个 shell 用户都对命令中使用管道比价熟悉,如下面这个统计一个目录中文件的数目的命令所示。

ls | wc -l

为了执行上面的命令,shell 创建了2个进程分别执行 ls、wc。(这是通过fork() 和 exec() 完成的,前面的文章有提到过)。

实际上,这2个进程并不知道管道的存在,它们只是从标准文件描述符中读取数据和写入数据。后面会介绍原理。

1.管道是一个字节流,读取它们的顺序跟写入它们的顺序是一致的。

2.从为空的管道里读取数据时会阻塞,直至有至少1个字节写入。如果管道的写入端被关闭,那么读完管道里的所有数据后会遇到文件结尾(read() 返回 0)。

3.可以确保向管道里写入不超过 PIPE_BUF 字节的操作是原子操作,在Linux上 PIPE_BUF 的值为4096。所谓的原子操作就是如果多个进程同时向一个管道写入不超过 PIPE_BUF 字节数据,它们不会出现数据交叉现象。

4.管道的容量是有限的,管道其实是一个在内核内存中维护的缓冲区,linux 上大小为65536个字节,一旦超过这个数量,那么写入的操作将会阻塞直至管道中有数据被读出。Linux上特有的fcntl() 可以修改管道的存储能力。

#修改管道的最大存储字节数
fcntl(fd, F_SETPIPE_SZ, size);

#获取管道的最大存储字节数
fcntl(fd, F_GETPIPE_SZ);


创建和使用管道

#include <unistd.h>

int pipe(int filedes[2]);

//Returns 0 on success, or –1 on error

成功调用会在数组 filedes 中返回2个打开文件描述符:filedes[0] 为读取端,filedes[1] 为写入端。管道上的 read() 调用会读取的数据量为所请求的字节数与管道当前存在的字节数两者之间较少的那个,管道为空时阻塞。

在单个进程中管道的用途不多,一般都是使用管道让进程和它的子孙进程进行通信。

int filedes[2];
if (pipe(filedes) == -1) /* Create the pipe */
    perror("pipe");

switch (fork()) {
    case -1:
        perror("fork");
    case 0:
        //子进程关闭无用的写入端
        if (close(filedes[1]) == -1)
            perror("close");
        //子进程读取
        break;
    default:
        //父进程关闭无用的读取端
        if (close(filedes[0]) == -1)
            perror("close");
        //父进程写入
        break;
}

关闭未使用的管道文件描述符

关闭未使用管道文件描述符不仅仅是为了确保进程不会耗尽其文件描述符的限制,还有其他更重要的原因。下面介绍为何必须要关闭管道的读取端和写入端的未使用文件描述符。

从管道中读取数据的进程会关闭其持有的管道的写入描述符,这样当其他进程完成输出并关闭其写入描述符之后,读者就能够看到文件结束(在读完管道中的数据之后)。如果读取进程没有关闭管道的写入端,那么在其他进程关闭了写入描述符之后,读者也不会看到文件结束,即使它读完了管道中的所有数据。相反, read() 将会阻塞以等待数据,这是因为内核知道至少还存在一个管道的写入描述符打开着,即读取进程自己打开了这个描述符。

写入进程关闭其持有的管道的读取描述符是出于不同的原因。当一个进程试图向一个管道中写入数据但没有任何进程拥有该管道的打开着的读取描述符时,内核会向写入进程发送 SIGPIPE 信号。在默认情况下,这个信号会杀死进程。但进程可以捕获或忽略该信号,这样就会导致管道的 write() 操作因 EPIPE 错误(已损坏的管道)而失败。收到 SIGPIPE 信号或得到 EPIPE 错误对于标示出管道的状态是有用的,这就是为何需要关闭管道的未使用读取描述符的原因。

如果写入进程没有关闭管道的读取端,那么即使在其他进程已经关闭了管道的读取端之后写入进程仍然能够向管道写入数据,最后写入进程会将数据充满整个管道,后续的写入请求会被永远阻塞。

关闭未使用文件描述符的最后一个原因是只有当所有进程中所有引用一个管道的文件描述符被关闭之后才会销毁该管道以及释放该管道占用的资源以供其他进程复用。此时,管道中所有未读取的数据都会丢失。

例子

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>

#include <sys/wait.h>

#define BUF_SIZE 10

int main(int argc, char *argv[]){
    int pfd[2];                             /* Pipe file descriptors */
    char buf[BUF_SIZE];
    ssize_t numRead;

    if (argc != 2 || strcmp(argv[1], "--help") == 0){
        printf("%s string\n", argv[0]);
        return 0;
    }
        
    if (pipe(pfd) == -1)
        perror("pipe");

    switch (fork()) {
    case -1:
        perror("fork");

    case 0:
        if (close(pfd[1]) == -1)
            perror("close - child");

        for (;;) {
            numRead = read(pfd[0], buf, BUF_SIZE);
            if (numRead == -1)
                perror("read");
            
            //读到EOF
            if (numRead == 0)
                break;
            
            //一次读取10个字节并输出
            if (write(STDOUT_FILENO, buf, numRead) != numRead)
                perror("child - partial/failed write");
            write(STDOUT_FILENO, "\n", 1);
        }

        write(STDOUT_FILENO, "\n", 1);
        if (close(pfd[0]) == -1)
            perror("close");
        _exit(EXIT_SUCCESS);

    default:
        if (close(pfd[0]) == -1)            /* Read end is unused */
            perror("close - parent");

        if (write(pfd[1], argv[1], strlen(argv[1])) != strlen(argv[1]))
            perror("parent - partial/failed write");

        //写完之后关闭,那么子进程就能独到EOF
        if (close(pfd[1]) == -1)
            perror("close");
        wait(NULL);
        exit(EXIT_SUCCESS);
    }
}
[root@izj6cfw9yi1iqoik31tqbgz c]# ./a.out 01234567890123456789012
0123456789
0123456789
012


使用管道来同步多个进程

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>

#define BUF_SIZE 1000
char *currTime(const char *fmt);

char * currTime(const char *format){
    static char buf[BUF_SIZE];  /* Nonreentrant */
    time_t t;
    size_t s;
    struct tm *tm;

    t = time(NULL);
    tm = localtime(&t);
    if (tm == NULL)
        return NULL;

    s = strftime(buf, BUF_SIZE, (format != NULL) ? format : "%c", tm);

    return (s == 0) ? NULL : buf;
}

int main(int argc, char *argv[]){
    int pfd[2];
    int j, dummy;

    if (argc < 2 || strcmp(argv[1], "--help") == 0){
        printf("%s sleep-time...\n", argv[0]);
        return 0;
    }
        
    //使标准输出不缓冲
    setbuf(stdout, NULL);
    
    printf("%s  Parent started\n", currTime("%T"));

    if (pipe(pfd) == -1)
        perror("pipe");

    //创建多个进程
    for (j = 1; j < argc; j++) {
        switch (fork()) {
        case -1:
            printf("fork %d", j);
            return 1;

        case 0:
            //关闭写入端
            if (close(pfd[0]) == -1)
                perror("close");

            sleep(atoi(argv[j]));

            printf("%s  Child %d (PID=%ld) closing pipe\n",
                    currTime("%T"), j, (long) getpid());
            if (close(pfd[1]) == -1)
                perror("close");

            //额外工作

            _exit(EXIT_SUCCESS);

        default: /* Parent loops to create next child */
            break;
        }
    }

    //关闭读取端
    if (close(pfd[1]) == -1)
        perror("close");
    
    //阻塞读取,当所有子进程都关闭管道时
    //read会返回0
    if (read(pfd[0], &dummy, 1) != 0){
        printf("parent didn't get EOF\n");
        return 1;
    }
        
    printf("%s  Parent ready to end\n", currTime("%T"));

    //这里父进程可以做额外的动作

    exit(EXIT_SUCCESS);
}
[root@izj6cfw9yi1iqoik31tqbgz c]# ./a.out 3 4 5
22:41:53  Parent started
22:41:56  Child 1 (PID=23342) closing pipe
22:41:57  Child 2 (PID=23343) closing pipe
22:41:58  Child 3 (PID=23344) closing pipe
22:41:58  Parent ready to end

与使用信号来同步相比,使用管道同步具备一个优势:它可以同来协调一个进程的动作使之与多个其他(相关)进程匹配。而多个(标准)信号无法排队的事实使得信号不适用于这种情形。

此外,还可以对这项技术进行扩展,即不关闭管道,每个子进程向管道写入一条包含其进程 ID 和一些状态信息的消息。或者侮个子进程可以向管进写入1个字节。父进程可以计数和分析这些消息。这种方法考虑到了子进程意外终止而不是显式地关闭管道的情形。

管道链接

下面模拟shell

ls | wc -l

例子

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>

#include <sys/wait.h>

int main(int argc, char *argv[]){
    int pfd[2];                                     /* Pipe file descriptors */

    if (pipe(pfd) == -1){
        perror("pipe");
    }

    switch (fork()) {
        case -1:
            perror("fork");

        case 0:
            //第一个子进程关闭读取端
            if (close(pfd[0]) == -1)
                perror("close 1");

            if (pfd[1] != STDOUT_FILENO) {
                //复制文件描述符,此时有2个写入端
                //STDOUT_FILENO 和 pfd[1]
                if (dup2(pfd[1], STDOUT_FILENO) == -1)
                    perror("dup2 1");
                //关闭pfd[1]
                if (close(pfd[1]) == -1)
                    perror("close 2");
            }
            
            //输出到标准输出,就是管道的写入端。
            execlp("ls", "ls", (char *) NULL);
            
            //如果能执行到这里,就代表出错了。
            perror("execlp ls");

        default:            /* Parent falls through to create next child */
            break;
    }

    switch (fork()) {
    case -1:
        perror("fork");

    case 0:
        //第二个子进程关闭写入端
        if (close(pfd[1]) == -1)
            perror("close 3");

        //复制标准输入,使管道的读取端指向标准输入。
        if (pfd[0] != STDIN_FILENO) {
            if (dup2(pfd[0], STDIN_FILENO) == -1)
                perror("dup2 2");
            if (close(pfd[0]) == -1)
                perror("close 4");
        }

        execlp("wc", "wc", "-l", (char *) NULL);
        perror("execlp wc");

    default:
        break;
    }

    /* Parent closes unused file descriptors for pipe, and waits for children */

    if (close(pfd[0]) == -1)
        perror("close 5");
    if (close(pfd[1]) == -1)
        perror("close 6");
    if (wait(NULL) == -1)
        perror("wait 1");
    if (wait(NULL) == -1)
        perror("wait 2");

    exit(EXIT_SUCCESS);
}
[root@izj6cfw9yi1iqoik31tqbgz c]# ls
a.out  freecls  libmymalloc.so  libsay.so  main.c  mymalloc.c
[root@izj6cfw9yi1iqoik31tqbgz c]# ls | wc -l
6
[root@izj6cfw9yi1iqoik31tqbgz c]# ./a.out 
6


管道与 shell 命令进行通信:popen()

管道的一个常见的用途是执行 shell 命令并读取其输出或向其发送一些输入。

#include <stdio.h>

FILE *popen(const char *command, const char *mode);

//Returns file stream, or NULL on error

int pclose(FILE *stream);

//Returns termination status of child process, or –1 on error

popen() 函数创建了一个管道,然后创建了一个子进程来执行shell,而shell又创建了一个子进程来执行 command 字符串。mode参数是一个字符串,r 代表要从管道中读数据,w 代表要从管道中写数据。由于管道是单向的,因此无法再执行的command 中进行双向通信。

例子

关于返回状态的详细解释,请参考 linux c程序的执行

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <limits.h>

void printWaitStatus(const char *msg, int status);

typedef enum { FALSE, TRUE } Boolean;
#define POPEN_FMT "/bin/ls -d %s 2> /dev/null"
#define PAT_SIZE 50
#define PCMD_BUF_SIZE (sizeof(POPEN_FMT) + PAT_SIZE)

void printWaitStatus(const char *msg, int status){
    if (msg != NULL)
        printf("%s", msg);

    if (WIFEXITED(status)) {
        printf("child exited, status=%d\n", WEXITSTATUS(status));

    } else if (WIFSIGNALED(status)) {
        printf("child killed by signal %d (%s)",
                WTERMSIG(status), strsignal(WTERMSIG(status)));
#ifdef WCOREDUMP        /* Not in SUSv3, may be absent on some systems */
        if (WCOREDUMP(status))
            printf(" (core dumped)");
#endif
        printf("\n");

    } else if (WIFSTOPPED(status)) {
        printf("child stopped by signal %d (%s)\n",
                WSTOPSIG(status), strsignal(WSTOPSIG(status)));

#ifdef WIFCONTINUED     /* SUSv3 has this, but older Linux versions and
                           some other UNIX implementations don't */
    } else if (WIFCONTINUED(status)) {
        printf("child continued\n");
#endif

    } else {            /* Should never happen */
        printf("what happened to this child? (status=%x)\n",
                (unsigned int) status);
    }
}

int main(int argc, char *argv[]){
    char pat[PAT_SIZE];                 /* Pattern for globbing */
    char popenCmd[PCMD_BUF_SIZE];
    FILE *fp;                           /* File stream returned by popen() */
    Boolean badPattern;                 /* Invalid characters in 'pat'? */
    int len, status, fileCnt, j;
    char pathname[PATH_MAX];

    for (;;) {                  /* Read pattern, display results of globbing */
        printf("pattern: ");
        fflush(stdout);
        if (fgets(pat, PAT_SIZE, stdin) == NULL)
            break;                      /* EOF */
        len = strlen(pat);
        if (len <= 1)                   /* Empty line */
            continue;

        if (pat[len - 1] == '\n')
            pat[len - 1] = '\0';

        for (j = 0, badPattern = FALSE; j < len && !badPattern; j++)
            if (!isalnum((unsigned char) pat[j]) &&
                    strchr("_*?[^-].", pat[j]) == NULL)
                badPattern = TRUE;

        if (badPattern) {
            printf("Bad pattern character: %c\n", pat[j - 1]);
            continue;
        }

        //组合成shell 命令
        snprintf(popenCmd, PCMD_BUF_SIZE, POPEN_FMT, pat);

        fp = popen(popenCmd, "r");
        if (fp == NULL) {
            printf("popen() failed\n");
            continue;
        }

        //循环读取一行内容,直到EOF
        fileCnt = 0;
        while (fgets(pathname, PATH_MAX, fp) != NULL) {
            printf("%s", pathname);
            fileCnt++;
        }

        //获取返回状态
        status = pclose(fp);
        printf("    %d matching file%s\n", fileCnt, (fileCnt != 1) ? "s" : "");
        printf("    pclose() status = %#x\n", (unsigned int) status);
        if (status != -1)
            printWaitStatus("\t", status);
    }

    exit(EXIT_SUCCESS);
}
[root@izj6cfw9yi1iqoik31tqbgz c]# ./a.out 
pattern: m*
main.c
mymalloc.c
    2 matching files
    pclose() status = 0
	child exited, status=0
pattern: ma*
main.c
    1 matching file
    pclose() status = 0
	child exited, status=0
pattern:


管道和 stdio 缓冲

由于 popen() 调用返回的文件流指针没有引用一个终端,因此 stdio 库会对这种文件流应用块缓冲(对缓冲概念不清晰的可以参考 linux文件io缓冲)。这意味着当将 mode 的值设置为 w 来调用 popen()时,在默认情况下只有当 stdio 缓冲器被充满或使用 pclose() 关闭了管道之后输出才会被发送到管道另一端的子进程。如果需要确保子进程能够立即从管进中接收数据,那么就需要定期调用 fflush() 或使用 setbuf(fp, NULL) 禁用 stdio 缓冲。当使用 pipe() 系统调用创建管道,然后使用 fdopen() 获取一个与管道的写入端对应的 stdio 流时也可以使用这项技术。

如果调用 popen() 的进程正在从管道中读取数据(即 mode 是 r ) 。在这样情况下如果子进程正在使用 stdio 库( 除非它显式地调用了 fflush() 或 setbuf() ),就没办法了。除非能修改源代码,或使用伪终端来替换管道。


FIFO--命名管道

从语义上来讲, FIFO 与管道类似,它们两者之间最大的差别在于 FIFO 在文件系统中拥有一个名称,并且其打开方式与打开一个普通文件是一样的。这样就能够将 FIFO 用于非相关进程之间的通信。

一旦打开了 FIFO ,就能在它上面使用与操作管道和其他文件的系统调用一样的 I/O 系统调用了。与管道一样, FIFO 写入端和读取端,并且从管道中读取数据的顺序与写入的顺序是一样的。 FIFO 的名称也由此而来:先入先出。 FIFO 有时候也被称为命名管道。与管道一样,当所有引用 FIFO 的描述符都被关闭之后,所有未被读取的数据会被丢弃。使用 mkfifo 命令可以在shell 中创建一个FIFO。

mkfifo [ -m mode ] pathname
[root@izj6cfw9yi1iqoik31tqbgz tmp]# mkfifo -m 666 tmp_fifo
[root@izj6cfw9yi1iqoik31tqbgz tmp]# ll
prw-rw-rw- 1 root root      0 Jun 17 07:46 tmp_fifo


创建命名管道

#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);

//Returns 0 on success, or –1 on error

一旦 FIFO 被创建,任何进程都能够打开它,只要它能够通过常规的文件权限检测。打开一个 FIFO 具备一些不子常的语义。一般来讲,使用 FIFO 时唯一明智的做法是在两端分别设置一个读取进程和一个写入进程。这样在默认情况卜,打开一个 FIFO 以便读取数据 (open() O_RDONLY 标记) 将会阻塞直到另一个进程打开FIFO以写入数据(open() O_WRONLY 标记)为止。相应地,打开一个 FIFO 以写入数据将会阻塞直到另一个进程打开 FIFO 以读取数据为止。换句话说,打开一个 FIFO 会同步读取进程和写入进程。如果一个 FIFO 的另一端己经打开(可能是因为一对进程己经打开了 FIFO 的两端),那么 open() 调用会立即成功。

在大多数 UNIX 实现(包括 Linux ),当打开一个 FIFO 时可以通过指定 O_RDWR 标记来绕过打开 FIFO时的阻塞行为。这样, open() 就会立即返回,但无法使用返回的文件描述符在 FIFO 上读取和写入数据,不推荐这样做:1.移植性问题。2.读取端永远遇不到文件结尾(因为总有一个写入端打开着等待被写入)。不想阻塞,可以在open()里加上 O_NONBLOCK 标记。


服务端-客户端-例子

//main.c
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <limits.h>
#include <signal.h>
#include <sys/stat.h>

#define SERVER_FIFO "/tmp/seqnum_sv"
#define CLIENT_FIFO_TEMPLATE "/tmp/seqnum_cl.%ld"
#define CLIENT_FIFO_NAME_LEN (sizeof(CLIENT_FIFO_TEMPLATE) + 20)

struct request {
    pid_t pid;
    int seqLen;
};

struct response {
    int seqNum;
};

int main(int argc, char *argv[]){
    int serverFd, dummyFd, clientFd;
    char clientFifo[CLIENT_FIFO_NAME_LEN];
    struct request req;
    struct response resp;
    int seqNum = 0;

    umask(0);
    if (mkfifo(SERVER_FIFO, S_IRUSR | S_IWUSR | S_IWGRP) == -1 && errno != EEXIST){
        perror("mkfifo");
    }

    serverFd = open(SERVER_FIFO, O_RDONLY);
    if (serverFd == -1){
        perror("open");
    }

    //额外打开一个写文件描述符,这样就不会遇到EOF。
    dummyFd = open(SERVER_FIFO, O_WRONLY);
    if (dummyFd == -1){
        perror("open");
    }

    if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) perror("signal");

    for (;;) {
        //从客户端接收数据
        if (read(serverFd, &req, sizeof(struct request))
                != sizeof(struct request)) {
            fprintf(stderr, "Error reading request; discarding\n");
            continue;
        }
        
        printf("receive msg from client pid:%d\n", req.pid);
        
        //生成fifo文件名
        snprintf(clientFifo, CLIENT_FIFO_NAME_LEN, CLIENT_FIFO_TEMPLATE, (long) req.pid);
        clientFd = open(clientFifo, O_WRONLY);
        if (clientFd == -1) {
            perror("open");
            continue;
        }

        resp.seqNum = seqNum;
        if (write(clientFd, &resp, sizeof(struct response))
                != sizeof(struct response))
            fprintf(stderr, "Error writing to FIFO %s\n", clientFifo);
        if (close(clientFd) == -1)
            perror("close");

        seqNum += req.seqLen;           /* Update our sequence number */
    }
}
//client.c
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <limits.h>
#include <signal.h>
#include <sys/stat.h>

#define SERVER_FIFO "/tmp/seqnum_sv"
#define CLIENT_FIFO_TEMPLATE "/tmp/seqnum_cl.%ld"
#define CLIENT_FIFO_NAME_LEN (sizeof(CLIENT_FIFO_TEMPLATE) + 20)

static char clientFifo[CLIENT_FIFO_NAME_LEN];

struct request {
    pid_t pid;
    int seqLen;
};

struct response {
    int seqNum;
};

static void removeFifo(void){
    unlink(clientFifo);
}

int main(int argc, char *argv[]){
    int serverFd, clientFd;
    struct request req;
    struct response resp;

    if (argc > 1 && strcmp(argv[1], "--help") == 0){
        printf("%s [seq-len...]\n", argv[0]);
        return 0;
    }

    umask(0);
    snprintf(clientFifo, CLIENT_FIFO_NAME_LEN, CLIENT_FIFO_TEMPLATE, (long) getpid() );
    
    //如果报fifo文件已经存在,则不处理
    if(mkfifo(clientFifo, S_IRUSR | S_IWUSR | S_IWGRP) == -1 && errno != EEXIST){
        printf("mkfifo %s", clientFifo);
        return 0;
    }

    if (atexit(removeFifo) != 0)
        perror("atexit");

    req.pid = getpid();
    req.seqLen = (argc > 1) ? atoi(argv[1]) : 1;

    //打开服务端文件描述符
    serverFd = open(SERVER_FIFO, O_WRONLY);
    if (serverFd == -1){
        printf("open %s", SERVER_FIFO);
        return 0;
    }

    //向服务端发送消息
    if (write(serverFd, &req, sizeof(struct request)) != sizeof(struct request)){
        printf("Can't write to server\n");
        return 1;
    }

    clientFd = open(clientFifo, O_RDONLY);
    if (clientFd == -1){
        printf("open %s", clientFifo);
        return 1;
    }

    //准备接收服务端消息
    if (read(clientFd, &resp, sizeof(struct response)) != sizeof(struct response)){
        printf("Can't read response from server\n");
        return 1;
    }

    printf("server response seqNum:%d\n", resp.seqNum);
    exit(EXIT_SUCCESS);
}
[root@izj6cfw9yi1iqoik31tqbgz c]# gcc main.c -o server 
[root@izj6cfw9yi1iqoik31tqbgz c]# ./server 
receive msg from client pid:26079
receive msg from client pid:26080
receive msg from client pid:26081
[root@izj6cfw9yi1iqoik31tqbgz c]# gcc client.c -o client
[root@izj6cfw9yi1iqoik31tqbgz c]# ./client
server response seqNum:0
[root@izj6cfw9yi1iqoik31tqbgz c]# ./client
server response seqNum:1
[root@izj6cfw9yi1iqoik31tqbgz c]# ./client
server response seqNum:2


非阻塞I/O

fd = open("fifopath", O_RDONLY | O_NONBLOCK);
if (fd == -1)
    perror("open");
//设置O_NONBLOCK
int flags;
flags = fcntl(fd, F_GETFL);
flags |= O_NONBLOCK;
fcntl(fd, F_SETFL, flags);
//取消O_NONBLOCK标记
flags = fcntl(fd, F_GETFL);
flags &= ~O_NONBLOCK;
fcntl(fd, F_SETFL, flags);


如果FIFO的另一端已经被打开,那么 O_NONBLOCK 对 open() 调用不会产生任何影响,它会像往常一样立即成功地打开 FIFO 。只有当 FIFO 的另一端还没有被打开的时候此标记才会起作用,而具体产生的影响则依赖于打开 FIFO 是用于读取还是用于写入的。

1.如果打开 FIFO 是为了读取,并且 FIFO 的写入端没有被打开,那么 open() 调用会立即成功(就像 FIFO 的另一端己经被打开一样)。如果打开 FIFO 是为了写入,并且还没有打开 FIFO 的另一端来读取数据,那么 open() 调用会失败,并将 errno 设置为 ENXIO。

下面是open()调用的表格展示

 打开的目的 额外标记 另一端打开 另一端关闭
 读取 无 立即成功 阻塞
 读取 O_NONBLOCK 立即成功 成功
 写入 无 立即成功 阻塞
 写入 O_NONBLOCK 立即成功 失败(ENXIO)


管道和FIFO中 read() 和 write() 总结

下图对管道和FIFO上的read()操作进行了总结,包括O_NONBLOCK 标记的作用。



备注
1.编译器版本gcc4.8,运行环境centos7 64位
2.原文地址http://www.freecls.com/a/2712/5c

 

©著作权归作者所有
收藏
推荐阅读
  • linux c共享库(动态库)高级特性(2)

    动态加载库当一个可执行文件开始运行之后,动态链接器会加载程序的动态依赖列表中的所有共享库,但有些时候延迟加载库是比较有用的,如只在需要的时候再加载一个插件。动态链接器的这项功能是通过一组 API 来实...

  • err
    linux c共享库(动态库)基础(1)

    在很多情况下,源代码文件也可以被多个程序共享。因此要降低工作量的第一步就是将这些源代码文件只编译一次,然后在需要的时候将它们链接进不同的可执行文件中。虽然这项技术能够竹省...

  • linux c进程资源

    每个进程都会消耗诸如内存和CPU时间之类的系统资源,本文将介绍与资源相关的系统调用。#include &lt;sys/resource.h&gt; int getrusage(int who, st...

  • err
    linux c使用syslog记录消息

    syslog 工具提供了一个集中式日志工具,系统中的所有应用程序都可以使用这个记录日志消息。如下图syslogd 从两个不同的源接收日志消息:一个是unix domain...

  • linux c进程调度-CPU亲和力

    进程切换 CPU 时对性能会有一定的影响:如果在原来的 CPU 的高速缓冲器中存在进程的数据,那么为了将进程的一行数据加载进新 CPU 的高速缓冲器中,首先必须使这行数据失效(即在没被修改的情况下丢弃...

  • nginx模块 ngx_http_headers_module

    ngx_http_headers_module 模块是用来增加 Expires 和 Cache-control,或者是任意的响应头。Syntax: add_header name value [alw...

  • nginx模块 ngx_http_gunzip_module、ngx_http_gzip_module、ngx_http_gzip_static_module

    ngx_http_gunzip_module 模块将文件解压缩后并在响应头加上 "Content-Encoding: gzip" 返回给客户端。为了解决客户端不支持gzip压缩。编译的时候带上 --w...

  • nginx模块 ngx_http_flv_module、ngx_http_mp4_module

    ngx_http_flv_module模块提供了对 flv 视频的伪流支持。编译的时候带上 --with-http_flv_module。它会根据指定的 start 参数来指定跳过多少字节,并在返回数...

  • nginx模块 ngx_http_fastcgi_module

    ngx_http_fastcgi_module 模块使得nginx可以与 fastcgi 服务器通信。比如目前要使得 nginx 支持 php 就得使用 fastcgi技术,在服务器上装上 nginx...

  • nginx模块 ngx_http_autoindex_module

    ngx_http_autoindex_module 模块可以将uri以 / 结尾时,列出里面的文件和目录。Syntax: autoindex on | off; Default: autoindex ...

简介
天降大任于斯人也,必先苦其心志。