linux系统——管道

13 篇文章 0 订阅

一、管道基础

从一个进程连接数据到另一个进程时,使用管道(pipe),通常是把一个进程的输出通过管道连接到另一个进程的输入, linux的bash就是通过管道连接的
例如:ps |grep hh,键盘敲入ps命令,ps执行后的输出作为grep的输入,执行后输出到终端屏幕

二、进程匿名管道——poen调用

1、相关函数

2、poen的实现原理

请求popen调用执行另外一个程序的时候,首先启动的是shell,即系统中的sh,之后把参数传给它
好处:所以的参数都是shell来解析的,因此可以使shell得到拓展(如*.c所指的就是所有c文件)
坏处:针对每个popen调用,要启动一个被请求的程序,还要启动一个shell,每个popen调用都多启动两个进程,从系统资源来说成本较高,调用效率也略低

3、popen例子

在一个popen调用中使用了cat程序、wc程序、shell程序,并进行了异常重定向,但是只能看到最终popen调用的输出结果

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main()
{
    FILE *read_fp;
    char buffer[BUFSIZ + 1];
    int chars_read;

    memset(buffer, '\0', sizeof(buffer));
    read_fp = popen("cat popen*.c | wc -l", "r");//以读的方式打开管道,此时调用进程可通过fread()读取调用线程的数据
    //因为不知道要读取的数据的大小,因此循环使用fread函数进行读取,直到fread函数返回的读取的数据为0
    if (read_fp != NULL) {
        chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);
        while (chars_read > 0) {
            buffer[chars_read - 1] = '\0';
            printf("Reading:-\n %s\n", buffer);
            chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);
        }
        pclose(read_fp);
        exit(EXIT_SUCCESS);
    }
    exit(EXIT_FAILURE);
}

三、进程匿名管道——pipe调用

比popen调用更底层,对读写数据更多控制

1、相关函数

2、pipe例子——单进程例子下的管道

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main()
{
    int data_processed;
    int file_pipes[2];
    const char some_data[] = "123";
    char buffer[BUFSIZ + 1];

    memset(buffer, '\0', sizeof(buffer));

    if (pipe(file_pipes) == 0) {
        data_processed = write(file_pipes[1], some_data, strlen(some_data));//从1端写数据
        printf("Wrote %d bytes\n", data_processed);
        data_processed = read(file_pipes[0], buffer, BUFSIZ);//从0端读数据,数据符合先进先出的FIFO规则
        printf("Read %d bytes: %s\n", data_processed, buffer);
        exit(EXIT_SUCCESS);
    }
    exit(EXIT_FAILURE);
}

3、pipe管道——主进程和分离进程间的管道

fork()创建的进程后,新进程的许多属性跟原进程是相同的,新进程几乎跟原进程一模一样,但是有自己的数据空间、环境和文件描述符
父进程的原来数据空间、环境和文件描述符依然存在

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main()
{
    int data_processed;
    int file_pipes[2];
    const char some_data[] = "123";
    char buffer[BUFSIZ + 1];
    pid_t fork_result;

    memset(buffer, '\0', sizeof(buffer));

    if (pipe(file_pipes) == 0) {//主进程中创建管道
        fork_result = fork();//创建一个分离的子进程
        if (fork_result == -1) {
            fprintf(stderr, "Fork failure");
            exit(EXIT_FAILURE);
        }

// We've made sure the fork worked, so if fork_result equals zero, we're in the child process.

        if (fork_result == 0) {//子进程中动作——读
            data_processed = read(file_pipes[0], buffer, BUFSIZ);
            printf("Read %d bytes: %s\n", data_processed, buffer);
            exit(EXIT_SUCCESS);
        }
// Otherwise, we must be the parent process.
        else {//父进程中动作——写
            data_processed = write(file_pipes[1], some_data,
                                   strlen(some_data));
            printf("Wrote %d bytes\n", data_processed);
        }
    }
    exit(EXIT_SUCCESS);
}

4、pipe管道——主进程和其他进程之间的管道

