文章目录
- System V IPC
- System V 共享内存的直接原理
- System V共享内存的创建
- 挂接共享内存
- 取消挂接共享内存
- 释放共享内存
- 利用System V共享内存进行进程间通信
- 共享内存的特性
- 共享内存的属性
- 通过命名管道为共享内存添加同步互斥机制
System V IPC
System v
是一种操作系统和相关技术的合集,最初由 AT&T 开发,主要用于Unix
操作系统;
System v
是Unix
的一个重要版本;
System v IPC
则是其进程间通信机制,主要有:
-
共享内存
允许多个进程访问同一块内存区域以实现数据共享;
-
信号量
用于控制对共享资源的访问,提供进程间的同步和互斥;
-
消息队列
允许进程以消息的形式进行异步通信;
System V 共享内存的直接原理
每个进程中都有一个对应的task_struct
内核数据结构用于管理进程;
每个进程都有一个进程地址空间为虚拟地址,虚拟地址通过页表映射至物理内存中;
进程间通信的本质是时多个进程看到同一份资源;
共享内存的直接原理即由操作系统内核申请出一块物理内存空间,并将该空间的使用权移交给多个进程,即将该空间以挂接至多个进程的进程地址空间中使得多个进程可以同时访问同一块内存空间 (看到同一份"资源");
其原理与动态库的加载机制类似;
主要的操作为如下:
-
申请共享内存空间
申请共享内存空间主要分为需求方和执行方;
其中进程需要进行进程间通信,而内核不能直接将内存交给进程进行管理和使用所以进程将向操作系统内核申请;
-
将共享内存空间挂接到进程地址空间当中
当共享内存被创建后需要将共享内存挂接到进程地址空间中,即使进程与该共享内存进行关联;
如果需要释放共享内存则需要:
-
去关联
当需要释放共享内存时在此之前需要使得该共享内存没有再被某些进程进行关联,确保进程的读写操作不会因为共享内存的释放而发生读写错误;
共享内存的内核结构描述中存在对应的变量以引用计数的方式来观察当前有多少个进程挂接了这个共享内存;
-
释放共享内存
当去关联完毕后则可以正常释放共享内存,对应的释放也是执行方进行释放,进程只是告诉了内核该共享内存已经使用完毕可以进行释放;
需求方(进程)无法直接对共享内存进行操作,一切的操作都是由执行方(操作系统内核)进行,需求方(进程)只能通过系统调用接口来间接访问共享内存;
在系统中必定存在不止一个共享内存,当出现多个共享内存时这些资源将被进行管理,管理的方式即为 “先描述,再组织” ;
共享内存结构的描述与组织由操作系统内核进行,当描述后需要进行组织时将会以一个特定的数据结构将多个资源进行管理;
最终对共享内存的管理将会间接的成为对数据结构的管理从而简化操作;
System V共享内存的创建
使用系统调用接口shmget()
可以使进程向操作系统申请共享内存空间;
NAME
shmget - allocates a System V shared memory segment
SYNOPSIS
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
RETURN VALUE
On success, a valid shared memory identifier is returned. On errir, -1 is returned, and errno is set to indicate the error.
该系统调用接口的返回值是一个int
类型的整数值;
当接口调用成功时将会返回一个int
类型的值用来表明该共享内存的唯一值即共享内存标识符,调用失败则会返回-1
并设置errno
;
共享内存标识符与文件描述符具有相似性,其主要作用为对共享内存进行具体的操作;
该系统调用接口的参数分别为:
-
key_t key
用于在内核中标定该共享内存的唯一性;
其中
key_t
类型是对int
类型的一个typedef
;用户可直接自定义对应的
key
,也可使用ftok()
系统调用接口生成对应的key
以保证减少key
值的冲突概率;-
ftok()
系统调用接口该接口用于为
shmget()
系统调用接口生成一个更加不容易冲突的key
值;NAME ftok - convert a pathname and a project identifier to a System V IPC key SYNOPSIS #include <sys/types.h> #include <sys/ipc.h> key_t ftok(const char *pathname, int proj_id); RETURN VALUE On success, the generated key_t value is returned. On failure -1 is returned, with errno indicating the error as for the stat(2) system call.
该函数调用成功后将会返回一个
key_t
类型的key
值;调用失败则会返回
-1
并设置errno
;其接口参数:
const char *pathname
:该参数为路径,路径在内核中具有唯一性;int proj_id
:该参数由用户自行指定的一个值;该系统调用接口将使用一套算法对
pathname
与proj_id
进行数值计算;
在
shmget()
系统调用接口中key
的数值是多少并不重要,重要的是该值必须能够在内核中具有唯一性,能够让不同的进程进行唯一性标志;第一个进程可以通过
key
创建共享内存,而第一个以后的进程只需要使用同样的key
就可以和第一个进程看到同一个共享内存;对于已经创建好的共享内存,其
key
将在共享内存的描述对象中; -
-
size_t size
该参数用于标定需要申请的共享内存的大小,一般为
4096
的倍数(单位为字节);假设所申请的共享内存空间大小为
4097
,内核将会为共享内存空间实际分配4096*2
大小的空间,但实际上能合法使用的空间只有4097
;这表明共享内存的空间大小是按 页 的大小来进行分配的;
-
int shmflg
用于指明在创建共享内存中的选项以及所创建共享内存空间的权限;
选项使用的方式类似于
open()
系统调用接口的选项参数,以位的方式进行传输,通过按位或|
进行不同选项的组合;其中选项为:
IPC_CREAT to create a new segment. If this flag is not used, then shmget() will find the segment associated with key and check to see if the user has permission to access the segment. IPC_EXCL used with IPC_CREAT to ensure failure if the segment already exists.
-
IPC_CREAT
该选项为创建一个共享内存空间,若是对应
key
的共享内存存在则返回对应的共享内存标识符; -
IPC_CREAT|IPC_EXCL
两个选项的组合使用则为,创建一个共享内存空间,若是对应
key
的共享内存空间存在则出错;两个选项的组合使用确保了所申请的共享内存空间是一个全新的,而不会以外覆盖现有的段;
-
IPC_EXCL
该选项不单独使用,只是配合
IPC_CREAT
选项进行使用;
-
编码准备(下文不赘述):
由于System V
共享内存是用来多个进程间通信,此处创建两个毫不相关的进程即两个.cc
文件(processa.cc
,processb.cc
)即一个.hpp
文件comm.hpp
并且引入[简单日志插件]中的日志插件;
-
comm.hpp
#ifndef __COMM_HPP_ //包含卫士 用于防止头文件重复包含 #define __COMM_HPP_ #include <sys/ipc.h> #include <sys/shm.h> #include <sys/types.h> #include <cstring> #include <iostream> #include "log.hpp" using namespace std; #define PATHNAME "/home/" #define SIZE 4096 const int proj_id = 0x7878; Log log; key_t GetKey() { //调用ftok()系统调用接口生成一个不容易重复的key值 key_t key = ftok(PATHNAME, proj_id); if (key < 0) { log(FATAL, "ftok error : %s\n", strerror(errno)); // 通过日志插件将可能出现的内容打印出来 exit(-1); } log(INFO, "ftok sucess , key@ 0x%x\n", key); return key; } int GetShareMem() { //使用key值地调用shmget()系统调用接口创建一个共享内存 并返回对应的shmid key_t k = GetKey(); int shmid = shmget(k, SIZE, IPC_CREAT | IPC_EXCL | 0666); if (shmid < 0) { log(FATAL, "Creat shared memory fail : %s\n", strerror(errno)); exit(2); } log(INFO, "Creat shared memory sucess , shmid@ %d\n", shmid); return shmid; } #endif
封装两个函数分别用于获取
key
值与创建共享内存;在
GetShareMem()
函数中使用IPC_CREAT|IPC_EXCL|0666
的选项来保证每次创建的共享内存将是全新的同时对应的权限为0666
; -
processa.cc
#include "comm.hpp" int main() { int shmid = GetShareMem(); log(DEBUG, "ProcessA quit....\n"); sleep(10); return 0; }
在
processa.cc
文件中直接调用GetShareMem()
函数用来直接创建共享内存;
编译并运行processa
对应的可执行程序;
$ ./processa
[INFO][2024-07-20 10:10:05] ftok sucess , key@ 0x78010002
[INFO][2024-07-20 10:10:05] Creat shared memory sucess , shmid@ 65536
[DEBUG][2024-07-20 10:10:05] ProcessA quit....
结果为成功创建出共享内存,其对应的key
为 0x78010002
,shmid
为65536
;
key
与shmid
的差别在于:
-
key
key
值在操作系统内核范围内标定唯一性;该值用于多个进程能够访问到同一块共享内存;
-
shmid
shmid
只在进程内表示资源的唯一性;该值为共享内存的对应
id
值,用于对共享内存进行具体操作,例如挂接,释放等操作;
当再次运行processa
时:
$ ./processa
[INFO][2024-07-20 10:20:43] ftok sucess , key@ 0x78010002
[FATAL][2024-07-20 10:20:43] Creat shared memory fail : File exists
出现报错,报错信息为File exists
;
可用ipcs -m
命令查看当前共享内存情况:
$ ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x78010002 65536 _LJP 666 4096 0
进程已经结束但对应的共享内存依旧存在;
故可以得出结论:
-
共享内存的生命周期是随内核的,与进程无关
用户不主动释放共享内存,共享内存将一直存在,除非内核重启或用户释放;
在会话中可以使用
picrm -m (shmid)
选项释放对应共享内存,如picrm -m 65536
;
可将GetShareMem()
接口再次封装使得其通过传入不同的参数能够分别进行创建共享内存与获取共享内存shmid
的操作;
int GetShareMem(int flag) {
key_t k = GetKey();
int shmid = shmget(k, SIZE, flag);
if (shmid < 0) {
log(FATAL, "Creat shared memory fail : %s\n", strerror(errno));
exit(2);
}
log(INFO, "Creat shared memory sucess , shmid@ %d\n", shmid);
return shmid;
}
int CreatShm() { return GetShareMem(IPC_CREAT | IPC_EXCL | 0666); }
int GetShm() { return GetShareMem(IPC_CREAT); }
此处的key
值采用的都是相同的参数,两个进程将通过同一个key
值来看到同一块共享内存;
挂接共享内存
当共享内存被创建后需要将共享内存挂接到进程地址空间中才能被进程所给使用;
挂接共享内存通常使用系统调用接口shmat()
;
NAME
shmat, shmdt - System V shared memory operations
SYNOPSIS
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
RETURN VALUE
On success shmat() returns the address of the attached shared memory segment; on error (void *) -1 is returned, and errno is set to indicate the cause of the error.
当该系统调用接口才调用成功时将会返回一个void*
的值,该返回值值类比于malloc()
函数的返回值,即表示共享内存空间;
调用成功时将返回-1
并设置errno
;
其中参数为:
-
int shmid
该参数为一个或多个进程需要挂接的共享内存的
shmid
(见上文); -
const void*shmaddr
该参数为需要将共享内存挂接在进程地址空间的位置,设置为
nullptr
时将为默认(即操作系统自行决定); -
int shmflg
DESCRIPTION If SHM_RDONLY is specified in shmflg, the segment is attached for reading and the process must have read permission for the seg‐ment.Otherwise the segment is attached for read and write and the process must have read and write permission for the segment.There is no notion of a write-only shared memory segment.
该参数表示如果指定了
SHM_RDONLY
选项且共享内存具有读的权限情况下,该段将被用于只读;默认则可读可写;
默认情况下使用shmat()
系统调用接口时只需要传入shmid
参数即可,其他参数可设置为默认值,即nullptr
与0
;
/* processa.cc */
#include "comm.hpp"
int main() {
int shmid = CreatShm(); // 创建共享内存并获取shmid
sleep(5);
char* shmaddr = (char*)shmat(shmid,nullptr,0); // 挂接共享内存
log(DEBUG,"ProcessA attach shared memory done\n");
sleep(5);
log(DEBUG, "ProcessA quit....\n");
return 0;
}
//---------------------
/* processb.cc */
#include "comm.hpp"
int main() {
int shmid = GetShm();// 获取相同key下已经存在的共享内存的shmid
char* shmaddr = (char*)shmat(shmid, nullptr, 0); // 挂接共享内存
sleep(5);
log(DEBUG, "ProcessB attach shared memory done\n");
sleep(5);
log(DEBUG, "ProcessB quit....\n");
return 0;
}
可复制会话并采用shell
脚本监控程序执行过程中共享内存的情况;
$ while :; do ipcs -m ;sleep 1;done
当分别在另外两个会话中运行processa
与processb
;
# processa 所在会话
$ ./processa
[INFO][2024-07-20 11:19:50] ftok sucess , key@ 0x78010002
[INFO][2024-07-20 11:19:50] Creat shared memory sucess , shmid@ 98304
[DEBUG][2024-07-20 11:19:55] ProcessA attach shared memory done
[DEBUG][2024-07-20 11:20:00] ProcessA quit....
# processb 所在会话
$ ./processb
[INFO][2024-07-20 11:19:52] ftok sucess , key@ 0x78010002
[INFO][2024-07-20 11:19:52] Creat shared memory sucess , shmid@ 98304
[DEBUG][2024-07-20 11:19:57] ProcessB attach shared memory done
[DEBUG][2024-07-20 11:20:02] ProcessB quit....
两个会话中日志所显示的shmid
与key
值相同;
# 监控脚本所在会话
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x78010002 98304 _LJP 666 4096 0
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x78010002 98304 _LJP 666 4096 1
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x78010002 98304 _LJP 666 4096 2
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x78010002 98304 _LJP 666 4096 1
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x78010002 98304 _LJP 666 4096 0
对应的监控脚本会话中数据nattch
为引用计数,挂接数量由0
变成1
,1
变成2
,最后逐渐减回0
,原因为由于sleep()
函数与启动时间的差异,先创建共享内存,两个进程在分别挂接上共享内存,最后因为进程退出挂接被自动取消;
取消挂接共享内存
进程退出时将自动去关联,调用系统调用接口shmdt()
则可以在进程未退出时进行去关联操作;
NAME
shmat, shmdt - System V shared memory operations
SYNOPSIS
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
RETURN VALUE
On success shmdt() returns 0; on error -1 is returned, and errno is set to indicate the cause of the error.
该函数调用成功时将返回0
,调用失败时将返回-1
并设置errno
;
-
const void* shmaddr
该参数为需要去关联的共享内存,其机制与
free()
类似,只需要知道去关联共享内存的起始位置即可;共享内存中的内核数据结构将存储该空间实际的大小,同时该空间为一块连续不断的空间,传入起始位置即可在内核中自动计算终止位置从而去关联;
修改代码,调用shmdt()
:
/* processa.cc */
#include "comm.hpp"
int main() {
int shmid = CreatShm(); // 创建共享内存并获取shmid
sleep(5);
char* shmaddr = (char*)shmat(shmid, nullptr, 0); // 挂接共享内存
log(DEBUG,"ProcessA attach shared memory done\n");
sleep(5);
shmdt(shmaddr);
log(DEBUG, "ProcessA disattach shared memory done\n");
sleep(100);
log(DEBUG, "ProcessA quit....\n");
return 0;
}
//---------------------
/* processb.cc */
#include "comm.hpp"
int main() {
int shmid = GetShm(); // 获取相同key下已经存在的共享内存的shmid
char* shmaddr = (char*)shmat(shmid, nullptr, 0); // 挂接共享内存
sleep(5);
log(DEBUG, "ProcessB attach shared memory done\n");
sleep(10);
shmdt(shmaddr);
log(DEBUG, "ProcessB disattach shared memory done\n");
sleep(100);
log(DEBUG, "ProcessB quit....\n");
return 0;
}
两个.cc
文件中设置sleep(100)
使进程休眠且不退出;
重新编译,使用shell
脚本观察共享内存情况并运行两个程序;
# processa 所在会话
$ ./processa
[INFO][2024-07-20 11:43:06] ftok sucess , key@ 0x78010002
[INFO][2024-07-20 11:43:06] Creat shared memory sucess , shmid@ 131072
[DEBUG][2024-07-20 11:43:11] ProcessA attach shared memory done
[DEBUG][2024-07-20 11:43:16] ProcessA disattach shared memory done
# processb 所在会话
$ ./processb
[INFO][2024-07-20 11:43:08] ftok sucess , key@ 0x78010002
[INFO][2024-07-20 11:43:08] Creat shared memory sucess , shmid@ 131072
[DEBUG][2024-07-20 11:43:13] ProcessB attach shared memory done
[DEBUG][2024-07-20 11:43:23] ProcessB disattach shared memory done
两个会话中key
与shmid
相同且进程未退出;
# 监控脚本所在会话
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x78010002 131072 _LJP 666 4096 0
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x78010002 131072 _LJP 666 4096 1
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x78010002 131072 _LJP 666 4096 2
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x78010002 131072 _LJP 666 4096 1
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x78010002 131072 _LJP 666 4096 0
结果为进程未退出同时两个进程分别挂接共享内存而后分别去关联;
释放共享内存
共享内存的生命周期随内核,用户若是不主动释放,共享内存将持续存在;
在命令行中可使用ipcrm -m
选项释放共享内存;
# 发出命令会话
ipcrm -m 163840
# shell脚本会话
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x78010002 163840 _LJP 666 4096 0
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
通过命令并传入shmid
,共享内存被释放;
在程序中可调用系统调用接口shmctl()
释放共享内存;
NAME
shmctl - System V shared memory control
SYNOPSIS
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
RETURN VALUE
A successful IPC_INFO or SHM_INFO operation returns the index of the high‐
est used entry in the kernel's internal array recording information about
all shared memory segments. (This information can be used with repeated
SHM_STAT operations to obtain information about all shared memory segments
on the system.) A successful SHM_STAT operation returns the identifier of
the shared memory segment whose index was given in shmid. Other opera‐
tions return 0 on success.
On error, -1 is returned, and errno is set appropriately.
调用成功时返回0
,调用失败时返回-1
并设置errno
;
-
int shmid
该参数传入需要释放的共享内存的
shmid
; -
struct shmid_ds *buf
该参数为一个结构体,该结构体包含了共享内存的所有属性,可通过该结构体对共享内存进行获取属性,查看属性,修改属性等操作;
此处为传入了对应共享内存的属性;
The buf argument is a pointer to a shmid_ds structure, defined in <sys/shm.h> as follows: struct shmid_ds { struct ipc_perm shm_perm; /* Ownership and permissions */ size_t shm_segsz; /* Size of segment (bytes) */ time_t shm_atime; /* Last attach time */ time_t shm_dtime; /* Last detach time */ time_t shm_ctime; /* Last change time */ pid_t shm_cpid; /* PID of creator */ pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */ shmatt_t shm_nattch; /* No. of current attaches */ ... }; The ipc_perm structure is defined as follows (the highlighted fields are settable using IPC_SET): struct ipc_perm { key_t __key; /* Key supplied to shmget(2) */ uid_t uid; /* Effective UID of owner */ gid_t gid; /* Effective GID of owner */ uid_t cuid; /* Effective UID of creator */ gid_t cgid; /* Effective GID of creator */ unsigned short mode; /* Permissions + SHM_DEST and SHM_LOCKED flags */ unsigned short __seq; /* Sequence number */ };
在释放共享内存时不需要关注其共享内存属性,可传入
nullptr
; -
int cmd
该参数为传入一个选项使能够对共享内存进行对应操作;
参数如下:
IPC_STAT Copy information from the kernel data structure associated with shmid into the shmid_ds structure pointed to by buf. The caller must have read permis‐ sion on the shared memory segment. IPC_SET Write the values of some members of the shmid_ds structure pointed to by buf to the kernel data structure associated with this shared memory segment, updating also its shm_ctime member. The following fields can be changed: shm_perm.uid, shm_perm.gid, and (the least significant 9 bits of) shm_perm.mode. The effective UID of the calling process must match the owner (shm_perm.uid) or creator (shm_perm.cuid) of the shared memory seg‐ ment, or the caller must be privileged. IPC_RMID Mark the segment to be destroyed. The segment will only actually be destroyed after the last process detaches it (i.e., when the shm_nattch mem‐ ber of the associated structure shmid_ds is zero). The caller must be the owner or creator, or be privileged. If a segment has been marked for destruction, then the (nonstandard) SHM_DEST flag of the shm_perm.mode field in the associated data structure retrieved by IPC_STAT will be set. The caller must ensure that a segment is eventually destroyed; otherwise its pages that were faulted in will remain in memory or swap. See also the description of /proc/sys/kernel/shm_rmid_forced in proc(5). ...... # 具体参考 man shmctl 手册
其中释放共享内存的选项为
IPC_RMID
选项;
/* processa.cc */
#include "comm.hpp"
int main() {
int shmid = CreatShm(); // 创建共享内存并获取shmid
sleep(2);
char* shmaddr = (char*)shmat(shmid, nullptr, 0); // 挂接共享内存
log(DEBUG,"ProcessA attach shared memory done\n");
sleep(2);
shmdt(shmaddr);
log(DEBUG, "ProcessA disattach shared memory done\n");
sleep(2);
int n = shmctl(shmid,IPC_RMID,nullptr);
if(n<0){
log(FATAL,"ProcessA shmctl faile : %s\n",strerror(errno));
exit(3);
}
log(DEBUG,"ProcessA free the shared memory sucess\n");
sleep(2);
log(DEBUG, "ProcessA quit....\n");
return 0;
}
修改代码,在processa.cc
中添加shmctl()
系统调用接口并重新编译,打开shell
脚本进行观察并运行processa.cc
;
# processa所在会话
$ ./processa
[INFO][2024-07-20 12:28:19] ftok sucess , key@ 0x78010002
[INFO][2024-07-20 12:28:19] Creat shared memory sucess , shmid@ 196608
[DEBUG][2024-07-20 12:28:21] ProcessA attach shared memory done
[DEBUG][2024-07-20 12:28:23] ProcessA disattach shared memory done
[DEBUG][2024-07-20 12:28:25] ProcessA free the shared memory sucess
[DEBUG][2024-07-20 12:28:27] ProcessA quit....
# shell脚本所在会话
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x78010002 196608 _LJP 666 4096 0
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x78010002 196608 _LJP 666 4096 1
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x78010002 196608 _LJP 666 4096 0
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
结果为一系列流程即 创建共享内存,挂接共享内存,去关联,释放共享内存 ;
利用System V共享内存进行进程间通信
此处利用两个进程,即processa
与processb
;
思路为processa
创建共享内存并挂接共享内存,processb
获取共享内存shmid
进行挂接,从而建立通信信道,即 “使不同进程看到同一份资源” ;
在使用共享内存时可直接以内存的形式进行访问而不需要其他操作;
-
processa.cc
#include "comm.hpp" int main() { // cout << "hello world processa" << endl; int shmid = CreatShm(); // 创建共享内存并获取shmid // sleep(2); char* shmaddr = (char*)shmat(shmid, nullptr, 0); // 挂接共享内存 log(DEBUG,"ProcessA attach shared memory done\n"); // sleep(2); //ipc code while(true){ cout<<"processa get massage@ "<<shmaddr<<endl; sleep(2); } shmdt(shmaddr); log(DEBUG, "ProcessA disattach shared memory done\n"); // sleep(2); int n = shmctl(shmid,IPC_RMID,nullptr); if(n<0){ log(FATAL,"ProcessA shmctl faile : %s\n",strerror(errno)); exit(3); } log(DEBUG,"ProcessA free the shared memory sucess\n"); // sleep(2); log(DEBUG, "ProcessA quit....\n"); return 0; }
每隔
2s
从共享内存中读取数据并打印; -
processb.cc
#include "comm.hpp" int main() { // cout << "hello world processb" << endl; int shmid = GetShm(); // 获取相同key下已经存在的共享内存的shmid char* shmaddr = (char*)shmat(shmid, nullptr, 0); // 挂接共享内存 // sleep(5); log(DEBUG, "ProcessB attach shared memory done\n"); // sleep(10); //ipc code while(true){ cout<<"Please enter$ "; gets(shmaddr); } shmdt(shmaddr); log(DEBUG, "ProcessB disattach shared memory done\n"); // sleep(100); log(DEBUG, "ProcessB quit....\n"); return 0; }
调用
gets()
直接向共享内存中写入;当共享内存挂接至进程地址空间后可直接以内存的方式使用共享内存而不需要调用系统调用接口;
编译后分别运行两个程序;
# processa所在会话
$ ./processa
[INFO][2024-07-20 12:43:17] ftok sucess , key@ 0x78010002
[INFO][2024-07-20 12:43:17] Creat shared memory sucess , shmid@ 229376
[DEBUG][2024-07-20 12:43:17] ProcessA attach shared memory done
processa get massage@
processa get massage@
processa get massage@ hello
processa get massage@ hello
processa get massage@ hello
processa get massage@ hello world
processa get massage@ hello world
processa get massage@ hello world
# processb所在会话
$ ./processb
[INFO][2024-07-20 12:43:24] ftok sucess , key@ 0x78010002
[INFO][2024-07-20 12:43:24] Creat shared memory sucess , shmid@ 229376
[DEBUG][2024-07-20 12:43:24] ProcessB attach shared memory done
Please enter$ hello
Please enter$ hello world
Please enter$ ^C
# 该程序在 Ctrl C结束后并不会释放共享内存 需要在命令行中手动释放
结果显示无论有没有向共享内存中写入数据,processa
都会正常写入;
证明共享内存不存在同步互斥的保护机制;
共享内存的特性
-
共享内存没有同步互斥之类的保护机制
上文使用共享内存进行进程间通信过程中读写端不会因为对端因正在被读或是正在被写而进行阻塞等待;
在使用共享内存若是需要添加同步互斥类的保护机制应该额外增加并管理(如管道);
-
共享内存是所有进程间通信中速度最快的
共享内存在进程间通信的速度是最快的,原因是它不需要额外的拷贝造成的开销;
以管道为例:
管道需要将从写端写入用户自定义的缓冲区中,再将用户自定义缓冲区中的数据拷贝至内核缓冲区中;
读端才能从内核缓冲区中(管道文件)获取写端写入管道的文件,这几个步骤涉及了多次拷贝;
-
共享内存内部数据由用户自行维护
操作系统内核只为用户分配并维护共享内存这块空间,而空间内的数据是需要
共享内存的属性
一般情况下在操作系统内核中存在一个 struct shmid_ds
类型的结构体;
该结构体存储着共享内存的基本属性;
struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions */
size_t shm_segsz; /* Size of segment (bytes) */
time_t shm_atime; /* Last attach time */
time_t shm_dtime; /* Last detach time */
time_t shm_ctime; /* Last change time */
pid_t shm_cpid; /* PID of creator */
pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */
shmatt_t shm_nattch; /* No. of current attaches */
...
};
-
struct ipc_perm shm_perm
该参数为该结构体中嵌套了的一个结构体变量;
其中该参数用来存储共享内存的所有权和权限等信息;
struct ipc_perm { key_t __key; /* Key supplied to shmget(2) */ uid_t uid; /* Effective UID of owner */ gid_t gid; /* Effective GID of owner */ uid_t cuid; /* Effective UID of creator */ gid_t cgid; /* Effective GID of creator */ unsigned short mode; /* Permissions + SHM_DEST and SHM_LOCKED flags */ unsigned short __seq; /* Sequence number */ };
其中
key_t __key
即为该共享内存的key
;其他不同进程可通过该
key
来找到该共享内存; -
size_t shm_segsz
共享内存的大小(单位字节);
-
time_t shm_atime
共享内存上一次的连接时间;
-
time_t shm_dtime
共享内存的上一次的去关联时间;
-
time_t shm_ctime
共享内存上一次的修改时间;
-
pid_t shm_cpid
申请创建该共享内存(第一个)进程的
PID
; -
…
可通过调用系统调用接口shmctl()
并传递IPC_STAT
选项来查看该共享内存的对应信息,即struct shmid_ds
中的数据;
/* processa.cc */
#include "comm.hpp"
int main() {
log(DEBUG, "The PID of the processa:%d\n", getpid());
// 调用日志log打印当前进程的PID
int shmid = CreatShm();
char* shmaddr = (char*)shmat(shmid, nullptr, 0); // 挂接共享内存
log(DEBUG, "ProcessA attach shared memory done\n");
while (true) {
cout << "processa get massage@ " << shmaddr << endl;
// 查看共享内存信息代码
struct shmid_ds shmds;
shmctl(shmid, IPC_STAT, &shmds);
printf("The shm key :0x%x\n", shmds.shm_perm.__key);
cout << "The shm size :" << shmds.shm_segsz << endl;
cout << "PID of creator :" << shmds.shm_cpid << endl;
cout << "===========================" << endl;
sleep(2);
}
// 退出清理
shmdt(shmaddr);
log(DEBUG, "ProcessA disattach shared memory done\n");
int n = shmctl(shmid, IPC_RMID, nullptr);
if (n < 0) {
log(FATAL, "ProcessA shmctl faile : %s\n", strerror(errno));
exit(3);
}
log(DEBUG, "ProcessA free the shared memory sucess\n");
log(DEBUG, "ProcessA quit....\n");
return 0;
}
运行结果为:
$ ./processa
[DEBUG][2024-07-20 14:56:07] The PID of the processa:9923
[INFO][2024-07-20 14:56:07] ftok sucess , key@ 0x78010002
[INFO][2024-07-20 14:56:07] Creat shared memory sucess , shmid@ 327680
[DEBUG][2024-07-20 14:56:07] ProcessA attach shared memory done
processa get massage@
The shm key :0x78010002
The shm size :4096
PID of creator :9923
===========================
通过命名管道为共享内存添加同步互斥机制
命名管道具有同步互斥的保护机制;
该思路为使两个进程创建共享内存用于传输数量较大的数据,同时为其创建命名管道用于传输简单消息提醒对端接收数据;
-
comm.hpp
#ifndef __COMM_HPP_ #define __COMM_HPP_ #include <sys/ipc.h> #include <sys/shm.h> #include <sys/types.h> #include <cstring> #include <iostream> #include "log.hpp" using namespace std; #define PATHNAME "/home/" #define SIZE 4096 const int proj_id = 0x7878; Log log; #include <fcntl.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> #include <cstdlib> #include <iostream> #include <string> #define PIPEFILE "./myfifo" #define MODE 0666 #define SIZE 1024 using namespace std; enum { FIFO_CREATE_ERR = 1, FIFO_DELETE_ERR, FIFO_OPEN_ERR }; class PipeInit { public: PipeInit() { int n = mkfifo(PIPEFILE, MODE); if (n == -1) { cerr << "CREATE PIPE FAIL" << endl; exit(FIFO_CREATE_ERR); } } ~PipeInit() { int m = unlink(PIPEFILE); if (m == -1) { cerr << "DEL PIPE FAIL" << endl; exit(FIFO_DELETE_ERR); } } }; key_t GetKey() { key_t key = ftok(PATHNAME, proj_id); if (key < 0) { log(FATAL, "ftok error : %s\n", strerror(errno)); exit(-1); } log(INFO, "ftok sucess , key@ 0x%x\n", key); return key; } int GetShareMem(int flag) { key_t k = GetKey(); int shmid = shmget(k, SIZE, flag); if (shmid < 0) { log(FATAL, "Creat shared memory fail : %s\n", strerror(errno)); exit(2); } log(INFO, "Creat shared memory sucess , shmid@ %d\n", shmid); return shmid; } int CreatShm() { return GetShareMem(IPC_CREAT | IPC_EXCL | 0666); } int GetShm() { return GetShareMem(IPC_CREAT); } #endif
封装一个类用于管道的创建以及销毁;
-
processa.cc
#include "comm.hpp" int main() { PipeInit pi; log(DEBUG, "The PID of the processa:%d\n", getpid()); int shmid = CreatShm(); char* shmaddr = (char*)shmat(shmid, nullptr, 0); log(DEBUG, "ProcessA attach shared memory done\n"); // ipc code 通信代码 int fd = open(PIPEFILE, O_RDONLY); if (fd < 0) { log(FATAL, "open pipefile error :%s\n", strerror(errno)); exit(FIFO_OPEN_ERR); } log(INFO, "open pipefile sucess\n"); while (true) { char c; read(fd, &c, sizeof(c)); if (c == 'c') { cout << "processa get massage@ " << shmaddr << endl; } } shmdt(shmaddr); log(DEBUG, "ProcessA disattach shared memory done\n"); int n = shmctl(shmid, IPC_RMID, nullptr); if (n < 0) { log(FATAL, "ProcessA shmctl faile : %s\n", strerror(errno)); exit(3); } log(DEBUG, "ProcessA free the shared memory sucess\n"); log(DEBUG, "ProcessA quit....\n"); return 0; }
processa
作为读端,每当从管道读取到字符'c'
时从共享内存中读取数据并进行打印; -
processb.cc
#include "comm.hpp" int main() { int shmid = GetShm(); char* shmaddr = (char*)shmat(shmid, nullptr, 0); log(DEBUG, "ProcessB attach shared memory done\n"); // ipc code int fd = open(PIPEFILE, O_WRONLY); if (fd < 0) { log(FATAL, "open pipefile error :%s\n", strerror(errno)); exit(FIFO_OPEN_ERR); } log(INFO, "open pipefile sucess\n"); while (true) { cout << "Please enter$ "; gets(shmaddr); write(fd, "c", 1); } shmdt(shmaddr); log(DEBUG, "ProcessB disattach shared memory done\n"); log(DEBUG, "ProcessB quit....\n"); return 0; }
processb
作写端,每当从写端写入数据时向管道写入一个字符"c"
;
利用管道的同步互斥机制优化System V
共享内存;
完整代码(供参考):
[参考代码(gitee) - DIo夹心小面包 (半介莽夫)]