1. 概念介绍
System V IPC(Inter-Process Communication)是一组在UNIX系统中用于进程间通信的机制,包括共享内存、消息队列和信号量。这些机制由System V内核提供,并且它们的存在不依赖于创建它们的进程,而是由内核管理,直到显式删除。由于消息队列和信号量并不常用,故而以下主要介绍共享内存。
2. 共享内存 shm
共享内存允许不同进程共享一段由操作系统亲自开辟的物理内存,进程可以通过读写这段内存来交换数据。共享内存是最快的IPC方式,因为它减少了复制数据的需要,如通过管道交换数据时需要经过从内存复制到缓冲区中。
基本原理如上图,共享内存会被进程的页表直接映射到自己的进程地址空间的共享区,从而通过进程地址空间直接对内存进行操作,实现资源的共享与交换。
共享内存函数
shmget函数
参数:
- key: 一个整数键值,用于唯一标识共享内存段,以创建或获取
我们可以看到 key 的类型为 key_t,实际上也是一个整数,不过要保证其数值的唯一性,要获取该参数 key 我们需要使用 ftok 函数,它会根据一个文件路径和一个项目标识符(proj_id
)通过一系列算法生成一个几乎唯一的键值。
pathname
是一个指向存在的文件路径的指针,实际上可以随便写。proj_id
是一个整数,通常是一个字符常量,其低8位被用于生成键值,实际上可以随便写。
key 是System V的唯一标识符,注意不是shm的,shmid 是 shm 的唯一标识符。
- size: 共享内存大小,以字节为单位
注意:共享内存以4 kb
为基本单位开辟内存,也就是4096 byte
,哪怕只申请了1 byte
的内存,实际上还是会开辟4096 byte
大小的空间。因此开辟shm
的时候,这个参数最好设置为4096
的倍数。
- shmflg: 由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的。
其中最为常用的shmflg如下:
IPC_CREAT
:如果共享内存段不存在,则创建一个新的共享内存段,存在则返回 shmid。IPC_EXCL
:与IPC_CREAT
一起使用时,确保创建新的共享内存段,如果共享内存段已存在则失败。- 权限位:
shmflg
的低9位用于设置共享内存段的权限,与文件系统的权限位相同。
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
int main()
{
key_t key=ftok("/home/lbk/lesson17/test.cpp",8888);
int shmid=shmget(key,4096,IPC_CREAT|IPC_EXCL|0666);
return 0;
}
第一个参数 key:我们通过ftok获得该唯一的共享内存的system V标识符key;
第二个参数为4096:即开辟的共享内存大小为4096 byte;
第三个参数为IPC_CREAT | IPC_EXCL | 0666:如果当前的key不存在,则创建对应的共享内存,该共享内存的初始权限为0666,如果存在,则创建失败。
通过 ipcs
指令可以看当前所有的system V
的总体情况:
也可以通过 ipcs -m命令只看共享内存的情况:
前面我们已经提到共享内存的存在不依赖于创建它们的进程,而是由内核管理,直到显式删除。即进程已经结束了,但是进程创建的共享内存还存在,如果想要删除一个共享内存,我们可以通过ipcrm -m shmid 命令。
shmat函数
共享内存是被直接映射到进程地址空间的共享区的,进程可以通过访问进程地址空间来访问共享内存,而从内存映射到进程地址空间我们就需要shmat函数。
- shmid: 由 shmget 函数返回的共享内存段的标识符。
- shmaddr: 一个指向进程地址空间中的位置的指针,用于指定共享内存段附加的起始地址。通常,这个参数设置为 NULL,让系统自动选择一个合适的地址。
- shmflg: 一个标志位,可以包含 SHM_RDONLY 来指定以只读方式附加共享内存,或者 0 以允许读写。
shmdt函数
当一个进程不再需要访问共享内存时,可以调用 shmdt
来解除共享内存与该进程地址空间的关联。这个函数不会删除共享内存,只是使其对当前进程不可用。
shmctl函数
- shmid: 由shmget返回的共享内存标识码
- cmd: 命令参数,指定了要对共享内存段执行的操作(有三个可取值)
-
IPC_STAT
:获取共享内存段的状态信息
-IPC_SET
:设置共享内存段的某些属性
-IPC_RMID
:删除共享内存段
- buf: 一个指向
shmid_ds
结构的指针,用于存储或接收共享内存段的信息。如果 cmd 是IPC_STAT 或 IPC_SET, 则buf
不为NULL
;如果cmd
是IPC_RMID
,则buf
可以为NULL
。
shmid_ds
结构体是与System V共享内存段相关联的数据结构,它包含了共享内存段的各种状态信息和属性。这个结构体在 <sys/shm.h>
头文件中定义,并且在内核中用于维护每个共享内存段的信息。
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 */
...
};
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <iostream>
using namespace std;
int main()
{
key_t key = ftok("/home/lbk/lesson17/test.cpp", 8888);
int shmid = shmget(key, 4096, IPC_CREAT | IPC_EXCL | 0666);
if (shmid == -1)
{
perror("shmget");
exit(-1);
}
struct shmid_ds shm;
shmctl(shmid, IPC_STAT, &shm);
cout << "shm_nattch:" << shm.shm_nattch << endl;
cout << "shm_segsz:" << shm.shm_segsz << endl;
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
进程间通信示例
comm.hpp
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <iostream>
using namespace std;
#define PATH_NAME "/home/lbk/lesson17/comm.hpp"
#define PROJECT_ID 0x8888
#define SHM_SIZE 4096
key_t Getkey()
{
return ftok(PATH_NAME, PROJECT_ID);
}
int Creatshm()
{
int shmid = shmget(Getkey(), SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666);
if (shmid == -1)
{
cout << "shmget error" << endl;
return -1;
}
return shmid;
}
int Getshm()
{
int shmid = shmget(Getkey(), SHM_SIZE, IPC_CREAT);
if (shmid == -1)
{
cout << "shmget error" << endl;
return -1;
}
return shmid;
}
server.cpp
#include "comm.hpp"
int main()
{
int shmid = Creatshm();
char *buf = (char *)shmat(shmid, NULL, 0);
cout << "Client start" << endl;
while (true)
{
cout << "Please Enter@";
cin >> buf;
}
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
client.cpp
#include "comm.hpp"
int main()
{
int shmid = Getshm();
char *buf = (char *)shmat(shmid, NULL, 0);
while (true)
{
cout << "client receive:" << buf << endl;
sleep(1);
}
return 0;
}
makefile
.PHONY:all
all:client.exe server.exe
client.exe:client.cpp
g++ -o $@ $^
server.exe:server.cpp
g++ -o $@ $^
.PHONY:clean
clean:
rm -f client.exe server.exe
特点
-
高效性:共享内存允许直接在内存中交换数据,避免了数据拷贝的开销,因此在进程间通信中速度非常快。
-
同步问题:由于多个进程可以同时访问共享内存,因此需要同步机制来避免数据竞争和一致性问题。通常,进程会使用信号量或互斥锁来同步对共享内存的访问。
-
系统资源:共享内存不需要操作系统维护额外的通信队列或管道,它直接使用进程的地址空间。
在使用共享内存时,需要注意的是,共享内存的内容在系统重启后不会持久化,因为它存储在物理内存中,而不是磁盘上。此外,共享内存的管理通常涉及一系列系统调用,如 shmget
创建共享内存段,shmat
将共享内存段附加到进程的地址空间,shmdt
分离共享内存段,以及 shmctl
控制共享内存段的属性和状态.