因为进程的独立性,所以进程间通信变得麻烦,但有时又需要进程之间的通信:数据传输、资源共享、通知事件(进程退出时需要告知父进程)、进程控制。
系统就提供了以下几种进程间通信的方式:
一:管道
- 匿名管道,能在父、子进程间传递数据,一个进程创建一个管道,fork()子进程后,int pipe(int fd[2])创建管道。两个管道文件描述符(fd[0]和fd[1])都打开,因为管道是半双工的,所以一对这样的文件描述符只能保证一个方向的数据传输,要是先父进程向子进程写入数据。若要实现双向通信,就必须有两个管道。并且内核会对管道操作进行同步与互斥(一端操作的时候,另一端不能操作;并且当一方操作完成后,另一方才能操作)。
- 命名管道:没有关系的进程间通信,用 int mkfifo(const char *pathname, mode_t mode)。函数可以创建一个命名管道(特殊类型的文件),pathname是创建这个文件的名字,mode是这个文件的权限。命名管道与匿名管道的区别只有创建和打开的方式不同,在工作原理上是相同的。当两个进程通信后,会产生一个 文件,相当于一个缓冲区,两个进程对其进行(读/写)操作。
- 这里是管道进行通信的demo
二:消息队列
消息队列是在两个无关的进程间的传递数据的一种简单、高效的通信方式。消息队列是一种消息链表,由消息队列标识符组成,提供进程之间数据块传输的方法,用ipcs -q查看消息队列,ipcrm -q msgid来销毁。
下图是消息队列的结构:
消息队列和管道的区别:
- 进程结束后,用于通信的管道也就没有了;而消息队列还会存在(除了用IPC_RMID删除消息队列)。
- 管道是文件,在磁盘上,访问效率低;消息队列是数据结构,在内存中,访问效率高。
- 管道是流式存取,需要给出访问长度;而消息队列是块式存取。
消息队列的相关函数:
msgget:用来创建和访问一个消息队列
int msgget(key_t key, int msgflg);
key: 某个消息队列的名字
msgflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是⼀一样的
返回值:成功返回一个非负整数,即该消息队列的标识码;失败返回-1
msgctl:对消息队列的控制
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
msqid: 由msgget函数返回的消息队列标识码
cmd:是将要采取的动作,(有三个可取值)
IPC_STAT:把msqid_ds中数据设为当前关联值
IPC_RMID:删除消息队列
返回值:成功返回0,失败返回-1
msgsend:将消息添加到消息队列当中
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
msgid: 由msgget函数返回的消息队列标识码
msgp:是一个指针,指针指向准备发送的消息,
msgsz:是msgp指向的消息长度,这个长度不含保存消息类型的那个long int长整型
msgflg:控制着当前消息队列满或到达系统上限时将要发生的事情
msgflg=IPC_NOWAIT表⽰示队列满不等待,返回EAGAIN错误。
返回值:成功返回0;失败返回-1
msgrcv:从消息队列中获取消息
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
msgid: 由msgget函数返回的消息队列标识码
msgp:是一个指针,指针指向准备接收的消息,
msgsz:是msgp指向的消息长度,这个长度不含保存消息类型的那个long int长整型
msgtype:它可以实现接收优先级的简单形式
msgflg:控制着队列中没有相应类型的消息可供接收时将要发生的事
返回值:成功返回实际放到接收缓冲区里去的字符个数,失败返回-1
三:共享内存
创建一个共享内存区是最快的进程间通信方式,效率高。不需要进行多次的数据拷贝,直接从内存中进行读写。指同一块物理内存通过页表被映射到A、B两个进程地址空间。两个进程可以相互看到一方对于共享内存中数据的更新,鉴于此,就必须有一种同步机制(互斥锁、信号量)使双方能够知道数据的修改,因为共享内存本身没有任何的同步与互斥机制,所以用到了信号量实现对共享内存的存取的同步。所以共享内存的效率高,直接对内存进行读写。两个进程通信完毕之前,共享内存一直保持着。Linux支持多种共享内存方式,mmap( )系统调用、Posix共享内存、SystemV共享内存。
共享内存被映射到进程空间中,存在于进程的数据段,最大共享内存为0x2000 0000Byte。
mmap()文件映射(文件存储映射IO):将文件映射到一段内存上(虚拟内存),通过对这段内存的修改,而达到对文件的修改,mmap系统调用可以是进程之间映射一个普通的文件来实现共享内存。内存映射是将用户的一段空间映射到内核空间,成功后,用户对这段内存的修改,可以直接反映到内核空间。从而达到用户和内核之间进行大量数据传输的效率是非常高的。mmap并不是完全为了共享内存,本身是提供了一种不同于一般对文件的访问方式(read、write),进程可以像读写内存一样对文件进行操作。
System V共享内存映射:
SystemV共享内存:以文件的形式组织在特殊文件系统shm中的。通过shmget可以创建或获得共享内存的标识符。取得共享内存标识符后,通过shmat将内存区映射到本进程的虚拟地址空间,可以通过mmap( )映射文件(将一个文件映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中的一段虚拟地址一一对应)
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
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
两者对比:
- mmap是在磁盘上创建一个文件,每个进程都开辟一块空间进行映射。而相对于shm来说,其每个进程映射会映射到同一块物理内存,使读写速率快,但是不能存储太多。
- 在机器重启之后,因为mmap将文件保存在磁盘中,所以不会丢失;但shm不能保证。
- mmap更加简单,调用起来方便。
四:信号量
System V版本信号量也是IPC中的一种方法,相当于一个计数器。当多个进程同时访问系统上的某个资源时,需要考虑到进程的同步问题,确保一个时间只有一个只有一个进程拥有对该资源的访问权。
首先来了解一下进程相关的一些知识:
- 临界资源:临界资源说的是一次只能提供一个进程使用的资源。
- 互斥:是指某一个共享资源同时只允许一个访问者对其进行操作。
- 同步:按照一定的访问顺序对共享资源操作。
- 原子性:一个事务包含多个操作,这些操作要么全部执行,要么全都不执行。若处理事务中途遇到突发状况为能完成事务,就要回滚到最初状态,保证原子性。
- 锁:给临界区加锁,为了保护临界区,防止两个进程同时对其操作。
- 并行:同一时刻多个任务同时进行(进程)
- 并发:一段时间内,多个任务交替执行,不能同时处理(多线程)
信号量是为了解决有多个进程想要对共享资源进程访问,为了保证访问的同步与互斥,引入了信号量来保证同一时刻只有一个进程对共享资源操作,信号量具有原子性。程序对其操作是原子性的额,进行P/V操作。
信号量是由一个值和一个指针构成的,指针指向等待该信号量的进程,若为空,该信号量不被继承;值表示资源的使用情况,当值S>=0时,表示可用资源的个数,P操作是分配一个资源给某个进程使用,S值减1;当S<0时,其绝对值代表等待资源的进程个数,等待进程必须等待到有资源被释放时,被唤醒后才能继续运行,V操作是释放一个资源,S值加1。P/V操作必须成对出现。
进程通常处于就绪、运行和阻塞三个状态,三者转换是靠P/V操作控制的。
信号量和信号量集的相关操作: 信号量集是可以同时把多个共享资源设置为互斥资源
semget:创建和访问一个信号量集
int semget(key_t key, int nsems, int semflg);
key: 信号集的名字
nsems:信号集中信号量的个数
semflg: 由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该信号集的标识码;失败返回-1
semctl:用于控制信号量集
int semctl(int semid, int semnum, int cmd, ...);
semid:由semget返回的信号集标识码
semnum:信号集中信号量的序号,用semget的返回值接收所得
cmd:将要采取的动作(有三个可取值):关联、删除
返回值:成功返回0;失败返回-1
semop:用来创建和访问一个信号量集
int semop(int semid, struct sembuf *sops, unsigned nsops);
semid:是该信号量的标识码,也就是semget函数的返回值
sops:是个指向一个结构数值的指针
nsops:信号量的个数
返回值:成功返回0;失败返回-1