进程间通讯是进程间数据传输 数据共享 进程控制 时间通知 , 根据需求的不同,操作系统提供多种不同的进程间通信方式
进程通讯–提供一个公共的媒介
管道
linux管道继承于unix:匿名管道/命名管道
管道是一个半双工通信: 提供双向选择,但是只能单向传输
管道创建成功后,提供一个io操作:返回文件描述符作为句柄
句柄(文件描述符)有两个:
一个用于读取数据,一个用于写入数据
匿名管道只能用于有血缘关系的进程间通讯
命名管道可以用于任意的进程间通讯
原理: 操作系统在内核中提供一块缓冲区(只要进程能够访问到这块缓冲区就可以实现通信)
匿名管道
通过子进程复制父进程的方式(复制文件描述符)实现通信
#include <unistd.h>
功能:创建一无名管道
原型
int pipe(int fd[2]);
参数
fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值: 成功返回0.失败返回错误代码
读写特性:
管道中没有数据,则 read 会阻塞,直到读取到数据(有数据写入到管道了)
管道中若数据满了,则 write 会触发异常–SIGPIPE(导致进程退出)
若所有写端被关闭,则read读完数据后不会阻塞,直接返回0
进程在操作管道时,如果没有用到某一段,则把这一端关闭关闭
- 当没有数据可读时
- O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
- O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。
- 当管道满的时候
- O_NONBLOCK disable:write调用阻塞,直到有进程读走数据
- O_NONBLOCK enable:调用返回-1,errno值为EAGAIN
- 如果所有管道写端对应的文件描述符被关闭,则read返回0
- 如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程 退出
- 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
- 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性
管道特点:
- 通常情况下一个管道由一个进程创建,然后该进程调用fork. 由此父, 子进程之间就可以应用该管道
- 管道提供流式服务
- 一般情况下进程退出,管道退出,管道声明周期随进程
- 内核会对管道操作进行同步与互斥
- 管道是半双工通讯,数据只能向一个方向流动; 需要双方通信时,需要建立两个管道
管道符: ’ | ’ ps -ef | gerp pipe
shell – 父进程
ps : —子进程1 -> 将处理结果打印到标准输出–标准输出重定向
grep – 子进程2 -> 循环从标准输入读取数据–标准输入重定向
命名管道
命名管道可在命令行上创建
$ mkfifo filename
同时也可在程序中创建
int mkfifo(const char *filename,mode_t mode);
int main(int argc, char *argv[])
{
mkfifo("p2", 0644);
return 0;
}
匿名管道与命名管道的区别:
- 匿名管道用pipe函数创建
- 命名管道用 mkfifo 创建,打开用open
- FIFO(命名管道) 与pipe (匿名管道) 之间唯一的区别就是创建于打开方式不同, 之后的操作相同
命名管道的打开规则:
- 如果当前打开操作是为读而打开FIFO
O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
O_NONBLOCK enable:立刻返回成功 - 如果当前打开操作是为写而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
O_NONBLOCK enable:立刻返回失败,错误码为ENXIO
共享内存
共享内存是最快的IPC(Interprocess communication)形式,一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核, 即进程不再通过执行进入内核的系统调用来传递数据
操作步骤:
- 创建共享内存 shmget
- 将共享内存映射到虚拟地址空间 shmat
- 直接对这块内存进行操作 memcpy
- 接触映射关系 shmdt
- 删除共享内存 shmctl
进程间通信方式的查看: ipcs [-m/-s/-q] shmid
进程间通信方式的删除: Ipcrm [-m/-s/-q] shmid
共享内存函数
shmget函数
功能:用来创建共享内存
原型
int shmget(key_t key, size_t size, int shmflg);
参数
key:这个共享内存段名字
size:共享内存大小
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1
shmat函数
功能:将共享内存段连接到进程地址空间
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
shmid: 共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1
shmaddr为NULL,核心自动选择一个地址
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr - (shmaddr % SHMLBA)
shmflg=SHM_RDONLY,表示连接操作用来只读共享内存
shmdt函数
功能:将共享内存段与当前进程脱离
原型
int shmdt(const void *shmaddr);
参数
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
shmctl函数
功能:用于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1
命名管道实现通讯小例:
fifo_myread.c
1 /**********************************************************
2 * Author : yang
3 * Email : wk_eternity@163.com
4 * Last modified : 2019-07-25 15:16
5 * Filename : fifo_myread.c
6 * Description : 命令管道的基本使用
7 * 命令管道可见于文件系统,会创建一个文件系统(文件只是名字)
8 * int mkfifo (const char *pathname, mode_t mode);
9 * pathname : 管道文件的路径名
10 * mode : 创建文件的权限
11 * 返回值 : 0 失败:-1
12 * *******************************************************/
13 #include <stdio.h>
14 #include <unistd.h>
15 #include <stdlib.h>
16 #include <errno.h>
17 #include <string.h>
18 #include <fcntl.h>
19
20 int main()
21 {
22 char *file = "./tmp.fifo";
23 umask(0);
24 int ret = mkfifo(file, 0664);
25 if(ret < 0){
26 //如果文件不是因为已经存在而报错, 则退出
27 if (errno != EEXIST) {
28 perror ("mkfifo error");
29 return -1;
30 }
31 }
32 printf("create fifo success!\n");
33 int fd = open(file, O_RDONLY);
34 if (fd < 0){
35 perror("open errno");
36 return -1;
37 }
38 printf("open fifo success!\n");
39
40 while(1) {
41 sleep(5);
42 char buf[1024] = {0};
43 int ret = read(fd, buf, 1023);
44 if (ret < 0) {
45 perror("write error!\n");
46 return -1;
47 }else if(ret == 0){
48 printf("write closed!\n");
49 return -1;
50 }
51 printf("buf : [%s]\n",buf);
52 }
53 close(fd);
54 return 0;
55 }
fifo_write.c
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <errno.h>
4 #include <fcntl.h>
5 #include <string.h>
6 #include <stdlib.h>
7
8 int main()
9 {
10 char *file = "./tmp.fifo";
11 umask(0);
12 int ret = mkfifo(file, 0664);
13 if(ret < 0){
14 //如果文件不是因为已经存在而报错,则退出
15 if (errno != EEXIST){
16 perror("mkfifo error!\n");
17 return -1;
18 }
19 }
20 printf("create fifo succes!\n");
21 int fd = open(file, O_WRONLY);
22 if (fd < 0) {
23 perror("open error");
24 return -1;
25 }
26 printf("open file success!\n");
27 while(1) {
28 char buf[1024] = {0};
29 scanf("%s", buf);
30 write(fd, buf, strlen(buf));
31 }
32 close(fd);
33 return 0;
34 }
在两个终端下运行可执行文件,即可实现简单通讯.
匿名管道完成ps -ef|grep pipe命令
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
int main()
{
int fd[2];
int ret = pipe(fd);
if (ret < 0) {
perror("pipe error\n");
return -1;
}
int pid1 = fork();
if (pid1 == 0){
//child1 -- ps-ef
close(fd[0]);
dup2(fd[1], 1);
execlp("ps", "ps", "-ef",NULL);
exit(0);
}
int pid2 = fork();
if (pid2 == 0) {
//child2 -- grep pipe
close(fd[1]);
dup2(fd[0], 0);
execlp("grep","grep" ,"pipe", NULL);
exit(0);
}
close(fd[0]);
close(fd[1]);
waitpid(pid1, NULL, 0);
printf("child1 exit----\n");
waitpid(pid2, NULL, 0);
printf("child2 exit----\n");
return 0;
}