进程间通讯(一).pipe

2021-09-16 09:42:42 浏览数 (1)

前言

UNIX/Linux 是多任务的操作系统,通过多个进程分别处理不同事务来实现,如果多个进程要进行协同工作或者争用同一个资源时,互相之间的通讯就很有必要了

进程间通信,Inter process communication,简称 IPC ,在 UNIX/Linux 下主要有以下几种方式:

  • 无名管道 ( pipe )
  • 有名管道 ( fifo )
  • 信号 ( signal )
  • 信号量 ( semaphore )
  • 消息队列 ( message queues )
  • 共享内存 ( shared memory )
  • 套接字 ( socket )

这里分享一下我在学习进程通讯过程中的笔记和心得


概要


IPC 方式的区别

  • 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用,进程的亲缘关系通常是指父子进程关系。
  • 有名管道 (named pipe/ fifo) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
  • 消息队列( message queue ) : 消息队列是消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
  • 信号 ( singal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
  • 信号量( semaphore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
  • 共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
  • 套接字( socket ) : 套接字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同主机间的进程通信。

管道

管道是 UNIX 系统 IPC 的最古老形式,所有 UNIX 系统都提供此种通信机制,但是管道有以下两种局限性

  • 历史上,它们是半双工的(即数据只能在一个方向上流动),现在,某些系统提供全双工管道,但是为了最佳的可移植性,我们决不应预先假定系统支持全双工管道
  • 管道只能在具有公共祖先的两个进程之间使用,通常,一个管道由一个进程创建,在进程调用fork之后,这个管道就能在父进程和子进程之间使用了

尽管有这两种局限性,半双工管道仍然是最常用的IPC形式

Tip: 每当在管道中键入一个命令序列,让shell执行时,shell都会为每一条命令单独创建一个进程,然后用管道将前一条命令进程的标准输出与后一条命令的标准输入相连接,管道是通过调用 pipe 函数创建的

下面通过一个例子,演示一下pipe管道的使用方法


代码示例

要求

创建一个从父进程到子进程的管道,并且父进程经由该管道向子进程传送数据

代码示例

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

#define MAX 1024

int main()
{
  pid_t pid=0;
  int res=-1,rb=0,wb=0,fd[2]; //定义一个整型数组来存放pipe函数的返回值,即管道的文件描述符
  char buf[MAX]; //用于临时存放数据

  memset(buf,0,sizeof(buf)); //置空缓存
  if(0 > pipe(fd)) //创建管道,失败则报错
  {
    perror("pipe");
    return res;
  }
  pid=fork(); 
  if(0 == pid)  //如果在子进程中
  {
    close(fd[1]); //关闭写端
    printf("this is child, pid is :%d, my father pid is %dn",getpid(),getppid()); 
    if (0 > (rb=read(fd[0],buf,MAX))) //从管道的读端读出数据放到buf中
    {
      perror("read");
      return res;
    }
    printf("pipe(%d):'%s': %dn",fd[0],buf,rb); //将读端的文件描述与buf的内容进行打印
    close(fd[0]); 
  }
  else if (0 < pid) //如果在父亲进程中
  {
    close(fd[0]); //关闭读端
    printf("this is the father process, my pid is :%d, child process pid is %dn",getpid(),pid);
    sprintf(buf,"pipe(%d)(%s)",fd[1],"hello pipe"); //将写端的文件描述符存到buf中,并且写入一段信息
    if (0 > (wb=write(fd[1],buf,MAX))) //将buf中的内容写到管道中
    {
      perror("write");
      return res;
    }
    close(fd[1]);
  }
  else  //fork 出错,则提醒
  {
    perror("fork"); 
    return res;
  }
  
  res=0;
  return res;
}

编译执行

代码语言:javascript复制
emacs@ubuntu:~/c$ alias  gtc
alias gtc='gcc -Wall -g -o'
emacs@ubuntu:~/c$ gtc pipe.x pipe.c
emacs@ubuntu:~/c$ ./pipe.x 
this is the father process, my pid is :10337, child process pid is 10338
this is child, pid is :10338, my father pid is 10337
pipe(3):'pipe(4)(hello pipe)': 1024
emacs@ubuntu:~/c$ 

编译执行过程中没有报错,从结果来看,符合预期

Note: 有时子进程的输出中显示父进程为1,原因是父进程先于子进程退出,这样子进程就变成了孤儿进程,孤儿进程会被init进程收养,所以父进程号就变成了1

代码语言:javascript复制
emacs@ubuntu:~/c$ ./pipe.x 
this is the father process, my pid is :10323, child process pid is 10324
emacs@ubuntu:~/c$ this is child, pid is :10324, my father pid is 1
pipe(3):'pipe(4)(hello pipe)': 1024

emacs@ubuntu:~/c$

pipe 函数原型

unistd.h 中有 pipe 函数的原型声明

代码语言:javascript复制
/* Create a one-way communication channel (pipe).
   If successful, two file descriptors are stored in PIPEDES;
   bytes written on PIPEDES[1] can be read from PIPEDES[0].
   Returns 0 if successful, -1 if not.  */
extern int pipe (int __pipedes[2]) __THROW __wur;

sprintf 函数原型

stdio.h 中有 sprintf 的函数原型声明

代码语言:javascript复制
/* Write formatted output to S.  */
extern int sprintf (char *__restrict __s,
                    __const char *__restrict __format, ...) __THROW;

总结

以下函数可以进行无名管道的创建

  • pipe

通过各方面资料弄懂其参数的意义和返回值的类型,是熟练掌握的基础

原文地址

0 人点赞