一,目的
·数据传输:一个进程需要将它的数据发送给另一个进程。
·资源共享:多个进程之间共享同样的资源。
·通知事件:一个进程需要向另一个或一组进程发送消息,通知它们发生了某种事件。
·进程控制:有些进程希望完全控制另一个进程的执行,此时控制进程希望能够拦截另一个进 程的所有陷入和异常,并能够及时知道它的状态改变。
二、分类
管道:命名管道、命名管道
System V进程间通信: 消息队列、共享内存、信号量
POSIX进程间通信: 消息队列、共享内存、信号量、互斥量、条件变量、读写锁
三、进程间通信的模型——管理者与被管理者模式
由两步构成:1、将对象描述起来(用结构体); 2、将结构体全部组织起来。
进程间通信的过程,每次只要提到进程间通信,我们就应该想到下面的图
进程地址空间是通过页表映射到物理内存中的
进程地址空间的作用:
1、为了保护物理内存;2、实现进程的独立性
进程的替换:进程数并没有增加,只是用新的进程数据来替换当前进程的代码和数据
四、管道
1、本质:管道的本质是内核的一块缓存
2、匿名管道的创建
函数:int pipe(int fds[2]);
参数:fds:文件描述符数组,其中fds[0]代表读端,fds[1]代表写端
返回值:成功返回0,失败返回错误代码
下面我们通过以下代码进一步了解一下创建匿名管道的函数
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys / wait.h>
int main()
{
int fds [2] ;
pipe(fds); //创建管道
if(fork()== 0)//子进程读取管道
{
close(fds [1]); //管道的读端是fds [0],故关闭fds[1]
char buf [100] = {};
read(fds[0],buf,100); //读取管道中的内容
close(fds[0]); //关闭管道的读端;
exit(0);
}
else //父进程向管道中写入数据
{
close(fds [0]); //父进程要往管道中写数据,所以关闭fds[0]
char * msg =“hello world !!!”;
sleep(2);
write(fds[1],msg,strlen(msg));//两秒后向管道中写入数据
close(fds [1]);
}
}
3、文件描述符复制:dup2(OldFile,NewFile)
我们通过下面的代码来进一步理解dup函数
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys / wait.h>
int main()
{
int fds [2] ;
pipe(fds); //创建管道
if(fork()== 0)//创建子进程,子进程执行ls -l
{
dup2(fds[1],1); //将标准输出重定向到管道
close(fds [0]);
close(fds[1]);
execlp("ls","ls","-l",NULL);
perror("execlp");
exit(1);
}
else //父进程执行wc -l
{
dup2(fds[0],0); //让标准输入来自管道的读
close(fds [1]);
close(fds [0]);
execlp("wc","wc","-l",NULL); //用wc替换进程空间
perror("execlp");
exit(1);
}
}
4、管道读写规则
管道的内核缓存大小是65536个字节,向管道中写入数据时,一次写入的大小不要超过PIPE_BUF的大小(PIPE_BUF在本机平台下为4096个字节)。
·当没有数据可读时:
O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来为止;
O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。
·当管道满时:
O_NONBLOCK disable:write调用阻塞,直到有进程读走数据;
O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。
5、管道特点
只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;
管道提供流式服务;
一般而言,进程退出,管道释放,所以管道的生命周期跟随进程;
一般而已,内核会对管道操作进行同步与互斥;
管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个通道。
6、命名管道
解决了匿名管道不能让没有亲缘关系的进程拿到管道内核缓存的问题;
是一种特殊类型的文件。
(1)创建一个命名管道
函数原型:int mkfifo(const char* filename,mode_t mode)
参数1:文件名; 参数2:权限
创建一个命名管道mp,代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<sys/stat.h>
int main()
{
mkfifo("mp",0644);
return 0;
}
(2)命名管道的打开规则
·如果当前打开操作是为读而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为写而打开FIFO;
O_NONBLOCK enable:立刻返回成功;
·如果当前打开操作是为写而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为读而打开FIFO;
O_NONBLOCK enable:立刻返回失败,错误码为ENXIO;
7、匿名管道和命名管道的区别
匿名管道由pipe函数创建并打开;
命名管道由mkfifo函数创建,打开用open;
FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在于它们的创建与打开方式不同,一旦这些工作完成后,它们具有相同的语义。
用命名管道实现文件拷贝的例子
读取文件,写入命名管道
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<errno.h>
5 #include<string.h>
6
7 #define ERR_EXIT(m)
8
9 do{
10 perror(m);
11 exit(EXIT_FAILURE);
12 }while(0)
13
14
15 int main(int argc,char* argv[])
16 {
17 mkfifo("tp",0644);
18 int infd;
19 infd = open("abc",O_RDONLY);
20 if(infd == -1)
21 ERR_EXIT("open");
22
23 int outfd;
24 outfd = open("tp",O_WRONLY);
25 if(outfd == -1)
26 ERR_EXIT("open");
27
28 char buf[1024];
29 int n;
30 while((n = read(infd,buf,1024)) > 0)
31 write(outfd,buf,n);
32 close(infd);
33 close(outfd);
34 return 0;
35 }
读取管道,写入目标文件
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<errno.h>
5 #include<string.h>
6
7 #define ERR_EXIT(m)
8
9 do{
10 perror(m);
11 exit(EXIT_FAILURE);
12 }while(0)
13
14
15 int main(int argc,char* argv[])
16 {
17 int outfd;
18 outfd = open("abc.bak",O_WRONLY | O_CREAT | O_TRUNC,0644);
19 if(outfd == -1)
20 ERR_EXIT("open");
21
22 int infd;
23 infd = open("tp",O_RDONLY);
24 if(infd == -1)
25 ERR_EXIT("open");
26
27 char buf[1024];
28 int n;
29 while((n = read(infd,buf,1024)) > 0)
30 write(outfd,buf,n);
31 close(infd);
32 close(outfd);
33 unlink("tp");
34 return 0;
35 }