三、System V IPC
3.1 共享内存
3.1.1 共享内存原理
共享内存让不同进程看到同一份资源的方式是:在物理内存中申请一块内存空间,然后将这块内存空间分别与各个进程各自的页表之间进行联系,再在虚拟地址空间当中开辟空间并将虚拟地址填充到各自页表的对应位置,使得虚拟地址和物理地址之间建立起映射关系,至此进程便看到同一份物理内存
3.1.2 "描述"共享内存
在系统当中可能会有大量的进程在进行通信,因此系统当中就可能存在大量的共享内存,那么操作系统必然要对其进行管理,所以共享内存除了在内存当中真正开辟空间之外,为了维护管理共享内存,系统一定要"描述"共享内存
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,shm_perm是一个ipc_perm类型的结构体变量,每个共享内存的key值存储在shm_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; };
注意:shmid_ds和ipc_perm结构体分别在/usr/include/linux/shm.h和/usr/include/linux/ipc.h中定义
3.1.3 查看共享内存信息
使用ipcs命令可以查看共享内存的信息。但是该命令会默认列出消息队列、共享内存以及信号量相关的信息,若只想查看某一个的相关信息,可以选择携带选项
-q:列出消息队列相关信息
-m:列出共享内存相关信息
-s:列出信号量相关信息
ipcs命令列出的每列信息的含义如下:
注意: key是内核层面上保证共享内存唯一性的方式;shmid是在用户层上保证共享内存的唯一性
3.1.4 共享内存的创建
使用shmget()函数进行共享内存的创建
int shmget(key_t key, size_t size, int shmflg);
返回值:
shmget调用成功,返回一个有效的共享内存标识符(用户层标识符)
shmget调用失败,返回-1
参数:
参数key,表示待创建共享内存在系统当中的唯一标识(需要自行生成)
参数size,表示待创建共享内存的大小
参数shmflg,表示创建共享内存的方式,可添加共享内存权限(按位或)
注意: 具有标定某种资源能力的东西被称作句柄(譬如FILE即文件句柄),而shmget函数的返回值实际上就是共享内存的句柄,其可以在用户层标识共享内存。当共享内存被创建后,在后续使用共享内存的相关接口时,都需要通过这个句柄对指定共享内存进行操作
使用ftok函数获取参数key
key_t ftok(const char *pathname, int proj_id);
ftok函数的作用就是,将一个已存在的路径名pathname和一个整数标识符proj_id转换成一个key值,称为IPC键值,在使用shmget函数获取共享内存时,这个key值会被填充进维护共享内存的数据结构当中
注意:
- 使用ftok函数生成key值可能会产生冲突,此时可以对传入ftok函数的参数进行修改
- 需要进行通信的各个进程,在使用ftok函数获取key值时,都需要采用同样的路径名和和整数标识符,进而生成同一种key值,然后才能找到同一个共享资源
- pathname所指定的文件必须存在且可存取(有权限)
参数shmflg
选项(组合) | 作用 |
IPC_CREAT | 若不存在键值与key相等的共享内存,则新建一个共享内存并返回该共享内存的句柄;若存在这样的共享内存,则直接返回该共享内存的句柄 |
IPC_CREAT | IPC_EXCL | 若内核中不存在键值与key相等的共享内存,则新建一个共享内存并返回该共享内存的句柄;若存在这样的共享内存,则出错返回 |
IPC_EXCL | 单独使用无任何作用 |
使用组合IPC_CREAT,一定会获得一个共享内存的句柄,但无法确认该共享内存是否是新建的共享内存
使用组合IPC_CREAT | IPC_EXCL,只有shmget函数调用成功时才会获得共享内存的句柄,并且该共享内存一定是新建的共享内存
3.1.5 共享内存的释放
当进程运行完毕后申请的共享内存依旧存在,并没有被操作系统释放,因为共享内存的生命周期是随内核的。若进程不主动删除创建的共享内存,那么共享内存就会一直存在,直到关机重启(system V IPC都是如此)。
用命令释放共享内存资源
使用ipcrm -m shmid命令
使用程序接口释放共享内存资源
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数shmid,表示所控制共享内存的用户级标识符
参数cmd,表示具体的控制动作
第参数buf,用于获取或设置所控制共享内存的数据结构
参数cmd:
返回值:
shmctl调用成功,返回0
shmctl调用失败,返回-1
3.1.6 共享内存的关联
建立共享内存与进程地址空间的映射关系时需要使用shmat函数
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数shmid,表示待关联共享内存的用户级标识符
参数shmaddr,指定共享内存映射到进程地址空间的某一地址,通常设置为NULL,表示让内核自行决定合适的地址位置
参数shmflg,表示关联共享内存时设置的某些属性
参数shmflg:
返回值:
shmat调用成功,返回共享内存映射到进程地址空间中的起始地址
shmat调用失败,返回(void*)-1
3.1.7 共享内存的去关联
取消共享内存与进程地址空间之间的关联需使用shmdt()函数
int shmdt(const void *shmaddr);
shmaddr参数:
待去关联共享内存的起始地址,即调用shmat函数时得到的起始地址
返回值:
shmdt调用成功,返回0
shmdt调用失败,返回-1
3.1.8 利用共享内存实现serve&&client通信
服务端创建一个新的共享内存并建立连接
//my_server.cxx #include "com.h" 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 | 0666); //创建新的共享内存 if (shm < 0) { perror("shmget"); return 2; } char* mem = (char*)shmat(shm, NULL, 0); //关联共享内存 //服务端不断读取共享内存当中的数据并输出 while (1) { printf("client# %s\n", mem); sleep(1); } shmdt(mem); //共享内存去关联 shmctl(shm, IPC_RMID, NULL); //释放共享内存 return 0; }
客户端连接的是已创建好的共享内存,并往其中不断写入数据
//my_client.cxx #include "com.h" int main() { key_t key = ftok(PATHNAME, PROJ_ID); //获取与server进程相同的key值 if (key < 0) { perror("ftok"); return 1; } int shm = shmget(key, SIZE, IPC_CREAT); //获取server进程创建的共享内存的用户层id if (shm < 0) { perror("shmget"); return 2; } char* mem = (char*)shmat(shm, NULL, 0); //关联共享内存 //客户端向共享内存写入数据 int i = 0; while (1) { mem[i++] = 'A' + i; mem[i] = '\0'; sleep(1); } shmdt(mem); //共享内存去关联 return 0; }
服务端和客户端代码中都包含该头文件,确保生成的key、路径、连接的共享内存相同
#include <stdio.h> #include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <unistd.h> #define PATHNAME "/home/bjy/BaoLinux/code/signal_com/shm/shm_2" //路径名 #define PROJ_ID 0x6666 //整数标识符 #define SIZE 4096 //共享内存的大小
3.1.9 共享内存与管道对比
共享内存创建好后就不需要调用系统接口进行通信了,而管道创建好后仍需要read、write等系统接口进行通信。共享内存是所有进程间通信方式中最快的一种通信方式
使用管道通信的方式,将文件中的数据从一个进程传输到另一个进程需要进行四次拷贝操作:
- 服务端将信息从输入文件复制到服务端的临时缓冲区中
- 将服务端临时缓冲区的信息复制到管道中
- 客户端将信息从管道复制到客户端的缓冲区中
- 将客户端临时缓冲区的信息复制到输出文件中
使用共享内存进行通信,将文件中的数据从一个进程传输到另一个进程只需要进行两次拷贝操作:
1.从输入文件到共享内存
2.从共享内存到输出文件
共享内存同样也存在问题,其并没有提供任何的保护机制,类似于同步与互斥
3.2 消息队列
该技术已面临淘汰,等博主有时间会进行更新
3.3 信号量
后续博主有时间会进行更新