先用fork()创建一个跟主进程分离开的子进程,再在子进程中使用exec系列函数,从子进程中创建一个复制进程,
此时的情况是:子进程已被exec创建的进程替代了,要想在复制进程中使用主进程的数据,该数据必须作为参数,在exec创建进程的时候传递过去
/程序1——生产者/

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main()
{
    int data_processed;
    int file_pipes[2];
    const char some_data[] = "123";
    char buffer[BUFSIZ + 1];
    pid_t fork_result;

    memset(buffer, '\0', sizeof(buffer));

    if (pipe(file_pipes) == 0) {//程序1的主进程创建管道
        fork_result = fork();
        if (fork_result == (pid_t)-1) {
            fprintf(stderr, "Fork failure");
            exit(EXIT_FAILURE);
        }

        if (fork_result == 0) {//已经在由fork创建的相对独立的进程内,此时再调用execl函数替换进程为Ppip4
            sprintf(buffer, "%d", file_pipes[0]);
            (void)execl("pipe4", "pipe4", buffer, (char *)0);//子进程内调用execl函数创建一个子进程的复制进程
            exit(EXIT_FAILURE);
        }
        else {
            data_processed = write(file_pipes[1], some_data,
                                   strlen(some_data));//程序1的父进程动作,向管道写数据
            printf("%d - wrote %d bytes\n", getpid(), data_processed);
        }
    }
    exit(EXIT_SUCCESS);
}

/程序2——消费者 程序名:pipe4/

// The 'consumer' program, pipe4.c, that reads the data is much simpler.

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    int data_processed;
    char buffer[BUFSIZ + 1];
    int file_descriptor;

    memset(buffer, '\0', sizeof(buffer));
    sscanf(argv[1], "%d", &file_descriptor);
    data_processed = read(file_descriptor, buffer, BUFSIZ);//程序2的动作,从管道中读数据

    printf("%d - read %d bytes: %s\n", getpid(), data_processed, buffer);
    exit(EXIT_SUCCESS);
}

5、管道关闭后的读操作

6、把管道作为标准输入和标准输出

在这里插入图片描述

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main()
{
    int data_processed;
    int file_pipes[2];
    const char some_data[] = "123";
    pid_t fork_result;

    if (pipe(file_pipes) == 0) {//创建管道,父子进程共4个文件描述符,t file_pipes[2]各有一份副本在父子进程
        fork_result = fork();//创建分离进程
        if (fork_result == (pid_t)-1) {
            fprintf(stderr, "Fork failure");
            exit(EXIT_FAILURE);
        }

        if (fork_result == (pid_t)0) {//在子进程中
            close(0);//关闭标准输入
            dup(file_pipes[0]);//打开一个新的文件描述符,该dup()函数总取最小的可用值,因此管道输入——》标准输入
            close(file_pipes[0]);//关闭管道原来用来读取的数据的文件描述
            close(file_pipes[1]);//因为子进程中不会向管道写输入,故关闭

            execlp("od", "od", "-c", (char *)0);
            exit(EXIT_FAILURE);
        }
        else {//现在在父进程中
            close(file_pipes[0]);//父进程不会从管道读取数据,因此关闭管道读取端
            data_processed = write(file_pipes[1], some_data,
                                   strlen(some_data));
            close(file_pipes[1]);//父进程向管道写完数据后,关闭管道写入端
            printf("%d - wrote %d bytes\n", (int)getpid(), data_processed);
        }
    }
    exit(EXIT_SUCCESS);
}

图示如下:
在这里插入图片描述

四、命名管道——FIFO

主要用在不相关的进程间交换数据

1、创建FIFO文件

使用函数创建的是一个特殊的文件,文件的模式受umask影响mode_t mode=0777,umask=0022,则创建的特殊文件的权限值为755

2、访问FIFO文件


例子如下:
程序中没有删除创建的文件,时因为我们无法知道是否有其他程序正在使用它

// Let's start with the header files, a #define and the check that the correct number
// of command-line arguments have been supplied.

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

#define FIFO_NAME "/tmp/my_fifo"

