引言
通信的本质是传递数据,是互相的。
进程间不能直接相互传递数据,进程具有独立性,所有的数据操作,都会发生写时拷贝,一定要通过中间媒介的方式来进行通信。
进程间通信的本质:让不同的进程先看到同一份资源(内存空间)。
进程通信
目的:
数据传输:一个进程需要将它的数据发送给另一个进程。
资源共享:多个进程之间共享同样的资源。
通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
进程控制:有些进程希望完全控制另一个进程的执行,此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
进程间通信发展
1.管道
2.System V进程间通信
3.POSIX进程间通信
进程间通信分类
管道:
1.匿名管道pipe
2.命名管道
System V IPC
1.System V消息内存
2.System V共享内存
3.System V信号量
POSIX IPC
1.消息队列
2.共享内存
3.信号量
4.互斥量
5.条件变量
6.读写锁
管道
匿名管道
供具有血缘关系的进程,进行进程间通信。(常见于父子)
管道只能进行单向数据通信。
因此,父子进程关闭不需要的文件描述符,来达到构建单向通信的信道的目的。
为什么曾经要打开?
不打开读写,子进程拿到的文件打开方式必定和父进程一样,无法通信。
建立管道代码:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
int main()
{
int pipe_fd[2] = {0};
if(pipe(pipe_fd) < 0)
{
perror("pipe");
return 1;
}
printf("%d", %d\n",pipe_fd[0],pipe_fd[1]);
pid_t id = fork();
if(id<0)
{
perror("fork");
return 2;
}
else if(id == 0)
{
//关闭读
close(pipe_fd[0]);
const char *msg = "hello parent ,i am child";
int count =5;
while(count)
{
write(pipe_fd[1],msg,strlen(msg));
sleep(1);
count--;
}
close(pipe_fd[1]);
exit(0);
}
else
{
//关闭写
close(pipe_fd[1]);
char buffer[64];
while(1)
{
buffer[0] = 0;
ssize_t size = read(pipe_fd[0],buffer,sizeof(buffer) - 1);
if(size>0)
{
buffer[size] = 0;
printf("parent get message from child# %s\n",buffer);
}
else if(size == 0)
{
printf("pipe file close,child quit!\n");
break;
}
else
{
//...........
}
}
int status = 0;
if(waitpid(id,&status,0)>0)
{
printf("child quit,wait success!\n");
}
close(pipe_fd[0]);
}
return 0;
}
进程间同步:
如果管道里面没有消息,父进程(读端)在等待,等管道内部有数据就绪(子进程写入)。
如果管道里面写端已经写满了。继续写入是不能写的,它在等待,等待管道内部有空闲空间(父进程读走)。
管道本身就是一个文件。
管道的特性:
1.管道自带同步机制!
2.管道是单向通信的!
3.管道是面向字节流的!
4.管道只能保证是具有血缘关系的进程进行通信,常用于父子进程。
5.管可以保证一定程度的数据读取的原子性!
进程退出,曾经打开的文件也会被关掉。管道也是文件,管道的生命周期随进程!
读取关闭,一直写,毫无意义。本质就是在浪费系统资源,写进程会立马被OS终止掉!
命名管道
不相关的进程之间进行进程间通信叫做命名管道。
匿名管道:父子共享文件的特征
命名管道:文件路径具有唯一性,让进程看到同一个文件
共享内存
进程间通信的本质:要先让不同的进程看到同一份资源!
1.OS申请一块物理内存空间
2.OS将该内存映射进对应进程的共享区中(堆栈之间)
3.OS可以将映射之后的虚拟地址返回给用户
1.申请共享内存
2.进程1和进程2分别挂接对应的共享内存到自己的地址空间(共享区)
3.双方就看到了同一份资源!即可以进行正常通信了!
操作系统内部提供了通信机制的(IPC)ipc模块
查看共享内存:ipcs -m
所有的ipc资源都是随内核的,不随进程。
删除共享内存:ipcrm -m shmid号
//创建key
key_t k = ftok(PATH_NAME,PROJ_ID);
//申请共享内存
int shmid = shmget(k,SIZE,IPC_CREAT|IPC_EXCL);//共享内存如果不存在,创建;如果存在,出错返回。
//释放共享内存
shmctl(shmid,IPC_RMID,NULL);
//将当前进程和共享内存进行关联
char* start = (char*)shmat(shmid,NULL,0);
//将当前进程和共享内存去关联
shmdt(start);
1.共享内存的生命周期随OS
2.共享内存不提供任何同步与互斥的操作,双方彼此独立
3.共享内存是所有的进程间通信中,速度最快的
共享内存的大小:系统在分配shm的时候,是按照4kb为基本单位的。
key:是一个用户层生成的唯一键值,核心作用是为了区分“唯一性”,不能用来进行IPC资源的操作!
shmid:是一个系统给我们返回的IPC资源标识符,用来进行操作IPC资源。
类比:
key,文件的inode号
shmid,文件的fd
信号量
1.让进程看到同一份资源(内存空间),这份资源叫做临界资源。
2.进程内的所有代码,不是全部的代码都在访问临界资源,而是只有一部分在访问。可能造成数据不一致问题的是这部分少量的代码。(临界区代码)
3.为了避免数据不一致,保护临界资源,需要对临界区代码进行某种保护。(方法叫做互斥)
4.互斥:一部分空间任何时候,有且只能有一个进程在进行访问。串行化的执行(锁,二元信号量)
5.加锁和解锁是有对应的代码的。本质:对临界区进行加锁和解锁,完成互斥操作。
信号量:本质是一个计数器。(用来描述临界资源中,资源数目的计数器)
申请信号量:P操作(计算器减减)
释放信号量:V操作 (计数器加加)
PV操作的伪代码:
P:
begin:
Lock();
if(count<=0)
{
goto begin;
}
else
{
count--;
}
Unlock();
//内部访问临界资源。
V:
Lock();
count++;
Unlock();
1.多个进程不能操作同一个count值
2.信号量是保护临界资源的安全性
信号量本身就是一个临界资源。
1.信号量,多进程环境下,如何保证信号量被多个进程看到?
semget,semctl,ftok() ---->Key ----->IPC
2.如果信号量的计数器的值是:1
则为1,0两种结果,二元信号量---->就是一种互斥语义。