目录
临界资源、临界区、同步/异步,P/V操作,阻塞/非阻塞,同步/互斥
一、了解多进程
所谓多进程是指能够同时完成多个任务,多进程进行工作时,进程之间需要通讯(传递数据)。
二、进程间通讯方式
进程间通讯方式主要有:管道、信号量、消息队列、共享内存以及socket(网络中通讯用到);
首先要明确在多进程中进程之间是相互独立的。在本节中主要介绍管道、信号量、消息队列以及共享内存四种通讯方式。
1、管道
利用管道进行通讯的时候,无论是通过有名管道还是无名管道,它们都属于半双工通讯,所谓的半双工通讯指的是在同一时刻数据流向是单向的。也就是说无论哪一端都可以是读或者写,但是在同一时刻它只能是读或者写。
(1)有名管道(也称为命名管道)
在磁盘上会存出一个管道文件标识(inode),但是它并不会占据磁盘空间(block),数据不会存储到磁盘上,数据是被缓存在内存上;
限制:同一台主机上同一个系统有权限操作管道文件的任意进程都可以通过其完成进程间通讯;
如图所示,A、B两个进程之间要进行通讯的时候,通过管道文件访问到同一内存区域,实现之间的通讯。
有名管道的使用:
创建管道文件:命令: mkfifo 文件名
函数: int mkfifo(const char* pathname, int mode);
Pathname:路径+文件名
Mode:权限
打开管道文件:int open();//仅仅打开,若文件不存在,不能创建;
写:int write();//会阻塞
读:int read();//会阻塞,read阻塞:管道中没有数据; 当管道中有数据或所有写端关闭,read返回;
关闭:int close();
Open阻塞在仅仅一端打开时,直到有读端有写端,open才会返回;
Wirte阻塞,当管道缓存区满;管道中有空间或者所有读端关闭,write才会返回;读与写的次数无必然联系;
(2)无名管道
无名管道不存在管道文件,其借助于父子进程共享fork之前打开的文件描述符,数据缓存在内存中。
限制:只能用在父子进程之间;
创建并打开无名管道:
Int pipe(int fds[2]);
Fds[0]:管道读端
fds[1]:管道写端
注意:
如果父进程向子进程发送数据,父进程直接关闭读端;
反之,子进程直接关闭写端;
读端判断是否通讯完成:read返回0;
子进程不关闭写端;
父进程结束,read不返回;
2、信号量
临界资源、临界区、同步/异步,P/V操作,阻塞/非阻塞,同步/互斥
在了解信号量之前,对于以下的概念也要进行了解:
临界资源:同一时刻只能被一个进程所使用的资源;
临界区:访问临界资源的代码区域;
原子操作:不能被终止,不能被暂停的,一旦开始操作,必须等待其结束。
P操作:对临界资源进行-1的操作;申请临界资源;
V操作:对临界资源进行+1的操作;释放临界资源;
阻塞:发出请求后,如果请求不能立即响应,则进程被挂起,等待请求响应。从而造成进程执行阻塞;
非阻塞:发出请求后,无论请求能否响应,进程都将继续执行下去;
进程同步:发出请求后,如果请求不能立即响应,则不断的去探测条件是否发生。多进程协同工作;
进程异步:发出请求后,进程继续往下执行,但是在请求的条件发生后,会通过内核消息通知机制,通知该进程条件已经发生;进程间执行互不影响,同时向下执行;
同步/互斥:互斥是两个进程是竞争关系对立的。
阻塞和非阻塞关注的是进程在发出请求后的状态,同步和异步关注的是进程发出请求后,进程如何获知请求的条件已经发生;
(1)何为信号量?
信号量不实现数据的发送,用于多进程访问临界资源时;进行进程同步控制;
类似于计数器,当计数器的值>0,表示临界资源的个数;
当临界资源==0时,表示无临界资源可用,此时若进行临界资源的申请,也就是执行P操作,会被阻塞。当执行V操作,信号量的值会被+1;
(2)信号量的使用
信号量的内核对象维护的是信号量集,也就是说信号量的数组
①创建或者获取信号量:
Int semget((key_t)key, int nsems,int flag);
Nsems:创建时,指定信号量集中信号量的个数;
②初始化信号量的值:
Int semctl(int semid, int semnum, int cmd, /*union semun {int val}*/);
Union semun val
{
Int val;
/*
Struct sem_id *buf;
Short *array;
*/
};
销毁:IRC_RMID
(3)P/V操作:用来操作信号量集
Int semop(int semid, struct sembuf[],int size);//size表示数组大小;
Struct sembuf
{
Short sem_num;//操作哪个信号量
Short sem_op;//P/V
Short sem_flag;//控制模式
};
3、消息队列
(1)消息
消息是指发送带有类型的数据,并且是按条发送,也是按条接收的;实现多进程间的定向发送。
(2)消息队列
在同一类型上符合先进先出的规则,对于整个消息队列可以类比与优先级队列;消息队列是在两个进程之间传递二进制块数据的一种简单有效的方式。每个数据块都有一个特定的类型,接收方可以根据类型来有选择的接收数据,而不一定像管道和命名管道那样必须以先进先出的方式接收数据。
(3)消息队列的操作
创建或者获取消息队列:
Int msgget((key_t)key, int flag);//flag:权限控制位(IPC_CREAT |0555)
成功返回内核对象的标识符id,失败返回-1;
发送:
Int msgsnd(int msdig, void *ptr,int datalen,int flag);//datalen:数据长度;flag:标记
Ptr指向的数据类型包含一个long的类型字段,一个数据字段;
Struct data
{
Long type;
Char text[128];//128 数据的有效长度,参数中的datalen
}
接收:
Int msgrcv(int msgid,void *ptr,int size,long type,int flag);
删除内核对象,即IPC结构:
Int msgctl(int msgid,int cmd,struct msgid_ds *buff);//cdm:IPC_RMID;
4、共享内存
(1)实现机制:内核地址
如图所示:共享内存的物理内存空间既属于A进程,又属于B进程。
如果ptr指向malloc申请的内存空间则4G虚拟地址空间上的区域,还是其映射的物理内存空间,是属于进程本身。
两进程的虚拟地址空间相等与否,对于共享内存而言,无影响。因为虚拟地址需要通过进程页表,进行页面映射才能找到物理内存空间。共享内存共享的是物理内存空间。
两个进程共享一块物理内存空间,可以同时使用此空间,共享内存空间属于临界资源,进程访问时必须互斥访问,必须通过同步机制(信号量)来控制其访问。
(2)操作 头文件#include<sys/shm.h>
创建或者获取内核对象:
Int shmget(key_t key, int nbytes, int flag);
Nbytes:开辟的共享内存区域大小;按页面对齐,4k
删除内核对象:立即删除
Int shmctl(int shmid, int cmd, struct shmid_ds *buf);
将虚拟内存空间上一个虚拟地址映射到shmid指定内核对象开辟的物理内存上,并将虚拟地址返回;
Void *shmat(int shmid ,void *addr, int flag);
将虚拟地址与共享内存区域断开连接:
Int shmdt(void *ptr);
(3)共享内存是一种最快的IPC
在通讯之前,访问内核对象,用户态切换到内核态;发送数据时,直接用指针操作空间,不需要进行用户态切换内核态;
共享内存可以直接通过指针将数据写到共享内存区域,接收方直接通过指针直接接收数据。区别于管道,消息队列少了两次数据的拷贝。