- 进程间通信即完成两个进程间数据的传递。需要借助操作系统提供特殊的方法,如:文件、管道、信号、共享内存、消息队列、套接字、命名管道等。比如其中的文件,之所以能通过文件进行IPC,是因为每个文件的文件描述符指向的文件结构体在内核中。注意,用不同进程打开同一文件得到的文件描述符不同。
- 现今常用的进程间通信方式有:
① 管道 (使用最简单)
② 信号 (开销最小)
③ 共享映射区 (无血缘关系)
④ 本地套接字 (最稳定)
文件
利用文件进行进程间通信的程序示例:
#include <stdio.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
pid_t pid;
int fd1, fd2;
char buf[1024];
char* str = "------test for shared fd in parent child process---\n";
pid = fork();
if(pid == -1) {
perror("fork error");
exit(1);
}
if(pid == 0) {
fd1 = open("test.txt", O_RDWR | O_CREAT, 0644);
if(fd1 < 0) {
perror("open error");
exit(1);
}
write(fd1, str, strlen(str));
printf("child wrote over\n");
}
if(pid > 0) {
fd2 = open("test.txt", O_RDWR | O_CREAT, 0644);
if(fd2 < 0) {
perror("open error");
exit(1);
}
sleep(1);
int ret = read(fd2, buf, sizeof(buf));
write(STDOUT_FILENO, buf, ret);
wait(NULL);
}
return 0;
}
管道(pipe)
- pipe函数
- 管道一般读写行为
程序示例:利用管道实现ls | wc -l
命令
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
void sys_err(char* str) {
perror(str);
exit(-1);
}
int main() {
int fd[2];
pid_t pid;
int ret = pipe(fd);
if(ret == -1)
sys_err("pipe error");
pid = fork();
if(pid == -1)
sys_err("fork error");
if(pid == 0) {
close(fd[0]);
dup2(fd[1], STDOUT_FILENO);
execlp("ls", "ls", NULL);
sys_err("execlp ls error");
} else if(pid > 0) {
close(fd[1]);
dup2(fd[0], STDIN_FILENO);
execlp("wc", "wc", "-l", NULL);
sys_err("execlp wc -l error");
}
return 0;
}
FIFO(命名管道)
- 用于非血缘关系IPC
//fifo_w.c
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
void sys_err(char *str)
{
perror(str);
exit(-1);
}
int main(int argc, char *argv[])
{
int fd;
char buf[4096];
if (argc < 2) {
printf("Enter like this: ./a.out fifoname\n");
return -1;
}
int ret = access(argv[1], F_OK);
if (ret != 0) {
mkfifo(argv[1], 0664);
}
fd = open(argv[1], O_WRONLY);
if (fd < 0)
sys_err("open");
while (1) {
fgets(buf, sizeof(buf), stdin);
write(fd, buf, strlen(buf));
}
close(fd);
return 0;
}
//fifo_r.c
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
void sys_err(char *str)
{
perror(str);
exit(1);
}
int main(int argc, char *argv[])
{
int fd, len;
char buf[4096];
if (argc < 2) {
printf("./a.out fifoname\n");
return -1;
}
fd = open(argv[1], O_RDONLY);
if (fd < 0)
sys_err("open");
while (1) {
len =read(fd, buf, sizeof(buf));
write(STDOUT_FILENO, buf, len);
}
close(fd);
return 0;
}
共享存储映射
-
mmap函数
1)参数
2)返回值 -
借助共享内存存放磁盘文件
-
父子进程、血缘关系进程间的通信
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
int var = 0;
int main() {
int fd;
int* p;
pid_t pid;
fd = open("temp.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);
if(fd < 0) {
perror("open error");
exit(1);
}
unlink("temp.txt");
ftruncate(fd, 4);
p = mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
//p = mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
if(p == MAP_FAILED) {
perror("mmap error");
exit(1);
}
close(fd);
pid = fork();
if(pid == 0) {
*p = 100;
var = 100;
printf("child: *p = %d, var = %d\n", *p, var);
} else if(pid > 0) {
sleep(1);
printf("parent: *p = %d, var = %d\n", *p, var);
wait(NULL);
int ret = munmap(p, 4);
if(ret == -1) {
perror("munmap error");
exit(1);
}
}
return 0;
}
- 匿名映射区
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
int var = 0;
int main() {
int* p;
pid_t pid;
//int fd = open("/dev/zero", O_RDWR);//类unix操作系统下的通行匿名映射方法
//p = mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
p = mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
if(p == MAP_FAILED) {
perror("mmap error");
exit(1);
}
pid = fork();
if(pid == 0) {
*p = 100;
var = 100;
printf("child: *p = %d, var = %d\n", *p, var);
} else if(pid > 0) {
sleep(1);
printf("parent: *p = %d, var = %d\n", *p, var);
wait(NULL);
int ret = munmap(p, 4);
if(ret == -1) {
perror("munmap error");
exit(1);
}
}
return 0;
}
- 非血缘关系进程间通信:
mmap借助一个文件在内核区创建了一个映射区,多个进程之间利用该映射区完成数据传递。由于内核空间多进程共享,因此无血缘关系的进程间也可以使用mmap来完成通信。只要设置相应的标志位参数flags即可(MAP_SHARED
)。关键点是各个进程在创建映射区时必须打开的是同一个文件。
//mmap_nonpc_write.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
#include <stdlib.h>
struct STD {
char name[20];
int age;
char sex;
};
void sys_err(const char* str) {
perror(str);
exit(-1);
}
int main(int argc, char** argv) {
int fd;
struct STD m_std = {"xl", 10, 'm'};
struct STD* mm;
if(argc < 2) {
printf("input error\n");
exit(-1);
}
fd = open(argv[1], O_RDWR | O_CREAT, 0644);
if(fd < 0) {
sys_err("open error");
}
int ret = ftruncate(fd, sizeof(struct STD));
if(ret < 0)
sys_err("ftruncate error");
mm = mmap(NULL, sizeof(struct STD), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if(mm == MAP_FAILED)
sys_err("mmap error");
close(fd);
while(1) {
memcpy(mm, &m_std, sizeof(struct STD));
m_std.age++;
sleep(1);
}
munmap(mm, sizeof(struct STD));
return 0;
}
//mmap_nonpc_read.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
#include <stdlib.h>
struct STD {
char name[20];
int age;
char sex;
};
void sys_err(const char* str) {
perror(str);
exit(-1);
}
int main(int argc, char** argv) {
int fd;
struct STD m_std;
struct STD* mm;
if(argc < 2) {
printf("a.out file_shared\n");
exit(-1);
}
fd = open(argv[1], O_RDONLY);
if(fd < 0) {
sys_err("open error");
}
mm = mmap(NULL, sizeof(struct STD), PROT_READ, MAP_SHARED, fd, 0);
if(mm == MAP_FAILED)
sys_err("mmap error");
close(fd);
while(1) {
printf("name = %s, age = %d, sex = %c\n", mm->name, mm->age, mm->sex);
sleep(1);
}
munmap(mm, sizeof(struct STD));
return 0;
}
并且,由于open、write、read函数的底层实现是借助mmap函数,因此非血缘关系进程间通信也可以借助文件的方式。
//file_mmap_w.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
void sys_err(char *str) {
perror(str);
exit(-1);
}
#define N 5
int main() {
int ret;
char buf[1024];
char* str = "-------write into file:file_mmap.txt\n";
int fd = open("file_mmap.txt", O_RDWR |O_TRUNC | O_CREAT, 0644);
if(fd < 0)
sys_err("open error");
write(fd, str, strlen(str));
printf("write into file:file_mmap.txt finished\n");
sleep(N);
lseek(fd, 0, SEEK_SET); //把读写指针位置移至起始
ret = read(fd, buf, sizeof(buf));
ret = write(STDOUT_FILENO, buf, ret);
if(ret < 0) {
perror("write second error");
exit(1);
}
close(fd);
return 0;
}
//file_mmap_r.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
void sys_err(char *str) {
perror(str);
exit(-1);
}
int main() {
char buf[1024];
char* str = "read and write into file: file_mmap.txt\n";
int fd = open("file_mmap.txt", O_RDWR);
if(fd < 0)
sys_err("open error");
int ret = read(fd, buf, sizeof(buf));
write(STDOUT_FILENO, buf, ret);
write(fd, str, strlen(str));
printf("read and write finished\n");
close(fd);
return 0;
}
本地套接字(domain socket)
- 虽然网络socket也可用于同一台主机的进程间通讯(通过 loopback地址127.0.0.1),但是UNIX Domain Socket用于IPC更有效率:不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程。
- UNIX Domain Socket是全双工的,API接口语义丰富,相比其它IPC机制有明显的优越性,目前已成为使用最广泛的IPC机制。
- UNIX Domain Socket与网络socket编程最明显的不同在于地址格式不同,用结构体sockaddr_un表示,网络编程的socket地址是IP地址加端口号,而UNIX Domain Socket的地址是一个socket类型的文件在文件系统中的路径,这个socket文件由bind()调用创建,如果调用bind()时该文件已存在,则bind()错误返回。