IPC--进程间通信一(管道)

1.什么是管道?

  管道分为无名管道和命名管道,本文中如无特殊说明均指无名管道。

  管道是Linux支持的最初Unix IPC形式之一,具有以下特点:
A.管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;
B.只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程);
C.单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中。
D.数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。

2.如何创建管道?

2.1 包含头文件

#include <unistd.h>

int pipe(int pipefd[2]);

返回值:成功,返回0,否则返回-1。

参数数组包含pipe使用的两个文件的描述符:fd[0]:读端,fd[1]:写端。

3.管道数据的读写

  管道两端可分别用描述字fd[0]以及fd[1]来描述,需要注意的是,管道的两端是固定了任务的。即一端只能用于读,由描述字fd[0]表示,称其为管道读端;另一端则只能用于写,由描述字fd[1]来表示,称其为管道写端。如果试图从管道写端读取数据,或者向管道读端写入数据都将导致错误发生。一般文件的I/O函数都可以用于管道,如close、read、write等。


从管道中读取数据:
  如果管道的写端不存在,则认为已经读到了数据的末尾,读函数返回的读出字节数为0;
当管道的写端存在时,如果请求的字节数目大于PIPE_BUF,则返回管道中现有的数据字节数,如果请求的字节数目不大于PIPE_BUF,则返回管道中现有数据字节数(此时,管道中数据量小于请求的数据量);或者返回请求的字节数(此时,管道中数据量不小于请求的数据量)

内核代码pipe.c里没有pipe的接口,而是定义成了系统调用接口sys_pipe,。内核里有如下定义

SYSCALL_DEFINE1(pipe, int __user *, fildes)
{
  return sys_pipe2(fildes, 0);
}

4.实例1

#include<unistd.h>
#include<memory.h>
#include<errno.h>
#include<stdio.h>
#include<stdlib.h>
int main()
{
  pid_t pid;

  int r_num; 

  int pipe_fd[2];
  char buf_r[100];
  char* p_wbuf;
  
  memset(buf_r,0,sizeof(buf_r));
  if(pipe(pipe_fd)<0)
  {
    printf("pipe create error\n");
    return -1;
  }


  if((pid=fork())==0)
  {
    printf("\n");
    close(pipe_fd[1]);
    sleep(1);
    if((r_num=read(pipe_fd[0],buf_r,100))>0)

    {
      printf("%d numbers read from pipe is:\n %s\n",r_num,buf_r);
    }


    close(pipe_fd[0]);
    exit(0);
  }


  else if(pid>0)
  {
    close(pipe_fd[0]);
    if(write(pipe_fd[1],"Hello",5)!=-1)
      printf("parent write success!\n");
    if(write(pipe_fd[1]," PIPE",5)!=-1)
      printf("parent wirte2 succes!\n");
    close(pipe_fd[1]);
    sleep(3);
    waitpid(pid,NULL,0);
    exit(0);

  }

}

4.1函数解释

  函数首先创建了一个管道,接着用fork创建了一个进程,在子进程中首先关闭管道的写端,再从管道中读取数据,接着关闭管道读端,最后子进程退出;在父进程中,首先关闭管道的读端,接着向管道中写入两次数据,接着关闭管道写端,等待子进程退出,最后退出程序;

4.2 fork()函数

  fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。

fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
1)在父进程中,fork返回新创建子进程的进程ID;
2)在子进程中,fork返回0;
3)如果出现错误,fork返回一个负值;

4.3程序的编译、运行

[wss@localhost pipe]$ls
pipe.c
[wss@localhost pipe]$gcc pipe.c -o pipe
[wss@localhost pipe]$ls
pipe pipe.c
[wss@localhost pipe]$./pipe
parent write success!
parent wirte2 succes!

10 numbers read from pipe is:
Hello PIPE
[wss@localhost pipe]$

5.实例2

这个例子是官方给的标准例子,程序首先创建一个管道,然后创建一个子进程,之后父进程关闭管道的读端,然后向管道写入命令行传入的参数,然后关闭管道的写端,父进程退出;在子进程中,首先关闭管道写端,接着读管道,数据读取完成,程序退出;

#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char *argv[])
{
  int pipefd[2];
  pid_t cpid;
  char buf;

  if (argc != 2)

  {
    fprintf(stderr, "Usage: %s <string>\n", argv[0]);
    exit(EXIT_FAILURE);
  }

  if (pipe(pipefd) == -1)

  {
    perror("pipe");
    exit(EXIT_FAILURE);
  }

  cpid = fork();
  if (cpid == -1)

  {
    perror("fork");
    exit(EXIT_FAILURE);
  }

  if (cpid == 0)

   {                   /* Child reads from pipe */
    close(pipefd[1]);         /* Close unused write end */

    while (read(pipefd[0], &buf, 1) > 0)
    write(STDOUT_FILENO, &buf, 1);

    write(STDOUT_FILENO, "\n", 1);
    close(pipefd[0]);
    _exit(EXIT_SUCCESS);

  }

  else

  {                  /* Parent writes argv[1] to pipe */
    close(pipefd[0]);       /* Close unused read end */
    write(pipefd[1], argv[1], strlen(argv[1]));
    close(pipefd[1]);        /* Reader will see EOF */
    wait(NULL);           /* Wait for child */
    exit(EXIT_SUCCESS);
  }
}

5.1程序的编译运行

[wss@localhost pipe2]$ls
pipe2.c
[wss@localhost pipe2]$gcc pipe2.c -o pipe2
[wss@localhost pipe2]$ls
pipe2 pipe2.c
[wss@localhost pipe2]$./pipe2 Hello-pipe
Hello-pipe
[wss@localhost pipe2]$

在Linux内核的实现中,read默认是阻塞的,write默认是非阻塞的,当然,用户也可以通过fcntl来改变文件的读写模式。

转载自: https://www.cnblogs.com/thinkinglife/p/5492747.html

猜你喜欢

转载自blog.csdn.net/baidu_38172402/article/details/88837768