Linux下的进程间通信(一)

一、     Linux进程间通信概述

主要分为以下几种:管道(无名管道pipe和命名管道FIFO)、信号(signal)、消息队列、共享内存、信号量、套接字(socket)等。

主要分为以下4个领域

(1)消息传递(管道,FIFO,消息队列)

(2)同步(互斥锁,条件变量,读写锁,信号量)

(3)共享内存区(匿名共享内存区,有名共享内存区)

(4)过程调用(Solaris门,Sun RPC)

二、     无名管道PIPE

普通Linux都允许重定向,而重定向就是使用的管道。管道是单向的,先进先出,固定大小,一个进程向管道里进行输入,另一个进程从管道里获取输出。一旦数据被读出,则从管道里面删除,其它进程无法再读到该数据。

管道采用了简单的流控制模式,进程在读空管道时,会一直阻塞到有进程对管道进行写时;同样,进行在写一个已满的管道时,会阻塞到有进程将管道数据取出时。

1.主要函数

l  int pipe(int fd[2])

返回值:如果成功,返回0;否则返回-1

errno=EMFILE(没有空亲的文件描述符)

EMFILE(系统文件表已满)

      EFAULT(fd数组无效)
注意:fd[0]用于读取管道,fd[1]用于写入管道。

l  int read(int fd, char *buf, int len)

int write(int fd, char *buf, int len)

返回值:如果成功,则返回读出或写入的字节数;否则,返回-1

l  FILE *popen(const char *command, const char *type)

与linux中文件操作有文件流的标准I/O一样,管道的操作也支持基于文件流的模式。

返回值:如果成功,返回一个新的文件流;否则返回NULL

参数:command为输入的命令,type为r或w,表示读或写,不能同时为读写。如果输入rw,则只读取第一个字符作为type,即为读。

l  int pclose(FILE *stream)

返回值:返回系统调用wait4()的状态。如果stream无效或调用wait4()失败,则返回1。

2. 实例

(1)管道实例

#include <iostream.h>

#include <stdlib.h>

#include <unistd.h>

#include <stdio.h>

#include <errno.h>

#include <sys/types.h>

#include <sys/wait.h>

      

using namespace std;

 

int main(int argc, char *argv[])

{

         int pipe_fd1[2], pipe_fd2[2];

         pid_t pid;

         char buf_r[100], buf_pr[100];

         char* p_wbuf;

         int r_num, pr_num;

         if(pipe(pipe_fd1) < 0 || pipe(pipe_fd2) < 0)

         {

                 cout<<"Create pipe failed!"<<endl;

                 return -1;

         }

        

         if((pid = fork()) < 0)

         {

                 cout<<"Create process failed!"<<endl;

                 return -1;

         }

         else if(pid == 0)         //child

         {

                 close(pipe_fd1[1]);

                 if((r_num=read(pipe_fd1[0],buf_r,100))>0){

                          cout<<"Child receive : "<<buf_r<<endl;

                          close(pipe_fd2[0]);

                          if(write(pipe_fd2[1], "I receive the data", strlen("I receive the data")) < 0)

                                   cout<<"child write failed"<<endl;

                          close(pipe_fd2[1]);

                 }

                 else

                 {

                          close(pipe_fd2[0]);

                          if(write(pipe_fd2[1], "I cannot receive the data", strlen("I cannot receive the data")) < 0)

                                   cout<<"child write failed"<<endl;

                          close(pipe_fd2[1]);

                 }

                 close(pipe_fd1[0]);

                 exit(0);

         }

         else                                        //parent

         {

                 close(pipe_fd1[0]);

                 if(write(pipe_fd1[1], "Hello child, I am parent!", strlen("Hello child, I am parent!")) < 0)

                          cout<<"parent write failed"<<endl;

                 close(pipe_fd1[1]);

                 close(pipe_fd2[1]);

                  if((pr_num=read(pipe_fd2[0],buf_pr,100))>0){

                          cout<<"Parent receive : "<<buf_pr<<endl;

                 }

                 close(pipe_fd2[0]);

                 waitpid(pid, NULL, 0);

                 exit(0);

         }

         return 0;

}

(2)popen实例

#include <iostream.h>

#include <stdlib.h>

#include <unistd.h>

#include <stdio.h>

#include <fcntl.h>

 

#define BUFSIZE 1024

 

int main(int argc, char *argv[])

{

         FILE *fp = NULL;

         char *cmd = "ps -ef";

         char buf[BUFSIZE];

         buf[BUFSIZE]='\0';

         if((fp = popen(cmd, "r")) < 0)

         {

                 cout<<"popen failed!"<<endl;

                 return -1;

         }

         while(fgets(buf, BUFSIZE, fp) != NULL)

         {

                 cout<<buf<<endl;

         }

         pclose(fp);

         exit(0);

}

3.协同进程

定义:当一个程序产生某个过滤程序的输入,同时又读取该过滤程序的输出,该过滤程序就是协同进程。

 

图:协同进程

       实例的第一个例子中的子进程就是一个协同进程。

三、     有名管道FIFO

FIFO有时被称为命名管道。管道(无名管道)只能由相关进程使用,这些相关进程的共同祖先创建了管道。但是,FIFO可以使不相干的进程也能交换数据。