int main(int argc, char *argv[])
{
    int res;
    int open_mode = 0;
    int i;

    if (argc < 2) {
        fprintf(stderr, "Usage: %s <some combination of\
               O_RDONLY O_WRONLY O_NONBLOCK>\n", *argv);
        exit(EXIT_FAILURE);
    }

// Assuming that the program passed the test, we now set the value of open_mode
// from those arguments.

    for(i = 1; i < argc; i++) {
        if (strncmp(*++argv, "O_RDONLY", 8) == 0)
             open_mode |= O_RDONLY;
        if (strncmp(*argv, "O_WRONLY", 8) == 0)
             open_mode |= O_WRONLY;
        if (strncmp(*argv, "O_NONBLOCK", 10) == 0)
             open_mode |= O_NONBLOCK;
     }

// We now check whether the FIFO exists and create it if necessary.
// Then the FIFO is opened and output given to that effect while the program
// catches forty winks. Last of all, the FIFO is closed.

    if (access(FIFO_NAME, F_OK) == -1) {
        res = mkfifo(FIFO_NAME, 0777);//创建命令管道文件
        if (res != 0) {
            fprintf(stderr, "Could not create fifo %s\n", FIFO_NAME);
            exit(EXIT_FAILURE);
        }
    }

    printf("Process %d opening FIFO\n", getpid());
    res = open(FIFO_NAME, open_mode);//打开命名管道文件
    printf("Process %d result %d\n", getpid(), res);
    sleep(5);
    if (res != -1) (void)close(res);//关闭命名管道文件
    printf("Process %d finished\n", getpid());
    exit(EXIT_SUCCESS);
 }
使用方式1:
//以读的方式打开命名管道,open调用将阻塞,直到另一个进程已写方式打开命名管道
$./fifo2 O_RDONLY &
//以写的方式打开命名管道,open函数将阻塞,直到另一个进程以读方式打开命名管道
$./fifo2 O_WDONLY

使用方式2:
//读方式+标志模式打开管道,open函数不会阻塞
$./fifo2 O_RDONLY O_NONBLOCK &
$./fifo2 O_WDONLY

使用方式3:
//写方式+标志模式打开管道
$./fifo2 O_WDONLY O_NONBLOCK &
$./fifo2 O_RDONLY

3、O_NONBLOCK对FIFO文件的读写有影响

在这里插入图片描述
程序例子:

//生产者程序fifo3.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>

#define FIFO_NAME "/tmp/my_fifo"
#define BUFFER_SIZE PIPE_BUF                 //定义在limit.h中,在linux和unix系统中一般是4096
#define TEN_MEG (1024 * 1024 * 10)            //单次进入管道的长度

int main()
{
    int pipe_fd;
    int res;
    int open_mode = O_WRONLY;
    int bytes_sent = 0;
    char buffer[BUFFER_SIZE + 1];

    if (access(FIFO_NAME, F_OK) == -1) {//检测FIFO管道文件是否存在
        res = mkfifo(FIFO_NAME, 0777);//若不存在则创建管道文件
        if (res != 0) {
            fprintf(stderr, "Could not create fifo %s\n", FIFO_NAME);
            exit(EXIT_FAILURE);
        }
    }

    printf("Process %d opening FIFO O_WRONLY\n", getpid());
    pipe_fd = open(FIFO_NAME, open_mode);//以读的方式打开管道,此时阻塞直到有进程以写方式打开管道
    printf("Process %d result %d\n", getpid(), pipe_fd);

    if (pipe_fd != -1) {
        while(bytes_sent < TEN_MEG) {
            res = write(pipe_fd, buffer, BUFFER_SIZE);
            if (res == -1) {
                fprintf(stderr, "Write error on pipe\n");
                exit(EXIT_FAILURE);
            }
            bytes_sent += res;
        }
        (void)close(pipe_fd); 
    }
    else {
        exit(EXIT_FAILURE);        
    }

    printf("Process %d finished\n", getpid());
    exit(EXIT_SUCCESS);
}
//消费者程序fifo4.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>

#define FIFO_NAME "/tmp/my_fifo"
#define BUFFER_SIZE PIPE_BUF

int main()
{
    int pipe_fd;
    int res;
    int open_mode = O_RDONLY;
    char buffer[BUFFER_SIZE + 1];
    int bytes_read = 0;

    memset(buffer, '\0', sizeof(buffer));
    
    printf("Process %d opening FIFO O_RDONLY\n", getpid());
    pipe_fd = open(FIFO_NAME, open_mode);//以读的方式打开管道,阻塞,直到有进程以写的方式打开管道
    printf("Process %d result %d\n", getpid(), pipe_fd);

    if (pipe_fd != -1) {
        do {
            res = read(pipe_fd, buffer, BUFFER_SIZE);
            bytes_read += res;
        } while (res > 0);
        (void)close(pipe_fd);
    }
    else {
        exit(EXIT_FAILURE);
    }

    printf("Process %d finished, %d bytes read\n", getpid(), bytes_read);
    exit(EXIT_SUCCESS);
}
  • 11
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值