System V 引入了三种高级进程间的通信机制:消息队列、共享内存和信号量。IPC对象(消息队列、共享内存和信号量)存在内核中而不是文件系统中,由用户控制释放。
共享内存
共享内存是System V 进程间通信中速度最快的,共享内存不提供同步和互斥机制,所以这部分必须由用户来完成。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,也就是说进程不再通过执行进入内核的系统调用来传递彼此的数据。共享内存的生命周期随内核。共享区和共享内存属于用户的。
共享内存函数
shmget函数
功能:得到一个共享内存标示符或创建一个共享内存对象并返回共享内存标示符(共享内存创建是以页为基本单位的)
原型:
int shmget(key_t key,size_t size,int shmflg);
头文件:
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
参数:
key:此共享内存段名字
size:共享内存大小
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:
成功返回一个非负整数,即共享内存段的标识码;失败返回-1
shmat函数
函数功能:将指定的共享内存映射到进程的地址空间用于访问
原型:
void *shmat(int shmid,const void *shmaddr,int shmflg);
头文件
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
参数:
shmid:要映射的共享内存区标示符
shmaddr:将共享内存映射到指定的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:
成功返回一个指针,指向共享内存第一个节;失败返回-1
说明:
若shmaddr为NULL,则表示由系统自动完成映射SHM_RND标记映射的地址会自动向下调整为SHMLBAd整数倍。公式为:shmaddr-(shmaddr%SHMLBA);
shmflg=SHM_RDONLY,表示映射操作用来只读共享内存。
shmdt函数
功能:将内存段与当前进程脱离
原型:
int shmdt(const void *shmaddr);
头文件:
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
参数:
shmaddr:由shmat所返回的指针
返回值:
成功返回0,失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
shmctl函数
功能:用于控制共享内存
原型:
int shmctl(int shmid,int cmd,struct shmid_ds *buf);
头文件:
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
参数:
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:
成功返回0;失败返回-1
cmd三个可取值及说明:
IPC_STAT | 把shmid_ds结构中的数据设置为共享内存当前关联值 |
IPC_SET | 在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds数据结构中给出的值 |
IPC_RMID | 删除共享内存段 |
ftok函数的使用
ftok函数的定义:系统建立IPC通讯 (消息队列、共享内存和信号量) 时必须指定一个ID值。通常情况下,该id值通过ftok函数得到。
头文件
#include <sys/types.h>
#include <sys/ipc.h>
函数原型:
key_t ftok( const char * fname, int id )
fname就是你指定的文件名(已经存在的文件名),一般使用当前目录,如:
key_t key;
key = ftok(".", 1); 这样就是将fname设为当前目录。
id是子序号。虽然是int类型,但是只使用8bits(1-255)。
详解可参考链接:
https://www.jianshu.com/p/348714e5b05e
举例说明共享内存函数的运用
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>
#include<string.h>
#include<sys/wait.h>
int main()
{
key_t key=ftok(".",'a');//根据文件路径和序列号获得key
if(key<0)
{
perror("ftok");
return -1;
}
int shmid=shmget(key,1024*1024,IPC_CREAT|0644);//创建或打开共享内存,通过ftok得到key
if(shmid<0)
{
perror("shget");
return -1;
}
//将共享内存映射到用户空间
char *p=shmat(shmid,NULL,0);
if(p==(char *)-1)
{
perror("shmat");
return -1;
}
pid_t pid;
if((pid=fork())<0)
{
perror("fork");
return -1;
}
else if(pid== 0)
{
strcpy(p,"hello,world");
strcpy(p,"happy");
}
else
{
char buf[100]={0};
wait(NULL);
printf("p= %s\n",p);
printf("p+3 = %s\n",p+3);
}
//解除共享内存映射
if(shmdt(p)<0)
{
perror("shmdt");
return -1;
}
//删除共享内存
if(shmctl(shmid,IPC_RMID,NULL)<0)
{
perror("shmctl");
return -1;
}
return 0;
}
System V消息队列
- 消息队列提供了一个从一个进程向里另一个进程发送一块数据的方法。
- 每个数据块都被认为是有一个类型,接受者进程接收的数据块可以有不同的类型值。
- 特性方面:IPC资源必须删除,否则不会自动清零,除非重启,所以system V IPC资源的生命周期随内核。
System V信号量
信号量是用来描述临界资源当中资源数目的,信号量本质上是一个计数器,信号量在自增或自减时必须保证自己是原子的。
信号量主要用于同步和互斥
进程互斥
- 由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥。
- 系统中的某些资源一次只允许一个进程使用,称这样的资源为临界资源。
- 在进程中涉及到互斥资源的程序段叫做临界区。
信号量的减减叫P操作,信号量的加加叫V操作。二元信号量就是互斥锁,其临界资源信号量为1.
理解信号量及PV操作 参考链接: