zoukankan      html  css  js  c++  java
  • 第八章 进程间通信 [notice pipe at RIL.pdf] [popen] [mkfifo]

    前言:

    管道只能用于父子进程或兄弟进程。pipe创建管道,fork创建子进程,完全继承管道,可以理解为对同一个通道拥有读写权(见图8.4),父子进程分别关闭其中一个不同的权限,形成父读子写或父写子读的一个通道。

    popen

    mkfifo 用于命名管道,但权限问题目前还没有搞清楚,难道管道必须开放读写权限。任何进程都可以访问。

    ========================================================================

    8.1 Linux下进程间通信概述

    Linux下的进程间通信基本上是从UNIX平台继承下来的,而AT&T和BSD对UNIX都做出了重大贡献,但侧重点不同。

    1.AT&T对UNIX早期的进程间通信手段进行了系统的改进和扩充,形成了"system V IPC",其通信进程主要局限在单个计算机内。

    2.BSD跳过了单机局限,形成了套接口(socket)的进程间通信机制。

    (1+2)Linux则把两者的优势都继承了下来。

    1.UNIX进程间通信(IPC)方式包括管道、FIFO、信号。

    2.System V 进程间通信(IPC)包括System V 消息队列、System V 信号灯、System V 共享内存区。

    3.Posix 进程间通信(IPC)包括Posix消息队列、Posix信号灯、Posix共享内存区。

    现在Linux中使用较多的进程间通信方式主要有以下几种:

    (1)管道及命名管道

    (2)信号:信号是在软件层次上对中断机制的一种模拟。

    (3)消息队列:

    (4)共享内存:可以说这是最有用的进程间通信方式。这种通信方式需要依靠同步机制,如互斥锁和信号量等。

    (5)信号量:主要作为进程间以及同一进程不同线程之间的同步手段。<--第10章中单独介绍

    (6)套接字:这是一种更为一般的进程间通信机制,它可用于不同机器之间的进程间通信

    管道可用于具有亲缘关系进程间的通信

    命名管道,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。

    8.2 管道通信

    8.2.1 管道概述

    它是把一个程序的输出直接连接到另一个程序的输入。ps -ef | grep ntp

    *它只能用于父子进程或者兄弟进程

    *它是一个半双工的通信模式,具有固定的读端和写端

    *管理也可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write等函数。但它不是普通文件,并不属于其他任何文件系统,并且只存在内存中。

    8.2.2 管道的创建与关闭

    1.管道创建与关闭说明

    管理是基于文件描述符的通信方式,当一个管道建立时,它会创建两个文件描述符fds[0]和fds[1],其中fds[0]固定用于读管道,而fds[1]固定用于写管道。

    就构成了一个半双工的通道。

    管道关闭时,只需要将这两个文件描述符关闭即可,可使用普通的close函数关闭各个文件描述符

    2.管道创建函数

    pipe.c
    #include<unistd.h>
    #include<errno.h>
    #include<stdio.h>
    #include<stdlib.h>
     
    int main(){
        int pipe_fd[2];
        if(pipe(pipe_fd)<0){
            printf("pipe create errorn");
            return -1;
        } else
            printf("pipe create successn");
     
        close(pipe_fd[0]);
        close(pipe_fd[1]);
     
    }

    8.2.3管道读写

    1.管道读写说明
    用pipe函数创建的管道两端处于一个进程中,由于管道主要用于在不同进程间通信的,因此这在实际应用中没有太大意义。
    实际上,通常先创建一个管理,再通过fork()函数创建一子进程,该子进程会继承父进程所创建的管道(fork是完全复制,且有两个返回值,分别是父子进程)
    父进程fd[0]读管道,fd[1]写管道
    子进程fd[0]读管道,fd[1]写管道
    如果关闭父进程fd[1]和子进程fd[0],就在父子进程间建立一条“子进程写入父进程读”的通道
    如果关闭父进程fd[0]和子进程fd[1],就在父子进程间建立一条“父进程写,子进程读”的通道

    #include<unistd.h>
    #include<sys/types.h>
    #include<errno.h>
    #include<stdio.h>
    #include<stdlib.h>
    
    int main(){
        int pipe_fd[2];
        pid_t pid;
        char buf_r[100];
        char* p_wbuf;
        int r_num;
    
        memset(buf_r, 0, sizeof(buf_r));
        if(pipe(pipe_fd)<0){
            printf("pipe create error
    ");
            return -1; 
        }   
        printf("1   getpid() = %d
    ", getpid());
        printf("1   getppid() = %d
    ", getppid());
        if((pid = fork()) == 0){ // call fork will return twice
            printf("
    ");
            close(pipe_fd[1]);//run at child
            printf("2   getpid() = %d
    ", getpid());
            printf("2   getppid() = %d
    ", getppid());
            sleep(2);
            if((r_num=read(pipe_fd[0], buf_r, 100))>0){
                printf("%d numbers read from the pipe is %s
    ", r_num, buf_r);
            }   
            close(pipe_fd[0]);
            exit(0);
        } else if(pid > 0){ 
            close(pipe_fd[0]);//run at parent
            printf("3   getpid() = %d
    ", getpid());
            printf("3   getppid() = %d
    ", getppid());
            if(write(pipe_fd[1], "Hello", 5)!=-1)
                printf("parent write1 success!
    ");
            if(write(pipe_fd[1], " Pipe", 5)!=-1)
                printf("parent write2 success!
    ");
            close(pipe_fd[1]);
            sleep(3);
            waitpid(pid,NULL, 0); 
            exit(0);
        }
    }
    pipe_rw

    waitpid,即等到子进程pid退出后才退出。

    3.管道读写注意点

    只有在管道的读端存在时向管道中写入数据才有意义。否则,向管道中写入数据的进程将收到内核传来的SIFPIPE信号。(通常是Broken pipe错误,以后遇到Broken pipe时,可以知道,管道读端已经不存在了)。

    向管道中写入数据时,linux将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读取管道缓冲区中的数据,那么写操作将会一直阻塞。

    父子进程在运行时,它们的先后次序并不能保证,因此,在这里为了保证父进程已经关闭了读描述符,可在子进程中调用sleep函数。

    8.2.4 标准流管道

    1.标准流管道函数说明

    popen
    include<stdio.h>
    #include<unistd.h>
    #include<stdlib.h>
    #include<fcntl.h>
    #define BUFSIZE 1000
    int main(){
        FILE *fp;
        char *cmd = "ps -ef";
        char buf[BUFSIZE];
        
        if((fp = popen(cmd,"r")) == NULL)
            perror("popen");
        while((fgets(buf, BUFSIZE, fp)) != NULL)
            printf("%s", buf);
        pclose(fp);
        exit(0);
    }

    8.2.5 FIFO

    1.有名管道说明

    有名管道可以使互不相关的两个进程实现彼此通信

    该管道可以通过路径名来指出,并且在文件系统中是可见的。

    在建立了管道之后,两个进程就可以把它当作普通文件一样进程读写操作,使用非常方便。

    不过值得注意的是,FIFO是严格遵循先进先出规则的,对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾,它们不支持如lseek()等文件定位操作。

    有名管道的创建可以使用函数mkfifo(),该函数类似文件中的open()操作,可以指定管道的路径和打开的模式。

    [用户还可以在命令行使用"mknod 管道名 p"来创建有名管道]

    在创建管道成功之后,就可以使用open read write这些函数了。

    对于为读而打开的管道可在open中设置O_RDONLY,对为写而打开的管道可在open中设置O_WRONLY

    在这里与普通文件不同的是阻塞问题。在管道中读写中却有阻塞的可能,这里的非阻塞标志可以在open函数中设定为O_NONBLOCK

    对于读进程
    • 若该管道是阻塞打开,且当前 FIFO 内没有数据,则对读进程而言将一直阻塞直到有数据写入。
    • 若该管道是非阻塞打开,则不论 FIFO 内是否有数据,读进程都会立即执行读操作。
    对于写进程
    • 若该管道是阻塞打开,则写进程而言将一直阻塞直到有读进程读出数据。
    • 若该管道是非阻塞打开,则当前 FIFO 内没有读操作,写进程都会立即执行读操作。

    2. mkfifo函数格式

    3.使用实例

    #include<sys/types.h>
    #include<sys/stat.h>
    #include<errno.h>
    #include<fcntl.h>
    #include<stdio.h>
    #include<stdlib.h>
    #include<limits.h>
    #define MYFIFO  "/tmp/myfifo"
    #define MAX_BUFFER_SIZE     PIPE_BUF
    int main()
    {
      char buff[MAX_BUFFER_SIZE];
      int fd;
      int nread;
      //判断管道是否存在,如果不存在则创建
      if(access(MYFIFO, F_OK) == -1)
      {
        if((mkfifo(MYFIFO, 0666) < 0) && (errno != EEXIST))
        {
          printf("cannot creat fifo file!
    ");
          exit(1);
        }
      }
      fd = open(MYFIFO, O_RDONLY);//打开管道,只读阻塞方式
      if(fd == -1)
      {
        printf("open fifo file error!
    ");
        exit(1);
      }
      while(1)
      {
        memset(buff, 0, sizeof(buff));
        if((nread = read(fd, buff, MAX_BUFFER_SIZE)) > 0)//读管道
        {
          printf("read '%s' from FIFO
    ", buff);
        }
      }
      close(fd);//关闭
      exit(0);
    }
    read
    #include<sys/types.h>
    #include<sys/stat.h>
    #include<errno.h>
    #include<fcntl.h>
    #include<stdio.h>
    #include<stdlib.h>
    #include<limits.h>
    #define MYFIFO  "/tmp/myfifo"
    #define MAX_BUFFER_SIZE     PIPE_BUF
    int main(int argc, char* argv[])
    {
      char buff[MAX_BUFFER_SIZE];
      int fd;
      int nwrite;
      if(argc <= 1)
      {
        printf("usage: ./write string!
    ");
        exit(1);
      }
      sscanf(argv[1], "%s", buff);
      fd = open(MYFIFO, O_WRONLY);//打开管道,写阻塞方式
      if(fd == -1)
      {
        printf("open fifo file error!
    ");
        exit(1);
      }
      if((nwrite = write(fd, buff, MAX_BUFFER_SIZE)) > 0)//写管道
      {
        printf("write '%s' to FIFO!
     ", buff);
      }
      close(fd);//关闭
      exit(0);
    }
    write

    8.3 信号通信

    8.3.1信号概述

    信号是UINX中所使用的进程通信的一种最古老的方法,它是在软件层次上对中断机制的一种模拟,是一种异步通信方式。

    信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。

    它可以在任何时候发给某一进程,而无需知道该进程的状态。

  • 相关阅读:
    [Objective-C] Block实现回调和简单的学习思考
    [iOS] 输入框高度随输入内容变化
    [iOS] UICollectionView初始化滚动到中间的bug
    [iOS] UICollectionView实现图片水平滚动
    [Objective-C] id类型和instancetype类型
    [iOS]圆形进度条及计时功能
    [Java][读书笔记]多线程编程
    Flutter 不一样的跨平台解决方案
    ONLYOFFICE连接数20个限制的由来
    Android:Gradle sync failed: Another 'refresh project' task is currently running for the project
  • 原文地址:https://www.cnblogs.com/jimwind/p/2832970.html
Copyright © 2011-2022 走看看