• 进程间通信一(管道)


    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 ");
        return -1;
      }


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

        {
          printf("%d numbers read from pipe is: %s ",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! ");
        if(write(pipe_fd[1]," PIPE",5)!=-1)
          printf("parent wirte2 succes! ");
        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> ", 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, " ", 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来改变文件的读写模式。

  • 相关阅读:
    软件工程实验三 面向对象分析与设计
    软件工程实验二 结构化分析与设计
    软件工程实验一 软件开发文档与工具安装与使用
    ATM管理系统
    举例分析流程图与活动图的区别与联系
    自动生成四则运算
    Java入门基础知识点总结(详细篇)
    数据库树状结构数据查询
    java中Date日期类型的大小比较
    文件转byte[ ]
  • 原文地址:https://www.cnblogs.com/thinkinglife/p/5492747.html
Copyright © 2020-2023  润新知