三、system V
1、共享内存概念及原理
- 概念:
- 管道通信本质是基于文件的,也就是说操作系统并没有为此做过多的设计工作,而systemV IPC是操作系统特地设计的一种通信方式;但是不管怎么样,它们的本质都是一样的,都是在想尽办法让不同的进程看到同一份由操作系统提供的资源
- 共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据
- system V IPC提供的通信方式有以下三种:
- system V共享内存
- system V消息队列
- system V信号量
注:system V共享内存和system V消息队列是以传送数据为目的的,而system V信号量是为了保证进程间的同步与互斥而设计的,虽然system V信号量和通信好像没有直接关系,但属于通信范畴
- 共享内存的基本原理:
- 用户申请共享内存:OS在物理内存当中申请一块内存空间
- 进程主动挂接共享内存:OS将这块内存空间分别与各个进程的进程地址空间建立映射关系(共享内存映射进进程地址空间的共享区)
- 各进程看到同一空间资源:OS将映射后的的共享内存的虚拟地址返回给进程
- 示图:
注:这里所说的开辟物理空间、建立映射等操作都是调用系统接口完成的,也就是说这些动作都由操作系统来完成
- 共享内存数据结构:
- 各个进程都可以进行申请共享内存,那么共享内存的需求就可能非常多,而OS也需要进行对共享内容的管理,而管理的本质就是:先描述,再组织
- 所以共享内存除了在内存当中真正开辟空间之外,系统一定还要为共享内存维护相关的内核数据结构
- shmid_ds结构定义:
struct shmid_ds { struct ipc_perm shm_perm; /* operation perms */ int shm_segsz; /* size of segment (bytes) */ __kernel_time_t shm_atime; /* last attach time */ __kernel_time_t shm_dtime; /* last detach time */ __kernel_time_t shm_ctime; /* last change time */ __kernel_ipc_pid_t shm_cpid; /* pid of creator */ __kernel_ipc_pid_t shm_lpid; /* pid of last operator */ unsigned short shm_nattch; /* no. of current attaches */ unsigned short shm_unused; /* compatibility */ void *shm_unused2; /* ditto - used by DIPC */ void *shm_unused3; /* unused */ };
- 注意:
- 当申请了一块共享内存后,为了让要实现通信的进程能够找到同一个共享内存进行挂接,每一个共享内存结构体中会存储一个key值,这个key值用于标识系统中共享内存的唯一性
- 上面共享内存数据结构的第一个成员shm_perm,每个共享内存的key值存储在shm_perm这个结构体变量当中
- ipc_perm结构体的定义:
struct ipc_perm{ __kernel_key_t key; __kernel_uid_t uid; __kernel_gid_t gid; __kernel_uid_t cuid; __kernel_gid_t cgid; __kernel_mode_t mode; unsigned short seq; };
- 共享内存使用过程:
- 调用系统接口进行在物理内存中申请共享内存空间
- 调用接口将申请到的共享内存挂接到地址空间,建立映射关系
- 使用之后调用接口将共享内存与地址空间去关联,取消映射关系
- 调用接口释放共享内存空间,将物理内存归还给系统
2、共享内存使用接口介绍
1、共享内存资源的查看
- 如何查看共享内存资源:
使用ipcs命令查看有关进程间通信设施的信息
- 选项:
-q:列出消息队列相关信息 -m:列出共享内存相关信息 -s:列出信号量相关信息
注:单独使用ipcs命令时,会默认列出消息队列、共享内存以及信号量相关的信息
- 示图:
- ipcs输出信息含义:
标题 | 含义 |
key | 系统区别各个共享内存的唯一标识 |
shmid | 共享内存的用户层id(句柄) |
owner | 共享内存的拥有者 |
perms | 共享内存的权限 |
bytes | 共享内存的大小 |
nattch | 关联共享内存的进程数 |
status | 共享内存的状态 |
注:key标识共享内存唯一性的方式,而shmid是用于用户指明操作对象,key和shmid之间的关系类似于inode和fd之间的的关系
2、共享内存的创建和释放
- ftok函数的函数原型:
key_t ftok(const char *pathname, int proj_id);
- 解释:
功能:将一个已存在的路径名pathname和一个整数标识符proj_id转换成一个key值,称为IPC键值,在使用shmget函数获取共享内存时,这个key值会被填充进维护共享内存的数据结构当中
- 注意:
- pathname所指定的文件必须存在且可存取;使用ftok函数生成key值存在可能会产生冲突
- 进行通信的各个进程在使用ftok函数获取key值时,需要采用同样的路径名和和整数标识符,进而生成同一种key值找到同一份共享内存
- shmget函数的函数原型:
#include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, size_t size, int shmflg);
- 解释:
- 功能:向系统申请共享内存
- 参数:第一个参数key,表示待创建共享内存在系统当中的唯一标识;第二个参数size,表示待创建共享内存的大小;第三个参数shmflg,表示创建共享内存的方式
- 返回值:shmget调用成功,返回一个有效的共享内存标识符,用于进行操作;shmget调用失败,返回-1
注:这里shmget函数的返回值实际上就是共享内存的句柄,这个句柄可以在用户层标识共享内存,当共享内存被创建后,我们在后续使用共享内存的相关接口时,都是需要通过这个句柄对指定共享内存进行各种操作
- 第三个参数shmflg常用组合方式:
组合方式 | 作用 |
IPC_CREAT | 如果内核中不存在与key相等的共享内存,则新建一个共享内存并返回该共享内存的句柄;如果存在这样的共享内存,则直接返回该共享内存的句柄,即该共享内存可能是已有的也可能的新建的 |
IPC_CREAT | IPC_EXCL | 如果内核中不存在与key相等的共享内存,则新建一个共享内存并返回该共享内存的句柄;如果存在这样的共享内存,则出错返回,即如果成功该共享内存一定是新建的共享内存 |
- 示例:
#include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <unistd.h> #define PATHNAME "./server.c" #define PROJ_ID 0x6666 #define SIZE 4096 int main() { key_t key = ftok(PATHNAME, PROJ_ID); //获取key值 if (key < 0){ perror("ftok"); return 1; } int shm = shmget(key, SIZE, IPC_CREAT | IPC_EXCL); //创建共享内存 if (shm < 0){ perror("shmget"); return 2; } printf("key: %x\n", key); printf("shm: %d\n", shm); return 0; }
- 效果:
- 注意:
- 进程运行完毕后,申请的共享内存依旧存在,即共享内存的生命周期是随内核的,也就是说共享内存并不会主动随进程的退出而释放
- 如果进程不主动删除创建的共享内存,那么共享内存就会一直存在,直到关机重启(system V IPC都是如此)
- 在命令行中我们可以使用命令ipcrm -m shmid释放共享内存
示图:
- shmctl函数的函数原型:
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- 解释:
- 功能:控制对应的共享内存资源
- 参数:第一个参数shmid表示要控制的共享内存;第二个参数cmd,表示具体的控制动作;第三个参数buf,用于获取或设置所控制共享内存的数据结构
- 返回值:shmctl调用成功,返回0;shmctl调用失败,返回-1
- shmctl函数的第二个参数常用的传入选项:
选项 | 作用 |
IPC_STAT | 获取共享内存的当前关联值,此时参数buf作为输出型参数 |
IPC_SET | 在进程有足够权限的前提下,将共享内存的当前关联值设置为buf所指的数据结构中的值 |
IPC_RMID | 删除释放共享内存段 |
注:一般使用接口进行释放对应的共享内存资源
3、共享内存的链接与去连接
- shmat函数的函数原型:
void *shmat(int shmid, const void *shmaddr, int shmflg);
- 解释:
- 功能:将共享内存与进程建立映射关系
- 参数:第一个参数表示要关联的共享内存的对应的shmid;第二个参数shmaddr指定共享内存映射到进程地址空间的某一地址,通常设置为NULL,表示让内核自己决定一个合适的地址位置;第三个参数shmflg,表示关联共享内存时设置的某些属性,一般设置为0
- 返回值:shmat调用成功,返回共享内存映射到进程地址空间中的起始地址;shmat调用失败,返回(void*)-1
- shmat函数第三个参数的常用传入选项:
选项 | 作用 |
SHM_RDONLY | 关联共享内存后只进行读取操作 |
SHM_RND | 若shmaddr不为NULL,则关联地址自动向下调整为SHMLBA的整数倍。公式:shmaddr-(shmaddr%SHMLBA) |
0 | 默认为读写权限 |
- shmdt函数的函数原型:
int shmdt(const void *shmaddr);
- 解释:
- 功能:取消共享内存与进程的映射关系
- 参数:待去关联共享内存的起始地址,即调用shmat函数时得到的起始地址
- 返回值:shmdt调用成功,返回0;shmdt调用失败,返回-1
4、接口使用示例
- 示例:
servershm.c: #include<stdio.h> #include<sys/ipc.h> #include<sys/shm.h> #include"Common.h" #include<stdlib.h> #include<unistd.h> int main() { //申请唯一key值 key_t key=ftok(PATHNAME,PROJ_ID); if(key<0) { perror("ftok"); exit(1); } printf("key alreadly creat...\n"); sleep(3); //创建共享内存资源,保证一定是新的共享内存资源,并设置权限为0644 int shm_id=shmget(key,SIZE,IPC_CREAT|IPC_EXCL|0644); if(shm_id<0) { perror("shmget"); exit(2); } printf("shm alreadly get...\n"); sleep(3); //以读写方式链接共享内存资源 char* start=(char*)shmat(shm_id,NULL,0); printf("server alreadly at shm...\n"); //进行读写操作 while(1) { printf("%s\n",start); sleep(1); } shmdt(start); printf("server alreadly dt shm...\n"); sleep(2); shmctl(shm_id,IPC_RMID,NULL); printf("delete shm...\n"); return 0; } clientshm.c: #include<stdio.h> #include<sys/ipc.h> #include<sys/shm.h> #include"Common.h" #include<stdlib.h> #include<unistd.h> int main() { //申请唯一key值 key_t key=ftok(PATHNAME,PROJ_ID); if(key<0) { perror("ftok"); exit(1); } //获取共享内存id int shm_id=shmget(key,SIZE,IPC_CREAT); if(shm_id<0) { perror("shmget"); exit(2); } sleep(3); //链接共享内存资源 char* start=(char*)shmat(shm_id,NULL,0); //进行读写操作 char ch='a'; while(ch<='z') { start[ch-'a']=ch; ch++; sleep(2); } sleep(5); shmdt(start); return 0; } Common.h: #define PATHNAME "/home/zgj/lesson/lesson16/shmdir" #define PROJ_ID 0x666 #define SIZE 4097
- 效果:
注:共享内存没有进行同步与互斥,读端并不会管写端写的原子性
3、共享内存与管道对比
- 共享内存通信方式需要进行的拷贝次数最少,由此速度最快对于管道通信数据传输过程:将数据先写到管道缓冲区,再冲管道缓冲区中读取数据
共享内存通信数据传输过程:直接对共享内存进行读写- 共享内存也是有缺点的,管道是自带同步与互斥机制的,但是共享内存并没有提供任何的保护机制,包括同步与互斥
4、消息队列/信号量
- 消息队列概念:
- 消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法
- 每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值
- 特性方面:
IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核
- 消息队列的基本原理:
- 消息队列实际上就是在系统当中创建了一个队列,队列当中的每个成员都是一个数据块,这些数据块都由类型和信息两部分构成
- 两个互相通信的进程通过某种方式看到同一个消息队列,这两个进程向对方发数据时,都在消息队列的队尾添加数据块,这两个进程获取数据块时,都在消息队列的队头取数据块
- 信号量概念:
- 信号量主要用于同步和互斥的,进程之间存在对资源的竞争性,但是资源有限,需要保证对象获取资源的个数在承受范围之内
- 就相当于每个进程在获取资源之前,需要先通过信号量获取获得资源的一个凭证,就像一个预定机制一样
- 也就是说,每个进行也需要竞争获取信号量资源,即信号量也是一个临界资源,此时就需要信号量本身就原子的,其对应的操作具有原子性
- 从本质上来说,信号量是用来描述临界资源数目的一个计数器
- 注意:
- 由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥
- 系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源,在进程中涉及到互斥资源的程序段叫临界区
- 特性方面:
IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核