目录
使用system V共享内存进行进程间通信:
获取共享内存shmget
将共享内存关联到进程
去关联共享内存
删除共享内存
简易模拟实现server和client之间的通信:
服务端代码:
客户端代码:
共享内存的特点:
其他进程间通信的方式
IPC之间的联系
上篇提到进程间通信的方式管道,system V共享内存也是一种进程间通信的方式。
使用获取共享内存函数第一个参数key是标识共享内存的一个唯一数字,相当于两个进程之间约定的同一个文件进行通信。它标识了两个进程,将使用的同一块共享内存进行通信,即可以通过ftok函数通过传入的参数获取唯一的数值,但是这个函数不保证生成的数值唯一。
ftok函数的第一个参数是路径,第二个参数是一个整数,但是函数内部实现只会使用低8bit位的数生成key,创建失败时返回-1。
第二个参数是size表示要创建共享内存的大小,一般设置为4K的整数倍,也就是4096byte的整数倍,这是因为物理内存是以页为单位的,每页就是4KB的大小,假设传入4097字节,OS会分配8KB,但是用户只能使用4097字节的大小。
第三个参数是创建共享内存的方式,是使用宏作为参数。
可用宏有:
IPC_CREAT表示不存在共享内存就创建之,存在就返回之。
IPC_EXCL,这个宏必须搭配上面的宏一起使用,表示如果存在就错误返回,如果不存在就创建共享内存,这两个宏搭配使用,可以保证创建的共享内存是新的。
函数的返回值是共享内存的id即shmid。
使用共享内存之前,需要用我们的关联共享内存函数去关联我们的共享内存,使用的时候像使用从堆申请的内存一样去使用即可,使用完之后,需要调用系统调用去关联,最后调用系统调用或使用命令行去删除共享内存。本质上是共享内存和进程产生关联,也就是页表建立了进程地址空间共享区和物理内存的映射。
使用shmat函数:
第一个参数shmid是共享内存id,是shmget函数的返回值。
第二个参数shmaddr是指定关联到进程地址空间的某一个地址,常用NULL、nullptr表示先关联到系统可以获得到的地址。
第三个参数shmflg是设置关联共享内存的属性,可传入0表示读写权限,传入SHM_RDONLY表示只读权限。
当关联失败时会返回(void*) -1,比如shmget函数第三个参数没有带入文件权限,默认文件权限就是0,关联共享内存时就会失败。
使用shmdt去掉之前的关联
创建的共享内存是在系统内核中维护的,所以它的生命周期是属于内核的,如果不通过命令行或者系统调用的方式显式删除掉共享内存,只就只能通过重启操作系统来删除共享内存的,所以共享内存的生命周期是随内核的。
可以使用shmctl函数,第一个参数是shmid,传入第二个参数是IPC_RMID即可。
或者在命令行使用ipcs -m查看共享内存,使用ipcsrm -m 共享内存id删除共享内存:
server服务端进行如下步骤:
1.创建key,并获取shm,返回shmid
2.关联共享内存
3.获取共享内存内容,直接打印
4.去关联共享内存
5.删除共享内存
#include "util.hpp"
int main()
{
// 1.创建key,并获取shm,返回shmid
key_t key = CreateKey();
int shmid = shmget(key, MEM_SIZE, IPC_CREAT | IPC_EXCL | 0666);
if (shmid < 0)
{
cerr << "shmget:" << strerror(errno) << endl;
return 2;
}
// 2.关联共享内存
char *shm = (char *)shmat(shmid, nullptr, 0);
cout << "attach success" << endl;
if (shm == (void *)-1)
{
cerr << "shmat error" << endl;
return 3;
}
// 3.使用共享内存
while (1)
{
printf("%s", shm);
if (strncasecmp(shm, "QUIT", 4) == 0) // 用户控制退出,忽略大小写,需要使用strncasecmp
{
cout << "server quit!" << endl;
break;
}
sleep(5);
}
// 4.去关联共享内存
shmdt(shm);
cout << "detach shm success!" << endl;
// 5.删除共享内存
shmctl(shmid, IPC_RMID, nullptr);
cout << "remove shm success!" << endl;
return 0;
}
客户端的步骤与服务端基本相同,先创建key值,然后获取共享内存,然后挂接共享内存,最后是使用共享内存,不需要进行删除共享内存,因为服务端会完成此工作。
#include "util.hpp"
int main()
{
// 1.创建key,并获取shm,返回shmid
key_t key = CreateKey();
int shmid = shmget(key, MEM_SIZE, IPC_CREAT | 0666);
if (shmid < 0)
{
cerr << "shmget:" << strerror(errno) << endl;
return 2;
}
// 2.关联共享内存
char *shm = (char *)shmat(shmid, nullptr, 0);
if (shm == (void *)-1)
{
cerr << "shmat error" << endl;
return 3;
}
// 3.使用共享内存
while (1)
{
printf("Please Enter#");
fflush(stdout);
ssize_t s = read(0, shm, MEM_SIZE); // 从键盘信息写入共享内存
if (s > 0)
shm[s - 1] = '\0';
char *str = shm;
if (strncasecmp(str, "QUIT", 4) == 0) // 用户控制退出,忽略大小写,需要使用strncasecmp
{
cout << "client quit!" << endl;
break;
}
}
// 4.去关联共享内存
shmdt(shm);
cout << "detach shm success!" << endl;
return 0;
}
公共头文件代码:
#include
#include
#include
#include
#include
#include
using std::cerr;
using std::endl;
using std::cout;
#define IPC_PATH "/home/ys/os-learning-code/oscall/IPC"
#define PROJ_ID 0x66
#define MEM_SIZE 4096
key_t CreateKey()
{
key_t key = ftok(IPC_PATH, PROJ_ID);
if (key == -1)
{
cerr << "ftok create key error" << endl;
exit(1);
}
return key;
}
测试结果:
共享内存的特点如下,首先它没有访问控制,所以它是所有进程间通信方式中,通信速度最快的,其次共享内存没有访问控制,共享内存是进程之间访问看到同一份的资源,即临界资源,临界资源没有受到访问控制,即没有互斥和同步机制,会导致访问临界资源出现问题。
还有消息队列,信号量等等。
信号量的本质是一个计数器,是一种预定的机制,类似于买电影票,预定的座位一样,只要拿到了票,就相当于获得了座位,就相当于获得了访问临界资源的权利,所以申请信号量的操作就相当于申请临界资源,对计数器的操作必须是原子性的才能保证,信号量和消息队列的生命周期也都是随内核的。
内核在底层实现各种进程间通信的内核结构中,每种内核结构的第一个字段都是ipc_perm。操作系统用指针指向的内核结构,当访问不同类型的IPC资源时进行强制类型转换,即可访问到不同类型的ipc资源,这样子去定义结构,有利于OS以统一的规则管理IPC资源。
ipc_perm结构体的字段中包含key等:
使用ipcs -m/-q/-s/-a命令查看共享内存,消息队列,信号量,或所有IPC资源。