Linux下开发经常有多个进程协调工作,由于进程间的空间是相互独立的,怎么去完成进程间的通信呢?在Linux系统中提供了管道、信号量、消息队列、互斥信号、共享内存等通讯方法。后面将一一解析。
管道
管道是Linux由Unix那里继承过来的进程间的通信机制,它是Unix早期的一个重要通信机制。其思想是,在内存中创建一个共享文件,从而使通信双方利用这个共享文件来传递信息。由于这种方式具有单向传递数据的特点,所以这个作为传递消息的共享文件就叫做“管道”。
1.匿名管道
匿名管道是在具有公共祖先的进程之间进行通信的一种方式。在这里插入图片描述由父进程创建的子进程将会赋值父进程包括文件在内的一些资源。如果父进程创建子进程之前创建了一个文件,那么这个文件的描述符就会被父进程在随后所创建的子进程所共享。也就是说,父、子进程可以通过这个文件进行通信。如果通信的双方一方只能进行读操作,而另一方只能进行写操作,那么这个文件就是一个只能单方向传送消息的管道,如下图所示:
进程可以通过调用函数pipe()创建一个管道。函数pipe()的原型如下:
int pipe(int fildes[2]);
从本质上来说,pipe()函数的功能就是创建一个内存文件,但与创建普通文件的函数不同,函数pipe()将在参数fildes中为进程返回这个文件的两个文件描述符fildes[0]和fildes[1]。其中,fildes[0]是一个具有“只读”属性的文件描述符,fildes[1]是一个具有“只写”属性的文件描述符,即进程通过fildes[0]只能进行文件的读操作,而通过fildes[1]只能进行文件的写操作。由于这种文件没有文件名,不能被非亲进程所打开,只能用于亲属进程间的通信,所以这种没有名称的文件形成的通信管道叫做“匿名管道”。
当一个进程调用函数pipe()创建一个管道后,管道的连接方式如下图:
从图中可以看到,由于管道的出入口都在同一个进程之中,这种管道没有多大的用途的。但是当这个进程在创建一个新进程之后,情况就变得大不一样了。如果父进程创建一个管道之后,又创建了一个子进程,那么由于子进程继承了父进程的文件资源,于是管道在父子进程中的连接情况如下图:
在确定管道的传输方向之后,在父进程中关闭(close())文件描述符fildes[0],在子进程中关闭(close())文件描述符fildes[1],于是管道的连接情况就变成单向传输管道,如下图:
也可以想象,通过关闭文件描述符的方法,在两个兄弟进程之间也可以实现通信管道。匿名管道具有如下特点:
由于这种管道没有其他同步措施,所以为了不产生混乱,它只能是半双工的,即数据只能向一个方向流动。如果需要双方互相传递数据,则需要建立两个管道;
只能在父子进程或兄弟进程这些具有亲缘关系的进程之间进行通信;
匿名管道对于管道两端的进程而言,就是一个只存在于内存的特殊文件;
下面给出一段示例代码介绍父子进程间的管道通讯,如下:
#include <unist.h>
#include <string.h>
#include <wait.h>
#include <stdio.h>
#define MAX_LINE 80
int main()
{
int testPipe[2], ret;
char buf[MAX_LINE + 1];
const char * testbuf = "主程序发送的数据";
if (pipe(testbuf) == 0) {
if (fork() == 0) {
ret = read(testPipe[0], buf, MAX_LINE);
buf[ret] = 0;
printf("子程序读到的数据为:%s", buf);
close(testPipe[0]);
}else {
ret = write(testPipe[1], testbuf, strlen(testbuf));
ret = wait(NULL);
close(testPipe[1]);
}
}
return 0;
}
2.命令管道
由于匿名管道没有名称,因此,它只能在一些具有亲缘关系的进程之间进行通信,这使它在应用方面受到极大的限制。
命名管道是在实际文件系统上实现的一种通信机制。由于它是一个与进程没有“血缘关系”的、真正且独立的文件,所以它可以在任意进程之间实现通信。由于命名管道不支持诸如lseek()等文件定位操作,严格遵守先进先出的原则进行传输数据,即对管道的读总是从开始处返回数据,对它的写总是把数据添加到末尾,所以这种管道也叫做FIFO文件。同样,由于需要由管道自身来保证通信进程间的同步,命名管道也是一个只能单方向访问的文件,并且数据传输方式为FIFO方式。
也就是说,命名管道提供了一个路径名与之关联,以FIFO的文件形式存在于文件系统中,在文件系统中产生一个物理文件,其他进程只要访问该文件路径,就能彼此通过管道通信。在读数据端以只读方式打开管道文件,在写数据端以只写方式打开管道文件。下面给出示例代码:
Read进程代码:
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main(int argc, char* argv[])
{
int ret = mkfifo("my_fifo",0777);
if (ret == -1){
printf("make fifo failed!\n");
return 1;
}
char buf[256] = {0};
int fd = open("my_fifo",O_RDONLY);
read(fd,buf,256);
printf("%s\n",buf);
close(fd);
unlink("my_fifo");
return 0;
}
Write进程代码:
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char* argv[])
{
char *buf = "i am write process\n";
int fd = open("my_fifo",O_WRONLY);
write(fd,buf,strlen(buf));
close(fd);
return 0;
}