Linux
- 1、引言
- 2、实现共享内存
- 2.1、创建一个共享内存
- 2.2、将共享内存链接到进程空间
- 2.3、断开与共享内存的链接
- 2.4、对共享内存进行后续操作
- 3、应用实例
1、引言
在之前一篇文章Linux学习笔记之六(进程之间的管道通信和信号处理)中我讲了进程间可以通过管道通信。管道通信也是在内存中某个地方开辟一块内存,让不同进程可以访问它,进而实现通信的目的。
而本文所讲的共享内存也大概是这么一个逻辑,不过相较与管道通信,内存共享实现起来更加复杂,也没有读写阻塞功能,但执行起来的效率也更加高,似乎是最快的进程通信方式。
2、实现共享内存
共享内存其任务在于从物理内存中申请一块内存区域,然后将这个区域映射到不同进程的空间中。进而进程访问该共享区域,进行读写操作。下图截取于B站,该图比较清晰明了说明了共享内存的运行逻辑。从这张图可以看出,任何进程都有自己的空间,而这些空间本质是取之于物理内存,但取的顺序则是杂乱的。共享内存无非是一块大家都可以取走的内存块而已。
接下来说明实现共享内存的基本步骤:
第一步:创建一个共享内存
第二步:将共享内存链接到进程空间
第三步:断开与共享内存的链接
第四步:对共享内存进行后续操作(如销毁)
2.1、创建一个共享内存
创建一个共享内存用到的函数是shmget(share memory get),其函数原型如下:
int shmget(key_t key, size_t size, int shmflag);
参数概览:
key:设置该共享内容的关键字标识。
size:指定共享内存的空间大小。
shmflag:设置共享内存的访问权限。
返回值:创建成功则返回共享内存标识符,失败则返回-1.
参数详解:
1. key参数:key 参数是一个用于标识共享内存段的关键字。它允许多个进程通过使用相同的 key 参数来访问同一个共享内存段。这就是说,如果多个进程想要访问同一个共享内存段,它们必须提供相同的 key 参数值。
通常情况下,key 参数可以是一个由 ftok() 函数生成的键值(通过给定文件路径名和一个项目标识符来生成唯一的键值),也可以是手动指定的一个键值,也可以通过一些宏指定一些key值。
关于ftok函数,其函数原型如下:
key_t ftok(const char *pathname, int proj_id);
第一个参数是真实存在的文件路径,第二个参数是项目的id,可以自己设定。
计算机会根据这两个参数的组合,生成一个独一无二的key值,保证不同进程可以访问同一个内存空间。
2. size参数:一般而言我们会设置成4096的整数倍,因为计算机的最小内存单元就是4kB,但如果不设置成4096的整数倍也无所谓,计算机会根据自动帮你调整到合适的值。比如你设置2009,计算机也会帮你调整到4096.
2. shmflag参数:该参数用于设置共享内存的访问权限,一般由一些宏和四位整数组成,比如0777 | IPC_CREAT。权限的设置相信大家都耳熟能详了,第一位0表示后面的三位数字是八进制数字,777分别表示,用户、用户组、其他人的权限。7会被拆解位111,分别表示rwx(读写执行)三个权限。
常见的宏有:
IPC_CREAT:若共享内存存在则打开,否则创建一个。
IPC_EXCL:若共享内存不存在则创建,否则报错。
IPC_NOWAIT:若该操作需要等待,直接报错。
最后是调用该函数需要头文件(后文提到的所有函数也需要包含这几个头文件):
#include "sys/types.h"
#include "sys/ipc.h"
#include "sys/shm.h"
2.2、将共享内存链接到进程空间
在这一步用到函数是shmat,其函数原型如下:
void *shmat(int shmid, const void *addr, int shmflg);
参数概览:
shmid:这里输入的是共享内存的标识符,其实就是shmget函数的返回值。
void *addr:指定链接到进程中的哪个地址块,一般不需要指定,由计算机自动分配即可。
int shmflg:标志位,一般设置为0即可。
返回值:成功则返回共享内存在进程空间中的链接地址,失败则返回-1。
注:void *指的是无类型指针,无类型换一种理解方式就是,可以赋予它任意类型。
通过这个步骤,我们拿到了链接地址,再用这个地址来索引相应的内存空间之后,便可以进行读写操作,以实现进程通信。
2.3、断开与共享内存的链接
进程通信结束之后自然是要将进程与共享内存之间的链接断开。这一步用到的函数时shmdt,其函数原型如下:
int shmdt(const void *address);
void *address:该地址指的进程空间中的链接地址。一般就是shmat中的返回值了。
返回值:成功则返回0,失败则返回-1。
2.4、对共享内存进行后续操作
进程通信结束,链接也断开,下一步自然是要考虑如何回收或销毁这个共享内存了。用到的函数时shmctl,其函数原型如下:
int shmctl(int shm_id, int command, struct shmid_ds *buf);
shm_id:共享内存对象的id,即内存的标识符。一般时shmget的返回值。
command:指定对该内存的操作命令,主要有删除命令(IPC_RMID)。
shmid_ds *buf:该参数用于获取内核结构的结构值,一般也不用。故直接设置为0即可。
最后注意一点,如果使用调用shmctl(shmid, IPC_RMID, 0)这句话,最后删除的只是共享内存的标识符,使系统不再维护该内存标识符,而不是真正把共享内存释放掉。如果想要共享内存被释放掉,则需要等到该内存没有被任何进程调用时,系统才会自动被释放掉。
3、应用实例
本例子的目的:创建两个进程A和B,A进程往共享内存写入数据,B进程将数据读出来。
故,首先创建一个进程A,去往一块共享内存写入数据。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sys/types.h"
#include "sys/ipc.h"
#include "sys/shm.h"int main()
{key_t key = ftok("$HOME/shared_memory/program_A.c", 9); //generate a unique keyint shmid = shmget(key, 4096, 0751|IPC_CREAT); //create a shared memorychar *addr = shmat(shmid, 0, 0); //link the shared memory with this program char buffer[100]; //create a buffer for writing datawhile(1){printf("\nPlease input a character: ");fgets(buffer, sizeof(buffer), stdin);if(strcmp(buffer, "q\n") == 0) //judge whether what I input is 'q' or notbreak; strncpy(addr, buffer, sizeof(buffer)); //writing data into the shared memory}if(shmdt(addr) == 0){printf("disconnect shared memory successfully!\n");}else{printf("fail to disconnect!\n");}if(shmctl(shmid, IPC_RMID, 0) == 0){printf("delete the identifer successfully!\n");}else{printf("the identifer already be deleted!\n");}return 0;
}
其次,创建一个进程B,从共享内存中读取数据。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sys/types.h"
#include "sys/ipc.h"
#include "sys/shm.h"int main()
{key_t key = ftok("$HOME/shared_memory/program_A.c", 9); int shmid = shmget(key, 4096, 0751|IPC_CREAT); char *addr = shmat(shmid, 0, 0); while(1){char input;printf("please input a character: ");scanf("%c", &input);if(input == 'q')break;int c;while ((c = getchar()) != '\n' && c != EOF) {}printf("what i receive is: %s\n", addr);}if(shmdt(addr) == 0){printf("disconnect successfully!\n");}else{printf("fail to disconnect!\n");}if(shmctl(shmid, IPC_RMID, 0) == 0){printf("delete successfully!\n");}else{printf("the identifer already be deleted!\n");}return 0;
}
最后,我打开两个终端,同时运行进程A和B,运行结果如下: