前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux内核编程--管道pipe

Linux内核编程--管道pipe

作者头像
Coder-ZZ
发布2022-05-09 21:41:17
3.7K0
发布2022-05-09 21:41:17
举报
文章被收录于专栏:C/C++进阶专栏C/C++进阶专栏

管道的定义:

管道是一种进程间通信机制,也是Linux操作系统中的一种文件形式。一个进程写入管道的数据可以被另一个进程读取。数据按先进先出顺序处理。Linux有两种形式的管道文件,管道FIFO

管道提供一个单向的数据流【半双工管道】, 示意图:

管道和FIFO的异同:

管道没有名字,只能由“亲缘关系”的进程间进行通信时使用,例如父子进程间的通信。

FIFO被称为已命名管道(named pipe), 进程需要按照名称打开 FIFO。

管道或FIFO都可以使用read/write函数访问,且读写操作都是按顺序发生的,从文件的开头读取并在末尾写入(先进先出机制)。管道或 FIFO 必须同时在读写的两端打开。

对管道或FIFO,由于是半双工模式,write()函数总是往末尾添加数据,read()函数则总是从开头读出数据。如果对管道或FIFO调用lseek(), 会返回ESPIPE错误。

管道的创建:

管道由pipe函数创建

代码语言:javascript
复制
#include <unistd.h>
int pipe(int fd[2])

--创建一个管道并将管道读写端的文件描述符(分别)放入fd[0]和fd[1]
--管道成功创建时返回0

*有些版本的操作系统可以创建全双工管道,使用socketpair函数创建

管道创建的经典场景:

一个进程在它派生一个或多个子进程之前创建一个管道, 然后将管道用于父进程和子进程之间或两个兄弟进程之间的通信。

*单个进程使用管道与自己对话的场景,没有实现的意义

创建子进程的函数fork()

代码语言:javascript
复制
#include <sys/types.h>
#include <unistd.h>
pid_t fork( void )

--成功运行后,向子进程返回0,并向父进程返回子进程的进程ID

Demo1: 父进程关闭管道的读端,只往写端写入数据;子进程关闭管道的写端,只从读端读出数据

代码语言:javascript
复制
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

/*Read characters from pipe and echo them to stdout*/
void read_from_pipe(int file)
{
    FILE *stream;  /*流式文件结构体*/
    int c;
    stream = fdopen(file, "r");
    while ((c = fgetc(stream)) != EOF)
      putchar(c);
    fclose(stream);
}

/*Write some random text to the pipe*/
void write_to_pipe(int file)
{
    FILE *stream;
    stream = fdopen(file, "w");
    fprintf(stream, "hello, world!\n");
    fprintf(stream, "goodbye, world!\n");
    fclose(stream);
}

int main(void)
{
    pid_t pid;
    int mypipe[2];
    /* Create the pipe. */
    if(pipe(mypipe))
      {
        fprintf(stderr, "Pipe failed.\n");
        return EXIT_FAILURE;
      }
    /* Create the child process. */
    pid = fork();
    /*在子进程中,fork返回0*/
    if(pid == (pid_t)0)
      {
        /* This is the child process.
           Close other end first. */
        close(mypipe[1]);
        read_from_pipe(mypipe[0]);
        return EXIT_SUCCESS;
      }
    /*如果出现错误,fork返回一个负值*/
    else if(pid < (pid_t)0)
      {
        /* The fork failed. */
        fprintf(stderr, "Fork failed.\n");
        return EXIT_FAILURE;
      }
    /*在父进程中,fork返回新创建子进程的进程ID*/
    else
      {
        /* This is the parent process.
           Close other end first. */
        close(mypipe[0]);
        write_to_pipe(mypipe[1]);
        return EXIT_SUCCESS;
      }
}

管道和标准输入/输出的交互:POPEN/PCLOSE

popen()的功能是 启动另外一个进程去执行一个shell命令行,调用popen的进程为父进程,由popen启动的进程称为子进程。popen函数还创建一个管道用于父子进程间通信

代码语言:javascript
复制
#include <stdio.h>

FILE *popen(const char *command, const char *type);
--运行成功时返回新文件流,没有正常调用fork()或pipe()时返回 NULL
--popen()会调用fork()产生子进程,然后从子进程中调用/bin/sh -c来执行参数command的指令
--参数type可使用"r"代表读取,"w"代表写入
--popen()会建立管道连到子进程的标准输入/输出设备,然后返回一个文件指针

int pclose(FILE *stream);
--运行成功时返回0,失败时返回-1
--pclose()用来关闭由popen()所建立的管道及文件指针,参数stream为先前由popen()所返回的文件指针

Demo2:

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/wait.h>

int main()
{
    FILE *fpr=NULL,*fpw=NULL;
    char buf[256];
    int ret;
    fpr=popen("cat /etc/group","r");
    fpw=popen("grep root","w");
    while((ret=fread(buf,1,sizeof(buf),fpr))> 0)
    {
        fwrite(buf,1,ret,fpw);
    }
    pclose(fpr);
    pclose(fpw);

    return 0;
}

Demo3:

代码语言:javascript
复制
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

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

    //Initialize the buffer
    memset(buffer,'\0', sizeof(buffer));
    
    //Open ls and grep process
    read_fp = popen("cd /opt; ls -l", "r");
    write_fp = popen("grep rwxrwxr-x", "w");
    
    //Both processes are opened successfully
    if (read_fp && write_fp)
    {
        //read a data block
        chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);
        while (chars_read> 0)
        {
            buffer[chars_read] ='\0';
            
            //Write data to the grep process
            fwrite(buffer, sizeof(char), chars_read, write_fp);
            
            //There is still data to read, read the data cyclically, until all the data is read
            chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);
        }
        
        //Close the file stream
        pclose(read_fp);
        pclose(write_fp);
        exit(EXIT_SUCCESS);
    }
    exit(EXIT_FAILURE);
}

FIFO(named pipe)的创建

FIFO与pipe不同的是,每个FIFO有一个路径名与之关联,从而允许无亲缘关系的进程访问同一个FIFO。

FIFO可以由mkfifo()函数或者mknod函数创建

代码语言:javascript
复制
#include <sys/types.h>
#include <sys/stat.h>

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

--创建成功返回0,失败返回-1
--file_path,是路径名,也是该FIFO的名字
--mode参数,定义在了<sys/stat.h>中, 指定了FIFO的权限
--mkfifo函数已隐含了 O_CREAT | O_EXCL

创建并打开一个管道只需要调用pipe(), 创建并打开一个FIFO,需要调用mkfifo()后再调用open()

管道在所有相关进程关闭它以后自动消失。FIFO的name则需要调用unlink()才能从文件系统中删除。

Demo4:

代码语言:javascript
复制
#define _POSIX_SOURCE
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>

main() {
  char fn[]="temp.fifo";
  char out[20]="FIFO's are fun!", in[20];
  int rfd, wfd;

  if (mkfifo(fn, S_IRWXU) != 0)
    perror("mkfifo() error");
  else {
    if ((rfd = open(fn, O_RDONLY|O_NONBLOCK)) < 0)
      perror("open() error for read end");
    else {
      if ((wfd = open(fn, O_WRONLY)) < 0)
        perror("open() error for write end");
      else {
        if (write(wfd, out, strlen(out)+1) == -1)
          perror("write() error");
        else if (read(rfd, in, sizeof(in)) == -1)
          perror("read() error");
        else printf("read '%s' from the FIFO\n", in);
        close(wfd);
      }
      close(rfd);
    }
    unlink(fn);
  }
}

*为了保证进程访问管道和FIFO的原子性,需要对管道和FIFO加以限制:

OPEN_MAX: 一个进程在任意时刻打开的最大描述符数

PIPE_BUF:可原子地写往一个管道或FIFO的最大数据量

shell脚本中的管道指令:

参考阅读:

https://www.ibm.com/docs/en/

https://www.gnu.org/software/libc/manual/html_node/Pipes-and-FIFOs.html

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-03-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员与背包客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档