一、Linux进程间通信概述
Linux 下的进程通信手段基本上是从UNIX 平台上的进程通信手段继承而来的。经过不断的发展形成了如下图所示的进程间通信类型:
- UNIX 进程间通信(IPC)方式包括管道、FIFO 以及信号。
- System V 进程间通信(IPC)包括System V 消息队列、System V 信号量以及System V 共享内存区。
- Posix 进程间通信(IPC)包括Posix 消息队列、Posix 信号量以及Posix 共享内存区。
- 管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。
- 信号(Signal):信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一样的。
- 消息队列(Messge Queue):消息队列是消息的链接表,包括Posix 消息队列SystemV 消息队列。它克服了前两种通信方式中信息量有限的缺点,具有写权限的进程可以按照一定的规则向消息队列中添加新消息;对消息队列有读权限的进程则可以从消息队列中读取消息。
- 共享内存(Shared memory):可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种通信方式需要依靠某种同步机制,如互斥锁和信号量等。
- 信号量(Semaphore):主要作为进程之间以及同一进程的不同线程之间的同步和互斥手段。
- 套接字(Socket):这是一种更为一般的进程间通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛。
二、无名管道
1、无名管道的特点。
- 它只能用于具有亲缘关系的进程之间的通信(也就是父子进程或者兄弟进程之间)。
- 它是一个半双工的通信模式,具有固定的读端和写端。
- 管道也可以看成是一种特殊的文件,对于它的读写也可以使用普通的read()和write()等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内核的内存空间中。
管道是基于文件描述符的通信方式,当一个管道建立时,它会创建两个文件描述符fds[0]和fds[1],其中fds[0]固定用于读管道,而fd[1]固定用于写管道。管道关闭时只需将这两个文件描述符关闭即可,可使用普通的close()函数逐个关闭各个文件描述符。
3、管道创建函数
int pipe(int fd[2])
fd[2] : 管道的两个文件描述符,之后就可以直接操作这两个文件描述符
返回值:0成功,其他返回错误代码
4、管道创建实例
编写一个无名管道的测试例程:创建两个进程,实现父子进程之间的通信。具体的测试代码如下:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#define BUFFER_SIZE 256 // 定义缓冲区的大小
/*
* 使用pipe实现父子进程之间的通信
*/
int main(void)
{
pid_t pid;
int pipefd[2];
int ret;
char buf[BUFFER_SIZE];
int buflen;
/* 创建一个管道
* pipefd[0] : read
* pipefd[1] : write
*/
ret = pipe(pipefd);
if(-1 == ret)
{
printf("pipe error!\n");
return -1;
}
/* fork出一个子进程 */
pid = fork();
if(-1 == pid) // 错误
{
printf("fork error!\n");
return -1;
}
else if(0 == pid) // 子进程
{
/* 1、子进程向父进程发送一条消息 */
sprintf(buf, "%s", "to main process!");
write(pipefd[1], buf, BUFFER_SIZE);
printf("(1) child to parent : %s\n", buf);
/* 4、子进程获得父进程发送的消息 */
buflen = read(pipefd[0], buf, BUFFER_SIZE);
buf[buflen] = '\0';
printf("(4) child from parent : %s\n", buf);
}
else if(pid > 0) // 父进程
{
/* 2、父进程获得子进程发送的消息 */
buflen = read(pipefd[0], buf, BUFFER_SIZE);
buf[buflen] = '\0';
printf("(2) parent from child : %s\n", buf);
/* 3、父进程向子进程发送一条消息 */
sprintf(buf, "%s", "to child process!");
write(pipefd[1], buf, BUFFER_SIZE);
printf("(3) parent to child : %s\n", buf);
/* 等待子进程执行完毕 */
waitpid(pid, NULL, 0);
}
return 0;
}
编译并运行,结果如下:
由结果可知,实现了父子进程的通信。
三、标准流管道
1、标准流管道创建过程
与Linux 的文件操作中有基于文件流的标准I/O 操作一样,管道的操作也支持基于文件流的模式。创建一个标准流管道需要以下几步:
- 创建一个管道。
- fork()一个子进程。
- 在父子进程中关闭不需要的文件描述符。
- 执行exec 函数族调用。
- 执行函数中所指定的命令。
2、标准流管道创建函数
FILE *popen(const char *command, const char *type)
command : 指向的是一个以null 结束符结尾的字符串,这个字符串包含一个shell 命令,并被送到/bin/sh 以-c 参数执行,即由shell 来执行
type:“r”:文件指针连接到command 的标准输出,即该命令的结果产生输出;“w”:文件指针连接到command 的标准输入,即该命令的结果产生输入
返回值:成功返回文件流指针,出错返回-1
int pclose(FILE *stream)
stream : 要关闭的文件流
返回值:成功返回进程的退出码,出错返回-1
3、测试例程
编写一个测试程序:使用标准流管道把一个命令的产生结果打印出来。
#include <stdio.h>
#define BUFFER_SIZE 256 // 定义缓冲区的大小
/* 实现一个标准流管道的测试小例子
* usage : stream_pipe <cmd>
*/
int main(int argc, char *argv[])
{
FILE *fp;
char buf[BUFFER_SIZE];
if(2 != argc)
{
printf("usage : %s <cmd>\n", argv[0]);
return -1;
}
/* 打开一个标准流管道从相应的进程当中 */
fp = popen(argv[1], "r");
if(NULL == fp)
{
printf("popen error!\n");
return -1;
}
/* 把内容打印出来 */
while(fgets(buf, BUFFER_SIZE, fp) != NULL) // 判断是否读完
{
printf("%s", buf);
}
/* 关闭一个标准流管道 */
pclose(fp);
return -1;
}
编译并运行结果如下: