zoukankan      html  css  js  c++  java
  • Unix环境高级编程(十六)进程间通信

      进程间通信(IPC)是指能在两个进程间进行数据交换的机制。现代OS都对进程有保护机制,因此两个进程不能直接交换数据,必须通过一定机制来完成。
      IPC的机制的作用:
      (1)一个软件也能更容易跟第三方软件或内核进行配合的集成,或移植.如管道,在shell 下执行 ps –aux | grep bash。
      (2)简化软件结构, 可以把一个软件划分多个进程或线程,通过IPC,集成在一起工作.如消息队列。
      (3)让操作系统各个模块交换数据,包括内核与应用程序机制。
      (4)提供进程之间或同一进程之间多线程的同步机制,如信号量。

    1、管道

      管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道 只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程)单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中。

      数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。
      管道的创建:int pipe(int fd[2]) ;
      管道的读写:管道文件也是一种文件,用write,read 即可完成读写。管道两端可分别用描述字fd[0]以及fd[1]来描述,需要注意的是,管道的两端是固定了任务的。即一端只能用于读,由描述字fd[0]表示,称其为管道读端;另一端则只能用于写,由描述字fd[1]来表示,称其为管道写端。如果试图从管道写端读取数据,或者向管道读端写入数据都将导致错误发生。
      管道的关闭:管道文件也是一种文件,因此用close关闭即可。
      管道的局限:(1)只支持单向数据流; (2)只能用于具有亲缘关系的进程之间; (3)没有名字; (4)管道的缓冲区是有限的(管道制存在于内存中,在管道创建时,为缓冲区分配一个页面大小); (5)管道所传送的是无格式字节流,这就要求管道的读出方和写入方必须事先约定好数据的格式,比如多少字节算作一个消息(或命令、或记录)等等。

    现在使用管道实现进程的同步,父进程读取子进程输入的数据、子进程读取父进程恢复的数据。实现TELL_WAIT、TELL_PARENT、TELL_CHILD、TELL_PARENT及WAIT_CHILD函数。程序如下:

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <unistd.h>
     4 #include <errno.h>
     5 #include <sys/types.h>
     6 
     7 static int fd1[2],fd2[2];
     8 
     9 void TELL_WAIT()
    10 {
    11     pipe(fd1);
    12     pipe(fd2);
    13 }
    14 
    15 void TELL_PARENT(pid_t pid)
    16 {
    17     write(fd2[1],"c",1);
    18 }
    19 void WAIT_PARENT(void)
    20 {
    21     char c;
    22     read(fd1[0],&c,1);
    23     if(c!='p')
    24     {
    25         printf("WAIT_PARENT: Incorretc data");
    26         exit(0);
    27     }
    28     else
    29       printf("Read from parent.
    ");
    30 }
    31 void TELL_CHILD(pid_t pid)
    32 {
    33     write(fd1[1],"p",1);
    34 }
    35 void WAIT_CHILD()
    36 {
    37     char c;
    38     read(fd2[0],&c,1);
    39     if(c!='c')
    40     {
    41         printf("WAIT_CHILD: Incorretc data");
    42         exit(0);
    43     }
    44     else
    45         printf("Read from child.
    ");
    46 }
    47 
    48 int main()
    49 {
    50     pid_t pid;
    51     TELL_WAIT();
    52     pid =fork();
    53     if(pid == -1)
    54     {
    55         perror("fork() error");
    56         exit(-1);
    57     }
    58     if(pid == 0)
    59     {
    60         printf("child process exec.
    ");
    61         WAIT_PARENT();
    62         TELL_PARENT(getppid());
    63     }
    64     else
    65     {
    66         printf("Parent process exec.
    ");
    67         TELL_CHILD(pid);
    68         WAIT_CHILD();
    69 
    70     }
    71     exit(0);
    72 }

    程序执行结果如下:

    popen和pclose函数

      常见的操作时创建一个管道连接到另外一个进程,然后读取其输出或向其输入端发送数据。popen和pcolse函数实现的操作是:创建一个管道,调用fork产生一个子进程,关闭管道的不使用端,执行一个shell以运行命令,然后等待命令终止。函数原型如下:

    FILE *popen(const char *command, const char *type);
    int pclose(FILE *stream);

    函数popen先执行fork,然后调用exec执行cmdstring,并且返回一个标准I/O文件指针。如果type是“r”,则文件指针连接到cmdstring的标准输出,如果type是“w”,则文件指针连接到cmdstring的标准输入。popen特别适用于构造简单的过滤程序,它变换运行命令的输入或输出。写一个程序,将标准输入复制到标准输出,复制的时候将所有的大写字母变换为小写字母,程序分为两部分,转换程序如下:

     1 #include <stdio.h>
     2 #include <ctype.h>
     3 #include <stdlib.h>
     4 int main()
     5 {
     6     int c;
     7     while((c = getchar()) != EOF)
     8     {
     9         if(isupper(c))
    10             c= tolower(c);
    11         if(putchar(c) == EOF)
    12             printf("output error");
    13         if(c=='
    ')
    14             fflush(stdout);
    15     }
    16     exit(0);
    17 }

    将可执行文件保存为change。输入输出程序如下:

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <unistd.h>
     4 #include <errno.h>
     5 #include <errno.h>
     6 
     7 #define MAXLINE 1024
     8 
     9 int main()
    10 {
    11     char    line[MAXLINE];
    12     FILE    *fpin;
    13     if((fpin = popen(".//change","r")) == NULL)
    14     {
    15         perror("popen() error");
    16         exit(-1);
    17     }
    18     for(; ;)
    19     {
    20         fputs("prompt> ",stdout);
    21         fflush(stdout);
    22         if(fgets(line,MAXLINE,fpin) == NULL)
    23             break;
    24         if(fputs(line,stdout) == EOF)
    25         {
    26             perror("fputs error to pipe");
    27         }
    28     }
    29     if(pclose(fpin) == -1)
    30     {
    31         perror("pclose() error");
    32         exit(-1);
    33     }
    34     putchar('
    ');
    35     exit(0);
    36 }

    程序执行结果如下

      协同进程:当一个进程产生某个过滤程序的输入,同时又读取该过滤程序的输出。popen只提供链接到另一个进程的标准输入或标准输出的一个单向管道,对于协同进程,则连接到另一个进程的两个单向管道,一个接到标准输入,一个接标准输出。写个程序展示一下协同进程,程序从标准输入读入两个整数,调用程序计算它们的和,然后将结果输出到标准输出。过滤程序即求和程序如下:

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <string.h>
     4 #include <fcntl.h>
     5 #include <unistd.h>
     6 #define MAXLINE 1024
     7 
     8 int main()
     9 {
    10     int n,int1,int2;
    11     char line[MAXLINE];
    12     while((n=read(STDIN_FILENO,line,MAXLINE)) > 0)
    13     {
    14         line[n] = '';
    15         if(sscanf(line,"%d%d",&int1,&int2) == 2)
    16         {
    17             sprintf(line,"%d
    ",int1+int2);
    18             n = strlen(line);
    19             if(write(STDOUT_FILENO,line,n) != n)
    20             {
    21                 perror("write() error");
    22                 exit(-1);
    23             }
    24         }
    25         else if(write(STDOUT_FILENO,"invalid arg
    ",13) != 13)
    26         {
    27             perror("write() error");
    28             exit(-1);
    29         }
    30     }
    31     exit(0);
    32 }

    编译执行保存为可执行文件为add。

    协同程序如下:

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <unistd.h>
     4 #include <errno.h>
     5 #include <signal.h>
     6 #include <string.h>
     7 #define MAXLINE 1024
     8 
     9 static void sig_pipe(int);
    10 
    11 int main()
    12 {
    13     int n,fd1[2],fd2[2];
    14     pid_t   pid;
    15     char line[MAXLINE];
    16 
    17     if(signal(SIGPIPE,sig_pipe) ==SIG_ERR)
    18     {
    19         perror("signal() error");
    20         exit(-1);
    21     }
    22     if(pipe(fd1) == -1||pipe(fd2) == -1)
    23     {
    24         perror("pipe() error");
    25         exit(-1);
    26     }
    27     if((pid =fork()) == -1)
    28     {
    29         perror("fork() error");
    30         exit(-1);
    31     }
    32     if(pid == 0)
    33     {
    34         close(fd1[1]);
    35         close(fd2[0]);
    36         if(fd1[0] != STDIN_FILENO)
    37             if(dup2(fd1[0],STDIN_FILENO) != STDIN_FILENO)
    38             {
    39                 perror("dup2 error in stdin");
    40                 close(fd1[0]);
    41                 exit(-1);
    42             };
    43         if(fd2[1] != STDOUT_FILENO)
    44             if(dup2(fd2[1],STDOUT_FILENO) != STDOUT_FILENO)
    45             {
    46                 perror("dup2 error in stdout");
    47                 close(fd2[1]);
    48                 exit(-1);
    49             };
    50         if(execl(".//add","add",(char *)0) == -1)
    51         {
    52             perror("execl() error");
    53             exit(-1);
    54         }
    55     }
    56     else
    57     {
    58         close(fd1[0]);
    59         close(fd2[1]);
    60         printf("Enter two number: ");
    61         while(fgets(line,MAXLINE,stdin) != NULL)
    62         {
    63             n = strlen(line);
    64             if(write(fd1[1],line,n) != n)
    65             {
    66                 perror("write errot to pipe");
    67                 exit(-1);
    68             }
    69             if((n=read(fd2[0],line,MAXLINE)) ==-1)
    70             {
    71                 perror("read error to pipe");
    72                 exit(-1);
    73             }
    74             if(n== 0)
    75             {
    76                 printf("child close pipe.
    ");
    77                 break;
    78             }
    79             line[n] = '';
    80             printf("The result is: ");
    81             fputs(line,stdout);
    82         }
    83     }
    84 }
    85 
    86 static void sig_pipe(int signo)
    87 {
    88     printf("SIGPIPE caught
    ");
    89     exit(1);
    90 }

    程序执行结果如下:

    2、FIFO

      FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信。FIFO严格遵循先进先出(first in first out),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek()等文件定位操作。

    命名管道的命名管道创建:int mkfifo(const char * pathname, mode_t mode) 。
    命名管道的打开:命名管道比管道多了一个打开操作:open ,在open时,用O_NONBLOCK 标志表示非阻塞模式,如fd=open(“/tmp/fifo”,O_RDONLY|O_NONBLOCK,0)。
    命名管道的读入:read 读取管道数据,读取分为阻塞和非阻塞模式,阻塞模式下,如果没有数据被入,进程会在read处停下来.直到有新数据被写入,或管道被关闭,才会继续。
    命名管道的写入:write 写入管道数据,PIPE_BUF表示一次触发管道读操作最大长度.如果每次写入数据长于PIPE_BUF ,write将会多次触发read 操作。
    命名管道的关闭:管道文件也是一种文件,因此用close关闭即可。

    FIFO的两种用途:

    (1)FIFO有shell命令使用以便将数据从一条管道线传送到另一条,为此无需创建中间临时文件。

    (2)FIFO用于客户进程—服务器进程应用程序中,以在客户进程和服务器进程之间传递数据。

    3、XSI IPC

     消息队列、信号量、共享存储区相似的特征如下:具有标识符和键,标识符是IPC对象的内部名,每个IPC对象都与一个键相关联,创建IPC结构需要指定一个键,键的数据类型为key_t。每个IPC都设置了权限结构。

      优点及缺点:IPC结构是在系统范围内起作用,没有访问计数。在文件系统中没有名字,不使用文件描述符,不能对它们使用多路转接I/O函数。优点:可靠、流是受控的,面向记录、可以用非先进先出方式处理。

      消息队列(Messge Queue):消息队列是消息的链接表,包括Posix消息队列SystemV消息队列。它克服了前两种通信方式中信息量有限的缺点,具有写权限的进程可以按照一定的规则向消息队列中添加新消息;对消息队列有读权限的进程则可以从消息队列中读取消息。
      信号量(Semaphore):主要作为进程之间以及同一进程的不同线程之间的同步和互斥手段。
      共享内存(Shared memory):可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种通信方式需要依靠某种同步机制,如互斥锁和信号量等。
     
    建议:要学会使用管道和FIFO,因为大量应用程序中仍可以有效地使用这两种技术,在新的应用程序中,要尽可能避免使用消息队列及信号量,应当考虑全双工管道和记录锁。
  • 相关阅读:
    web自动化中的三种切换---alert弹框切换
    web自动化中的三种切换--窗口切换
    web自动化中的三种切换---iframe
    web元素定位中的三种等待方法
    web自动化浏览器chrome和驱动chromedriver
    selenium安装
    pytest用例标记规则
    键盘事件
    鼠标事件
    控制浏览器
  • 原文地址:https://www.cnblogs.com/alantu2018/p/8466148.html
Copyright © 2011-2022 走看看