FIFO其实也是一种文件类型,存在于文件系统中,所以创建FIFO类似于创建一个文件。

当共享管道的进程执行完所有的I/O操作以后,命名管道将继续保存在文件系统中以便以后使用。

1.函数

Int mkfifo(const char *pathname, mode_t mode);

头文件:#include <sys/stat.h>

参数:mode与open函数中的mode相同,有各种权限。

返回值:如果创建成功,则返回0;否则返回-1

一旦已经用mkfifo创建了一个FIFO,就可以用open打开它。一般的文件操作函数都能够用于FIFO(close,read,write和unlink等)。

当打开一个FIFO时,非阻塞标志(O_NONBLOCK)产生下列影响:

ü  没有指定O_NONBLOCK(一般情况下),只读open要阻塞到某个进程以写的方式打开此FIFO时,同样,只写open要阻塞到某个进程以读的方式打开此FIFO。

ü  如果指定O_NONBLOCK,则只读open立即返回;而如果没有进程已经为读打开一个FIFO,那么只写open将会出错,返回-1,errno是ENXIO。

类似管道PIPE,如果用write写一个尚无进程为读而打开的FIFO,则产生一个SIGPIPE信号。若某个FIFO的最后一个写进程关闭该FIFO,则将为该FIFO的读进程产生一个文件结束标识。

      一个FIFO可能有多个写进程,这可能会产生数据的穿插,可以用原子写操作来避免这个问题。

2.实例

(1)    用FIFO复制输出流

FIFO可被用于复制串行管道命令之间的输出流,这样就不需要写数据到临时磁盘文件中。

大家都知道,linux管道命令只能有一个进程来接收上一个进程的输出作为输入,如果有两个以上进程需要上一个进程的输出作为输入呢?这时就可以用FIFO了。

图:a为目标,b为通过FIFO实现目标

# mkfifo fifo1

# wc –l < fifo1 &

# ls -al | tee fifo1 | grep fifo1

注意:wc -l < fifo1(FIFO的读操作)一定要写向fifo1写操作的前面,否则会阻塞住。

(2)    客户进程-服务器进程使用FIFO进行通信

FIFO的另一个应用是在客户端进程和服务器进程之间传送数据。多个客户进程同时向一个FIFO中进行写操作,即向服务器进程发送请求。如果写入长度小于PIPE_BUF字节,则无需担心数据的交错;否则则需要采用原子写操作。

图:客户进程-服务器进程使用FIFO进行通信

这种设计是有缺陷的,一旦客户进程退出,很有可能留下一个只有写进程而没有读进程的客户专用FIFO。另外,如果服务进程只对众所周知的FIFO只读,那么,当客户进程减到0时,服务进程将读到文件结束标识,从而结束对FIFO的监听,解决办法是服务进程对众所周知的FIFO有读-写方式。

(1)代码-一个进程向FIFO写数据,另一个进程从FIFO读数据

write_fifo.cpp

#include<sys/types.h>
#include<sys/stat.h>
#include<errno.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include <iostream.h>

#define FIFO "/tmp/myfifo" 

using namespace std;

int main(int argc, char *argv[])
{
         char *buf = "Hello, I am a writer for FIFO!";
         int fd;
         int nwrite = 0;
         if((mkfifo(FIFO,O_CREAT|O_EXCL) < 0) && (errno!=EEXIST))
         {
                 cout<<"Create FIFO failed and the FIFO is not existed!"<<endl;
                 return -1;
         }

         if((fd = open(FIFO, O_WRONLY,0777)) == -1)
         {
                 cout<<"Open failed!"<<endl;
                 cout<<"Errno is "<<errno<<endl;
                 return -1;      
         }
         if((nwrite = write(fd, buf, strlen(buf))) < 0)
         {
                 cout<<"Write Failed!"<<endl;
                 return -1;
         }
         cout<<"Write:"<<buf<<endl;
         return 0;
}

 read_fifo.cpp

#include<sys/types.h>
#include<sys/stat.h>
#include<errno.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include <iostream.h>
 
#define FIFO "/tmp/myfifo"

using namespace std;

int main(int argc, char *argv[])
{
         char buf_r[100];
         int fd;
         int nread;
        
         if((mkfifo(FIFO,O_CREAT|O_EXCL) < 0) && (errno!=EEXIST))
         {
                 cout<<"Create FIFO failed and the FIFO is not existed!"<<endl;
                 return -1;
         }
        
         if((fd = open(FIFO, O_RDONLY, 0777)) == -1)
         {
                 cout<<"Open failed!"<<endl;
                 cout<<"Errno is "<<errno<<endl;
                  return -1;     
         }
         while(1){
                 memset(buf_r, 0, sizeof(buf_r));
                 if((nread = read(fd, buf_r, 100))== -1)
                 {
                          if(errno == EAGAIN)
                                   cout<<"no data yet\n"<<endl;
                 }
                 else if(nread > 0)
                         break;
         }
         cout<<"I receive the data:"<<buf_r<<endl;
         return 0;
}
原文地址:https://www.cnblogs.com/geekma/p/2588568.html