进程通信
前言
Vue框架:
Vue驾校-从项目学Vue-1
算法系列博客友链:
神机百炼
通信背景:
目的:
1. 数据传输:
- 含义:一个进程将其数据发送给另一进程
- 举例:下棋落一子
2. 资源共享:
- 含义:两个进程共享相同的数据
- 举例:下棋时当前的棋局
3. 事件通知:
- 含义:一个进程向另一进程发送消息,通知某种事件发生
- 举例:子进程通知父进程其到达终止阶段
4. 进程控制:
- 含义:一个进程想要控制另一进程执行
- 举例:拦截进程的异常和陷阱,及时知道进程状态改变
分类:
1. 管道通信:
- 匿名管道:适用于含有亲属关系的进程对之间
- 命名管道:适用于任意两进程之间,但常用于无亲属关系的进程
2. System V IPC:
- 消息队列
- 共享内存
- 信号量
3. Posix PC:
- 消息队列
- 共享内存
- 信号量
- 互斥量
- 条件变量
- 读写锁
匿名管道:
原理:
- 父子进程通过将内容相同的file_struct,可以“看见同一块内存”
- 写时拷贝:
- 发生条件:父子进程对自己的内存地址空间操作时发生
- 文件操作:只有操作系统才有权力对file进行操作,进程不可直接操作
- 匿名管道:
- 写入:进程将数据传递给OS,由OS将数据传递给文件。
- 读出:文件将数据传递给OS,由OS将数据传递给进程。
- 不涉及写时拷贝,不涉及和硬盘的IO
- 管道文件:
含义:一个进程负责向文件写入内容,一个进程负责向文件读取内容,就像单向水流在水管中一样,所以叫“管道”
举例:bash进程下的两个命令行子进程之间使用匿名管道通信
为什么管道必须是文件,而不能是全局变量?
全局变量属于进程地址空间中的初始化/未初始化数据区,进程可以脱离OS直接对全局变量操作
也就是父子进程对全局变量操作时必然发生写时拷贝,导致父子进程看到的不是同一块空间,只能读取自己的空间
核心函数:
pipe():
- 作用:在父子进程之间打开一个匿名管道文件
- 头文件:#include <unistd.h>
- pipe():
int pipefd[2];
if(pipe(pipefd) < 0){
perror(pipe error);
return -1;
}
- 输出型参数:int[2]
1. 读取管道文件存储在pipefd[0]
2. 写入管道文件存储在pipefd[1] - 返回值:获取管道文件失败返回-1
系统调用接口:
- write():向管道文件pipefd[0]中写
- read():在管道文件pipefd[1]中读
- close():关闭文件
实例演示:
- 父进程写,子进程读:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
int pipefd[2];
if(pipe(pipefd) < 0){
perror("pipe error");
return -1;
}
pid_t pid = fork();
if(pid < 0){
perror("fork error");
return -2;
}else if(pid == 0){
close(pipefd[1]);
char buffer[100];
while(read(pipefd[0], buffer, sizeof(buffer))){
printf("子进程读取到:%s\n",buffer);
}
close(pipefd[0]);
exit(0);
}else {
close(pipefd[0]);
char* msg = "pipe()匿名管道\n";
write(pipefd[1], msg, sizeof(msg));
printf("父进程写入完成\n");
close(pipefd[1]);
}
return 0;
}
linux命令:
- |:将前命令的输出,作为后命令的输入
- 举例:
who | wc -l
#who输出的是该主机上的用户
#wc -l统计输入内容条数
命名管道:
原理:
- 两个进程通过OS提供的系统调用接口open()打开同一文件及其缓冲区
- 特殊点:
- 命名管道虽然也是文件,但是和匿名管道一样,不占硬盘大小,只用内存映射
- open()命名管道文件后,之后的操作完全和文件操作一样,一个进程write,一个进程read()
核心函数:
mkfifo():
- 作用:在指定路径下创建一个命名管道文件
- 头文件:
- #include <sys/types.h>
- #include <sys/stat.h>
- mkfifo():
#define FILE_NAME = "name_pipe"
#define mode 0644
if(mkfifo(FILE_NAME, mode) < 0){
perror("mkfifo error");
return -1;
}
int fd = open(FILE_NAME, O_RDWR);
if(fd < 0){
perror("open error");
return -2;
}
参数:
- char* filename:文件路径和名称,默认为当前路径下
- mode_t mode: 所创文件的权限码,一般为0644
返回值:
创建命名管道文件失败/已创建过:-1
access():
- 作用:由于mkfifo不能重复创建同一路径下同名的命名管道文件,所以在mkfifo()前,需要我们确定该文件是否已存在
- 头文件:#include <unistd.h>
- access():
if(access(FILE_NAME, F_OK)){
if(mkfifo(FILE_NAME, 0644) < 0){
perror("mkfifo error");
return -1;
}
}
- 参数:
- FILE_NAME:文件路径&文件名
- mode:四种查找模式,写权限W_OK,读权限R_OK,执行权限X_OK,存在F_OK
- 返回值:
1. 对应权限或文件存在,返回0
2. 对应权限或文件不存在,返回1
系统调用接口:
- open():mkfile()返回值并不是直接打开命名管道的fd,所以需要我们根据FILE_NAME来手动打开文件
- close()
- write()
- read()
- 对命名管道的操作还是这四大文件调用接口,体现了linux下一切皆文件的思想
实例演示:
- 父进程读,子进程写:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <error.h>
#define FILE_NAME "name_pipe"
int main(){
if(access(FILE_NAME, F_OK)){
if(mkfifo(FILE_NAME, 0644) < 0){
perror("mkfifo error");
return -1;
}
}
pid_t pid = fork();
if(pid < 0){
perror("fork error");
return -2;
}else if(pid == 0){
int fd = open(FILE_NAME, O_RDONLY);
char buffer[100];
while(read(fd, buffer, sizeof(buffer))){
printf("子进程读取到:%s\n",buffer);
}
close(fd);
exit(0);
}else{
char* msg = "mkfifo命名管道\n";
int fd = open(FILE_NAME, O_WRONLY);
write(fd, msg, sizeof(msg));
printf("父进程写入完毕\n");
close(fd);
}
return 0;
}
linux命令:
mkfifo:
mkfifo fifo #在本目录下创建管道文件fifo
> >> < <<:
cmd > file #将cmd的结果输出到file中,直接覆盖file cmd >> file #将cmd的结果输出到file中,追加输出 cmd < file #将file中内容输出到cmd命令中
利用命名管道mkfifo实现内容写入:
共享内存:
原理:
基本原理:进程地址空间的共享内存部分指向内存中同一区域
操作步骤:
- 建立共享内存:
- 申请共享内存:向物理内存申请一块地址空间作为共享内存
- 页表地址挂接:向页表中建立进程地址空间共享区和共享内存之间的地址映射关系
- 释放共享内存:
- 页表地址去关联:删除页表中进程地址空间共享区和共享内存之间的地址映射关系
- 释放共享内存:将共享内存这一块地址空间归还给系统
- 建立共享内存:
共享内存管理:
由于每个进程都可以创建共享内存,所以OS需要对所有共享内存进行管理
- shmid:shared memory id,每块共享内存用户层面上的独立唯一标识
- key:每块共享内存系统层面上的独立唯一标识
核心函数:
ftok():
- 作用:返回系统层面对于共享内存的唯一标识key_t
- ftok():
key_t key = ftok(FILE_NAME, PROJ_ID);
if(key < 0){
perror("ftok error");
return -1;
}
参数:
- FILE_NAME:文件路径+文件名,默认文件路径为当前文件夹下
- PROJ_ID:工程id,可以随便取,运气不好可能和其他共享内存的PROJ_ID发生冲突
返回值:
- 调用失败:-1
- 调用成功:系统层面共享内存的唯一标识
shmget():
- 作用:利用系统层面唯一标识key,生成共享内存,并返回共享内存在用户层上的唯一id,供用户使用
- shmget():
int shmid = shmget(key, SIZE, IPC_CREAT|IPC_EXCL|0644);
if(shmid < 0){
perror("shmget error");
return -1;
}
- 参数:
- key:系统层面对于共享内存的唯一标识
- SIZE:预计开辟共享内存的大小,由于要与磁盘中分协同,所以只能是1024的整数倍
- 打开模式:IPC_CREAT为不存在则创建,IPC_CREAT | IPC_EXCL为不存在则报错,0644为创建的共享内存权限
- 返回值:
1. 获取共享内存成功:共享内存在用户层面的唯一标识shmid
2. 获取共享内存失败:-1
shmctl:
- 作用:删除shmid所指定的共享内存
- shmctl:
shmctl(shmid, IPC_RMID, NULL);
- 参数:
1. shmid:用户层面对共享内存的唯一标识
2. IPC_RMID:宏定义的整数,代表删除命令
3. struct shmid_ds *buf
shmat():
- 作用:将物理内存中开辟好的共享内存挂载到某个进程的进程地址空间中,供其使用
- shmat():
char* shm = shmat(shmid, NULL, 0);
- 参数:
- shmid:用户层对共享内存的唯一标识
- NULL
- 0
- 返回值:
- 共享内存的起始地址(页表映射加工过)
- shmget()时规定了共享内存的空间大小
shmdt():
- 作用:将已经挂载到某个进程的进程地址空间中的共享内存卸下
- shmdt():
shmdt(shm);
- 参数:已经挂载的共享内存地址
实例演示:
- 采用客户端服务器模式,客户端给服务器发送消息,两份代码体现效果更直观:
- 公共头文件comm.h:
#ifndef _COMMON_H_
#define _COMMIN_H_
#include <stdio.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/stat.h>
#define PATH_NAME "/home/whb/testipc"
#define PROJ_ID 0x8668 //此处可能冲突,报错就换个数字
#define SIZE 4096 //必须为1024倍数
#endif
- server.c:
#include "comm.h"
int main(){
key_t key = ftok(FILE_NAME, PROJ_ID);
if(key < 0){
perror("ftok error");
return -1;
}
int shmid = shmget(key, SIZE, IPC_CREAT);
if(shmid < 0){
perror("shmget error");
return -2;
}
char* shm = shmat(shmid, NULL, 0);
while(1){
printf("%s\n", shm);
}
shmdt(shmid);
return 0;
}
- client.c:
#include "comm.h"
int main(){
key_t key = ftok(FILE_NAME, PROJ_ID);
if(key < 0){
perror("ftok error");
return -1;
}
int shmid = shmget(key, SIZE, O_CREAT|O_EXCL|0644);
if(shmid < 0){
perror("shmger error");
return -2;
}
char* shm = shmat(shmid, NULL, 0);
int i=0;
while(1){
shm[i++] = 'A' + i;
shm[i] = 0;
sleep(1);
if(i == 27) break;
}
shmdt(shm);
return 0;
}
linux命令:
查看所有共享内存信息:
- 指令:
ipcs
###
------ Message Queues --------
key msqid owner perms used-bytes messages
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00005feb 0 root 666 12000 1
0x650101d0 1 whb 0 4096 0
------ Semaphore Arrays --------
key semid owner perms nsems
###
- 字段解释:
- key:ftok()的运行结果,共享内存在系统层唯一特殊标识
- shmid:shmget()的运行结果,共享内存在用户层唯一特殊标识
- owner:创建者
- perms:权限
- bytes:共享内存大小
- nattch:共享内存关联进程数
- status:共享内存当前状态
删除指定共享内存命令:
- 命令:
ipcrm -m shmid
- 共享内存由内核创建,内核维护,生命周期由OS内核决定而非具体进程
- 即使创建共享内存的进程终止,共享内存也不会释放,除非ipcrm或者关机