由于个人理解局限性和笔误,以下内容难免有误,忘谅解。欢迎指正。
进程间通讯大致可以分为以下4类:
- 管道(pipe)
- 信号(signal)
- 共享内存
- 套接字(socket)
一、管道(pipe)
pipe又称匿名管道。
1.特点:
- 双向全双工。同一时刻管道的只能在一端进行读操作,一端进行写操作。
- 只能在有血缘关系的的进程之间通讯。
- 管道属于伪文件。
Linux系统文件类型有7种,分别为普通文件(-)、目录(d)、符号链接(l)、字符设备(c)、块设备(b)、套接字(s)和管道(p),其中前三种都会占用磁盘空间,后边4种不占用磁盘空间,只存在内存种,因此后面4种别称为伪文件。
2.函数原型
#include <unistd.h>
int pipe(int pipefd[2]); // 成功返回0,失败返回-1
当调用pipe函数创建管道时,他会创建2个文件描述符,分别对应着管道的读端和写端,pipefd[0]为读的一端,pipe[1]为写的另一端。关闭时,将这两个文件描述符关闭即可。当然管道是在内核中生成的。
3.例程
由于管道是双向单双工的,因此数据信息可以从父进程流向子进程,同样也可以从子进程流向父进程。但是由于同一时刻只能单向流动,因此在读的进程中需要将写端关闭,在写的进程中需要将读端关闭即可。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
int pipefd[2];
char str[] = "hello my child";
char recv[256] = {0};
int ret;
ret = pipe(pipefd);
if (-1 == ret) {
perror("pipe is failed\n");
exit(-1);
}
ret = fork();
if (ret < 0) {
perror("fork is failed\n");
exit(-1);
} else if (ret == 0) {
printf("i am child.\n");
close(pipefd[1]);
sleep(1);
read(pipefd[0], recv, 256);
printf("recv msg from parent:%s\n", recv);
close(pipefd[0]);
} else {
printf("i am parent\n");
close(pipefd[0]);
write(pipefd[1], str, sizeof(str));
close(pipefd[1]);
wait(NULL);
}
return 0;
}
运行结果:
i am parent
i am child.
recv msg from parent:hello my child
二、共享内存
共享内存可用于用多个进程间通讯,没有血缘要求。
1.特点
- 这种通讯方式进程可直接对内存进行存取。
2.函数原型
创建共享内存的函数
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length);
mmap函数的目的是将文件描述符对应的文件在内核中映射成一块内存空间。
形参:
- void *addr:映射区的起始地址
- size_t length:映射区大小
- int port:映射区权限(PORT_READ、PORT_WRITE、PORT_READ | PORT_WRITE)
- int flag:标志位(MAP_SHARED、MAP_PRIVATE)
- int fd:文件描述符
- off_t offset:文件偏移地址
- port参数是映射区的权限,该参数规定了用户对映射区操作的权限
-
flag参数有两种,MAP_PRIVATE表示映射区的改动不会反映到物理设备上,只存在在映射区中。相反的MAP_SHARED表示映射区中修改会反映到物理设备上。
3.例程
- 利用映射区进行父子进程间通讯。
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
char str[] = "hello my father";
char recv[256] = {0};
int ret;
char *pfd;
int len = 128;
int fd = open("test", O_RDWR | O_CREAT | O_TRUNC, 0644);
if (fd < 0) {
perror("open file faied\n");
exit(-1);
}
unlink("test");
ret = ftruncate(fd, len);
if (ret < 0) {
perror("ftruncate faild\n");
exit(-1);
}
pfd = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (pfd == MAP_FAILED) {
perror("mmap faild\n");
exit(-1);
}
close(fd);
memset(pfd, 0, len);
ret = fork();
if (ret < 0) {
perror("fork is failed\n");
exit(-1);
} else if (ret == 0) {
printf("i am child, i send a msg to my parent.\n");
memcpy(pfd, str, strlen(str));
} else {
sleep(1);
printf("i am parent.\n");
printf("msg:%s\n", pfd);
wait(NULL);
munmap(pfd, len);
}
return 0;
}
执行结果:
i am child, i send a msg to my parent.
i am parent.
msg:hello my father
- 匿名映射区
前边的父子间的通信在创建映射区时利用了一个test文件,但是我们这个文件的作用只是为了创建映射区,创建完映射区后,这个文件就被删除了,对后边的通讯没有作用。linux在mmap的标志为提供了MAP_ANON,这个标志允许用户在内核中创建匿名映射区,此时的映射函数写法为:
pfd = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
但是此标志只能在Linux系统中使用,unix无法使用该方法创建匿名映射区。
显然为了创建映射区而生成一个无用的文件是没有意义的,linux系统中为我们提供两个伪文件/dev/zero和/dev/null。前者在打开h后可以映射成任意大小的映射区。后者可以无限的向里面写数据,类似于黑洞。在这里,我们用到的是/dev/zero,将前面父子通信中的test文件替换成/dev/zero即可。