进程通信介绍
进程通信的目的
- 数据传输:一个进程需要将它的数据发送给另一给进程
- 资源共享:多个进程之间共享同样的资源
- 通知事件:一个进程需要向另一个或另一组进程发送消息,通知它(们)发生了某种事件(eg:进程终止时需要通知父进程)
- 进程控制:有些进程希望完全控制另一个进程的执行(eg:Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变
如何做到进程通信
- 进程具有独立性(数据层面)
- 进程间通信,要借助第三方(OS)资源
- 进程A-》数据“拷贝”给OS-》OS数据“拷贝”给进程B(OS一定要提供一段内存区域,能够被双方进程看到)
进程通信本质是让不同的进程,看到同一份资源(内存文件内核缓冲等)
进程间通信发展
进程间通信发展分类
管道
System V IPC
- System V 消息队列
- System V共享内存
- Systwm V信号量
POSIX IPC
管道
- 管道是Unix中最古老的进程间通信的形式
- 一个进程连接到另一个进程的数据流称为一个管道
管道虽然用的是文件的方案,但是OS一定不会把数据刷新到磁盘(有IO参与,降低效率)
匿名管道
匿名管道本质是没有文件名。 管道只能单向通信
#include <unistd.h>
int pipe(int fd[2]);
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
close(2);
int fd[2] = {0};
if(pipe(fd) < 0)
{
perror("pipe!\n");
return 1;
}
printf("fd[0]:%d\n",fd[0]);
printf("fd[1]:%d\n",fd[1]);
return 0;
}
用fork来共享管道原理
建立管道
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{
int fd[2] = {0};
if(pipe(fd) < 0)
{
perror("pipe!\n");
return 1;
}
pid_t id = fork();
if(id == 0)
{
close(fd[0]);
}
close(fd[1]);
}
close(fd[1]);
waitpid(id,NULL,0);
return 0;
}
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{
int fd[2] = {0};
if(pipe(fd) < 0)
{
perror("pipe!\n");
return 1;
}
pid_t id = fork();
if(id == 0){
close(fd[0]);
const char* msg = "hello father ,I am child!";
int count = 5;
while(count){
write(fd[1],msg,strlen(msg));
count--;
sleep(1);
}
}
close(fd[1]);
exit(0);
}
close(fd[1]);
char buff[64];
while(1)
{
ssize_t s = read(fd[0],buff,sizeof(buff));
if(s > 0)
{
buff[s] = '\0';
printf("child send to father#%s\n",buff);
}
else if(s == 0)
{
printf("read file end!\n");
break;
}
else
{
printf("read error!\n");
break;
}
}
waitpid(id,NULL,0);
return 0;
}
父子通信不可以创建全局缓冲区来完成通信,因为进程运行具有独立性!
内核角度–管道本质
管道特点
- 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父子进程之间就可应用该管道
- 管道提供流式服务
- 一般而言,进程退出,管道释放,所以管道的生命期随进程
- 一般而言,内核会对管道操作进程同步与互斥
- 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{
int fd[2] = {0};
if(pipe(fd) < 0)
{
perror("pipe!\n");
return 1;
}
pid_t id = fork();
if(id == 0){
close(fd[0]);
const char* msg = "hello father ,I am child!";
int count = 5;
while(count){
if(count == 2){
sleep(1000);
}
else{
write(fd[1],msg,strlen(msg));
count--;
sleep(1);
}
}
close(fd[1]);
exit(0);
}
close(fd[1]);
char buff[64];
while(1)
{
ssize_t s = read(fd[0],buff,sizeof(buff));
if(s > 0)
{
buff[s] = '\0';
printf("child send to father#%s\n",buff);
}
else if(s == 0)
{
printf("read file end!\n");
break;
}
else
{
printf("read error!\n");
break;
}
close(fd[0]);
break;
}
sleep(10);
int status = 0;
waitpid(id,&status,0);
printf("child quit:sig:%d\n",status & 0x7F);
return 0;
}
查看管道大小
查看系统资源:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{
int fd[2] = {0};
if(pipe(fd) < 0)
{
perror("pipe!\n");
return 1;
}
pid_t id = fork();
if(id == 0)
{
close(fd[0]);
const char* msg = "hello father ,I am child!";
char c='a';
int count = 0;
while(1)
{
write(fd[1],&c,1);
count++;
printf("%d\n",count);
}
close(fd[1]);
exit(0);
}
close(fd[1]);
char buff[64];
while(1)
{
sleep(1000);
ssize_t s = read(fd[0],buff,sizeof(buff));
if(s > 0)
{
buff[s] = '\0';
printf("child send to father#%s\n",buff);
}
else if(s == 0)
{
printf("read file end!\n");
break;
}
else
{
printf("read error!\n");
break;
}
}
waitpid(id,NULL,0);
return 0;
}
管道读写规则
没有数据可读
- O_NORNBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止
- O_NORNBLOCK enable: read返回-1,errno值为EAGAIN
当管道满的时候
- O_NORNBLOCK disable:write调用阻塞,知道有进程走读数据
- O_NORNBLOCK enable: 调用返回-1,errno值为EAGAIN
1、如果所有管道写端对应的文件描述符被关闭,则read返回0 2、如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出 3、当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性 4、当要写入的数据量大于PIPE_BUF时,Linux不再保证写入的原子性
命名管道
- 管道应用的一个限制就是只能在具有相同祖先(有亲缘关系)的进程间通信
- 在不相关的进程之间交换数据,可以用FIFO文件,它被称为命名管道
- 命名管道是一种特殊类型的文件
想让无关系的进程通信-》通过名字打开同一个文件-》看到同一份资源
创建一个命名管道
在命令行中
mkfifo filename
在程序中
int mkfifo(const char* filename,mode_t mode);
创建简单命名管道
//Makefile
.PHONY:all
all:client server
client:client.c
gcc -o $@ $^
server:server.c
gcc -o $@ $^
.PHONY:clean
clean:
rm client server myfifo
1\
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#define FILE_NAME "myfifo"
int main()
{
if(mkfifo(FILE_NAME,0644) < 0)
{
perror("mkfifo");
return 1;
}
return 0;
}
#include <stdio.h>
int main()
{
return 0;
}
2\
#pragma once
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#define FILE_NAME "myfifo"
#include "comm.h"
int main()
{
int fd = open(FILE_NAME,O_WRONLY);
if(fd < 0)
{
printf("open error!\n");
return 1;
}
char msg[128];
while(1)
{
msg[0] = 0;
printf("please enter# ");
fflush(stdout);
ssize_t s = read(0,msg,sizeof(msg));
if(s > 0)
{
msg[s - 1] = 0;
write(fd,msg,strlen(msg));
}
}
close (fd);
return 0;
}
#include "comm.h"
int main()
{
if(mkfifo(FILE_NAME,0644) < 0)
{
perror("mkfifo");
return 1;
}
int fd = open(FILE_NAME,O_RDONLY);
if(fd < 0)
{
perror("open error!\n");
return 2;
}
char msg[128];
while(1)
{
msg[0] = 0;
}
return 0;
}
此时myfifo为0-》并未被刷新到磁盘中,还在内存里-》在内存之中通信(和匿名管道底层原理一样采用文件通信)
3\
#include "comm.h"
int main()
{
if(mkfifo(FILE_NAME,0644) < 0)
{
perror("mkfifo");
return 1;
}
int fd = open(FILE_NAME,O_RDONLY);
if(fd < 0)
{
perror("open error!\n");
return 2;
}
char msg[128];
while(1)
{
msg[0] = 0;
ssize_t s = read(fd,msg,sizeof(msg) - 1);
if(s > 0)
{
msg[s] = 0;
printf("client# %s\n",msg);
}
else if(s == 0)
{
printf("client quit\n");
break;
}
else{
printf("read error!\n");
break;
}
}
return 0;
}
4\
#include "comm.h"
#include <sys/wait.h>
#include <stdlib.h>
int main()
{
if(mkfifo(FILE_NAME,0644) < 0)
{
perror("mkfifo");
return 1;
}
int fd = open(FILE_NAME,O_RDONLY);
if(fd < 0)
{
perror("open error!\n");
return 2;
}
char msg[128];
while(1)
{
msg[0] = 0;
ssize_t s = read(fd,msg,sizeof(msg) - 1);
if(s > 0)
{
msg[s] = 0;
printf("client# %s\n",msg);
if(fork() == 0){
execlp(msg,msg,NULL);
exit(1);
}
waitpid(-1,NULL,0);
}
else if(s == 0)
{
printf("client quit\n");
break;
}
else{
printf("read error!\n");
break;
}
}
return 0;
}
5\
#include "comm.h"
#include <sys/wait.h>
#include <stdlib.h>
int main()
{
if(mkfifo(FILE_NAME,0644) < 0)
{
perror("mkfifo");
return 1;
}
int fd = open(FILE_NAME,O_RDONLY);
if(fd < 0)
{
perror("open error!\n");
return 2;
}
char msg[128];
while(1)
{
msg[0] = 0;
ssize_t s = read(fd,msg,sizeof(msg) - 1);
if(s > 0)
{
msg[s] = 0;
printf("client# %s\n",msg);
char* p = msg;
const char* label = "+-*/%";
int flag = 0;
while(*p)
{
switch(*p)
{
case '+':
flag = 0;
break;
case '-':
flag = 1;
break;
case '*':
flag = 2;
break;
case '/':
flag = 3;
break;
case '%':
flag = 4;
break;
}
p++;
}
char* data1 = strtok(msg,"+-*/%");
char* data2 = strtok(NULL,"+-*/%");
int x = atoi(data1);
int y = atoi(data2);
int z = 0;
switch(flag)
{
case 0:
z = x + y;
break;
case 1:
z = x - y;
break;
case 2:
z = x * y;
break;
case 3:
z = x / y;
break;
case 4:
z = x % y;
break;
}
printf("%d %c %d = %d\n",x,label[flag],y,z);
}
else if(s == 0)
{
printf("client quit\n");
break;
}
else{
printf("read error!\n");
break;
}
}
close(fd);
return 0;
}
通信进程的意义:让多个进程进行协同完成某种事情:执行命令,发送字符串,完成计算机任务等等
匿名管道与命名管道的区别
- 匿名管道由pipe函数创建并打开
- 命名管道由mkfifo函数创建,打开用open
- FIFO(命名管道)与pipe(匿名管道)之间的区别在于它们创建与打开的方式不同,一旦这些工作完成只后,它们具有相同的语义
命名管道的打开规则
1、如果当前开采操作是为读而打开FIFO时
- O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
- O_NONBLOCK enable:立刻返回成功
2、如果当前开采操作是为写而打开FIFO时
- O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
- O_NONBLOCK enable:立刻返回失败,错误码为ENXIO
用命名管道实现文件拷贝
#pragma once
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#define FILE_NAME "myfifo"
#include "comm.h"
int main()
{
int fd = open(FILE_NAME,O_WRONLY);
if(fd < 0)
{
printf("open error!\n");
return 1;
}
int in = open("file.txt",O_RDONLY);
char msg[128];
while(1)
{
msg[0] = 0;
ssize_t s = read(0,msg,sizeof(msg));
if(s == sizeof(msg))
{
msg[s - 1] = 0;
write(fd,msg,s);
}
else if(s<sizeof(msg))
{
write(fd,msg,s);
printf("read end of file!\n");
break;
}
else{
break;
}
}
close(in);
close (fd);
return 0;
}
#include "comm.h"
#include <sys/wait.h>
#include <stdlib.h>
int main()
{
if(mkfifo(FILE_NAME,0644) < 0)
{
perror("mkfifo");
return 1;
}
int fd = open(FILE_NAME,O_RDONLY);
if(fd < 0)
{
perror("open error!\n");
return 2;
}
int out = open("file-bak.txt",O_CREAT|O_WRONLY,0644);
char msg[128];
while(1)
{
msg[0] = 0;
ssize_t s = read(fd,msg,sizeof(msg) - 1);
if(s > 0)
{
write(out,msg,s);
}
else if(s == 0)
{
printf("client quit\n");
break;
}
else{
printf("read error!\n");
break;
}
}
close(out);
close(fd);
return 0;
}
|