前言:大家好😍,本文主要介绍了Linux——共享内存
目录
一、概念
二、共享内存的实现步骤
2.1 shmget 创建或获取共享内存段
2.2 shmat 访问共享内存
2.3 strcpy 对共享内存进行读写操作
2.4 shmdt 解除共享内存的映射
2.5 shnctl 删除共享内存段
三、共享内存
3.1创建一个进程 拿到数据test.c
3.2 加入循环test.c
3.3 简单的共享内存写入程序
3.4 带有信号量的共享内存
一、概念
共享内存是一种高效的进程间通信(IPC, Inter - Process Communication)机制。多个进程可以将同一块物理内存区域映射到各自的进程地址空间中,这样这些进程就可以直接对这块共享的内存区域进行读写操作,从而实现数据的共享和交换。由于不需要进行数据的复制(如通过管道或消息队列等方式),共享内存避免了数据在用户空间和内核空间之间的频繁拷贝,因此具有很高的通信效率。
二、共享内存的实现步骤
2.1 shmget 创建或获取共享内存段
在 Linux 系统中,可以使用 shmget
函数来创建或获取一个共享内存段。其函数原型如下:
#include <sys/shm.h>int shmget(key_t key, size_t size, int shmflg);
- 参数解释:
key
:一个唯一的键值,用于标识共享内存段。可以使用ftok
函数生成一个键值,也可以使用一个固定的整数值。size
:指定共享内存段的大小,单位为字节。shmflg
:标志位,用于指定操作的类型和权限。常见的标志有IPC_CREAT
(如果共享内存段不存在,则创建一个新的)、IPC_EXCL
(与IPC_CREAT
一起使用,若共享内存段已存在则返回错误),以及权限标志(如0600
表示所有用户都有读写权限)。- 返回值:
- 成功时返回一个非负的共享内存标识符(
shmid
),后续的操作将使用这个标识符。- 失败时返回 -1,并设置相应的错误码。
2.2 shmat 访问共享内存
使用 shmat
函数将创建或获取的共享内存段映射到当前进程的地址空间中,这样进程就可以像访问普通内存一样访问共享内存。其函数原型如下:
#include <sys/shm.h>void *shmat(int shmid, const void *shmaddr, int shmflg);
- 参数解释:
shmid
:由shmget
函数返回的共享内存标识符。shmaddr
:指定共享内存映射的地址。通常设置为NULL
,表示让系统自动选择一个合适的地址进行映射。shmflg
:标志位,常见的标志有SHM_RDONLY
(以只读方式映射),一般设置为 0 表示以读写方式映射。- 返回值:
- 成功时返回映射后的内存地址。
- 失败时返回
(void *)-1
,并设置相应的错误码。
2.3 strcpy 对共享内存进行读写操作
一旦共享内存段被映射到进程地址空间,就可以像操作普通内存一样对其进行读写操作。例如:
char *shared_memory = (char *)shmat(shmid, NULL, 0);
if (shared_memory != (char *)-1) {// 写入数据strcpy(shared_memory, "Hello, shared memory!");// 读取数据printf("Data in shared memory: %s\n", shared_memory);
}
2.4 shmdt 解除共享内存的映射
当进程不再需要访问共享内存时,需要使用 shmdt
函数将共享内存段从进程地址空间中解除映射。其函数原型如下:
#include <sys/shm.h>int shmdt(const void *shmaddr);
- 参数解释:
shmaddr
:由shmat
函数返回的映射后的内存地址。- 返回值:
- 成功时返回 0。
- 失败时返回 -1,并设置相应的错误码。
2.5 shnctl 删除共享内存段
当所有使用该共享内存段的进程都不再需要它时,需要使用 shmctl
函数将其从系统中删除。其函数原型如下:
#include <sys/shm.h>int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- 参数解释:
shmid
:由shmget
函数返回的共享内存标识符。cmd
:指定要执行的操作,常见的操作是IPC_RMID
,表示删除共享内存段。buf
:用于获取或设置共享内存段的状态信息。当cmd
为IPC_RMID
时,该参数通常设置为NULL
。- 返回值:
- 成功时返回 0。
- 失败时返回 -1,并设置相应的错误码。
三、共享内存
3.1创建一个进程 拿到数据test.c
#include <stdio.h> // 标准输入输出函数头文件,如printf
#include <stdlib.h> // 标准库函数头文件,包含exit等函数
#include <unistd.h> // Unix 系统函数定义头文件
#include <string.h> // 字符串处理函数头文件,如strcpy等
#include <sys/shm.h> // 共享内存相关的系统调用头文件int main() {// 使用shmget创建或获取共享内存段,键值为1234,大小为256字节,权限为0600(所有者可读写)int shmid = shmget((key_t)1234, 256, IPC_CREAT | 0600); if (shmid == -1) { // 如果shmget调用失败perror("shmget"); // 打印错误信息exit(1); // 终止程序}// 将共享内存段映射到当前进程的地址空间char* s = shmat(shmid, NULL, 0); if (s == (char*)-1) { // 如果shmat调用失败perror("shmat"); // 打印错误信息exit(1); // 终止程序}// 打印共享内存的地址(这里只是简单打印,实际可能需要进行读写操作)printf("s=%s\n", s); return 0;
}
使用了 System V 的共享内存机制来实现进程间通信 并没有信号量控制可以传递数据
3.2 加入循环test.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/shm.h>int main() {// 创建或者获取已经存在的共享内存int shmid = shmget((key_t)1234, 256, IPC_CREAT|0600); if (-1 == shmid) {exit(1);}// 将共享内存映射到进程地址空间,返回指针char* s = shmat(shmid, NULL, 0); if ((char*)-1 == s){exit(1);}while (1) {// 使用指针打印共享内存的数据printf("s=%s\n", s); sleep(2);}return 0;
}
- 创建或获取共享内存:
int shmid = shmget((key_t)1234, 256, IPC_CREAT|0600);
:
shmget
函数用于创建或获取共享内存段。(key_t)1234
是共享内存的键值,用于唯一标识这个共享内存段。256
是共享内存的大小,单位是字节。IPC_CREAT|0600
表示如果共享内存不存在则创建它,并且设置权限为0600
(即所有者可读可写)。if (-1 == shmid)
:如果shmget
函数调用失败,shmid
的值会是-1
,此时通过exit(1)
终止程序。- 映射共享内存到进程地址空间:
char* s = shmat(shmid, NULL, 0);
:
shmat
函数用于将共享内存段映射到调用进程的地址空间。shmid
是之前shmget
返回的共享内存标识符。NULL
表示由系统选择合适的地址进行映射。0
表示映射的选项,这里是默认选项。if ((char*)-1 == s)
:如果shmat
函数调用失败,返回值是(char*)-1
,此时通过exit(1)
终止程序。
3.3 简单的共享内存写入程序
main.c
这段代码实现了一个简单的共享内存写入程序。程序通过创建或获取一个共享内存区域,将其映射到当前进程的地址空间,然后不断从标准输入读取用户输入的字符串,并将这些字符串写入共享内存中,直到用户输入以 "end" 开头的字符串为止。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/shm.h>int main() {// 创建或获取共享内存int shmid = shmget((key_t)1234, 256, IPC_CREAT | 0600);if (-1 == shmid) {exit(1);}// 将共享内存映射到进程地址空间char* s = shmat(shmid, NULL, 0);if ((char*)-1 == s) {exit(1);}while (1) {printf("input\n");char buff[128] = {0};fgets(buff, 128, stdin);if (strncmp(buff, "end", 3) == 0) {break;}strcpy(s, buff); // 向共享内存写入数据}return 0;
}
创建或获取共享内存
shmget
函数用于创建或获取一个共享内存段。
(key_t)1234
:共享内存的键值,用于唯一标识该共享内存段。256
:共享内存段的大小,单位为字节。IPC_CREAT | 0600
:标志位,IPC_CREAT
表示如果指定键值的共享内存段不存在,则创建一个新的;0600
表示设置共享内存段的权限为所有者可读写。- 如果
shmget
调用失败,会返回 -1,此时程序调用exit(1)
终止。将共享内存映射到进程地址空间
shmat
函数用于将指定的共享内存段映射到当前进程的地址空间。
shmid
:共享内存段的标识符,由shmget
函数返回。NULL
:表示让系统自动选择一个合适的地址进行映射。0
:标志位,通常设置为 0 表示以读写方式映射。- 如果
shmat
调用失败,会返回(char*)-1
,此时程序调用exit(1)
终止。循环读取用户输入并写入共享内存
while (1)
:创建一个无限循环,用于不断读取用户输入。printf("input\n");
:提示用户输入。char buff[128] = {0};
:定义一个长度为 128 的字符数组buff
,并初始化为全 0。fgets(buff, 128, stdin);
:从标准输入读取一行字符串,最多读取 127 个字符(留一个字符用于存储字符串结束符'\0'
),并将其存储到buff
中。strncmp(buff, "end", 3) == 0
:比较buff
的前 3 个字符是否为 "end",如果是,则跳出循环。strcpy(s, buff);
:将buff
中的字符串复制到共享内存中,s
是共享内存映射后的地址。
3.4 带有信号量的共享内存
对信号量的分装 创建俩个信号量一个设为0一个设为1
sem.h代码
#include <stdio.h> // 标准输入输出函数头文件
#include <stdlib.h> // 标准库函数头文件,包含如exit等函数
#include <unistd.h> // Unix 标准函数定义,这里原代码可能误写为 <stdio.h>,推测是需要这个
#include <string.h> // 字符串处理函数头文件
#include <sys/sem.h> // 信号量相关系统调用的头文件// 定义用于信号量控制操作的联合体
union semun {int val; // 用于设置信号量的值// 这里在实际复杂使用时,还可能需要其他成员,如 struct semid_ds *buf 等,根据具体需求添加
};// 信号量初始化函数声明
void sem_init();
// 信号量P操作函数声明,用于获取资源,信号量值减1
void sem_p(int i);
// 信号量V操作函数声明,用于释放资源,信号量值加1
void sem_v(int i);
// 信号量销毁函数声明
void sem_destroy();
sem.c代码 用于操作信号量的函数
#include "sem.h"int semid = -1;void sem_init()
{semid = semget((key_t)1234,2,IPC_CREAT|IPC_EXCL|0600);//全新创建两个信号量if( semid == -1 )//失败,说明已经存在,直接获取{semid = semget((key_t)1234,2,0600);//获取if( semid == -1){printf("semget err\n");return;}}else//成功,初始化信号两{int arr[2] = {1,0};//信号量的值union semun a;for(int i = 0; i < 2; i++ ){a.val = arr[i];if( semctl(semid,i,SETVAL,a) == -1 ){printf("semct setval err\n");}}}
}void sem_p(int i)
{struct sembuf buf;buf.sem_num = i;buf.sem_op = -1;//pbuf.sem_flg = SEM_UNDO;if( semop(semid,&buf,1) == -1 ){printf("p err\n");}
}
void sem_v(int i)
{struct sembuf buf;buf.sem_num = i;buf.sem_op = 1;//vbuf.sem_flg = SEM_UNDO;if( semop(semid,&buf,1) == -1 ){printf("v err\n");}
}void sem_destroy()
{if( semctl(semid,0,IPC_RMID) == -1 ){printf("del err\n");}
}
int semid = -1;
:定义一个全局变量semid
用于存储信号量集的标识符,初始值设为 -1。
- 创建信号量集:
semid = semget((key_t)1234,2,IPC_CREAT|IPC_EXCL|0600);
:使用semget
函数创建一个信号量集。
(key_t)1234
:信号量集的键值,用于唯一标识该信号量集。key_t
是一种数据类型,用于表示信号量集的键值。键值是信号量集的唯一标识,不同的进程可以通过相同的键值来访问同一个信号量集。2
:表示要创建的信号量集中包含两个信号量。IPC_CREAT|IPC_EXCL|0600
:标志位,IPC_CREAT
表示如果信号量集不存在则创建,IPC_EXCL
表示如果信号量集已经存在则返回错误,0600
表示设置信号量集的权限为所有者可读写。- 如果第一次调用失败(
semid == -1
),说明信号量集已经存在,再次调用semget((key_t)1234,2,0600)
直接获取该信号量集。若获取也失败,则打印错误信息并返回。- 初始化信号量的值:
- 如果信号量集是新创建的,需要为每个信号量设置初始值。
int arr[2] = {1,0};
:定义一个数组存储两个信号量的初始值。union semun a;
:semun
是一个联合体,用于传递信号量控制操作所需的参数。- 通过
for
循环,调用semctl
函数来设置信号量集中某个信号量的值。使用semctl(semid,i,SETVAL,a)
为每个信号量设置初始值,SETVAL
表示设置信号量的值。- 这段代码封装了信号量的基本操作,通过这些函数可以方便地进行信号量的初始化、P 操作、V 操作和销毁,可用于实现进程间的同步和互斥。需要注意的是,
union semun
联合体在不同的系统中可能需要用户自行定义,使用时要确保其正确性。
信号量 P 操作函数
sem_p
sembuf
结构体设置:
struct sembuf buf;
:定义一个sembuf
结构体变量,用于描述对信号量的操作。buf.sem_num = i;
:指定要操作的信号量在信号量集中的编号。buf.sem_op = -1;
:表示 P 操作,即信号量的值减 1。当信号量的值大于 0 时,操作成功并将信号量的值减 1;当信号量的值为 0 时,进程会被阻塞,直到信号量的值大于 0。buf.sem_flg = SEM_UNDO;
:SEM_UNDO
标志表示如果进程异常终止,系统会自动恢复信号量的值,避免信号量状态不一致。semop
函数调用:
semop(semid,&buf,1)
:使用semop
函数执行信号量操作,semid
是信号量集的标识符,&buf
是指向sembuf
结构体的指针,1
表示要执行的操作数量。如果操作失败,打印错误信息。
信号量 V 操作函数
sem_v
- 与
sem_p
函数类似,只是buf.sem_op
的值为 1,表示 V 操作,即信号量的值加 1。V 操作会唤醒因该信号量为 0 而被阻塞的进程。同样使用semop
函数执行操作,若失败则打印错误信息。
信号量销毁函数
sem_destroy
semctl(semid,0,IPC_RMID)
:使用semctl
函数删除信号量集。
semid
是信号量集的标识符。0
在这里无实际意义,因为是删除整个信号量集。IPC_RMID
表示删除操作。如果删除失败,打印错误信息。
这段代码封装了信号量的基本操作,包括初始化、P 操作、V 操作和销毁。这些操作可以用于实现进程间的同步和互斥,确保多个进程在访问共享资源时不会发生冲突。例如,在多进程对共享内存进行读写操作时,可以使用信号量来控制对共享内存的访问顺序。
main.c是写
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/shm.h>
#include "sem.h"
int main()
{//创建,或获取共享内存int shmid = shmget((key_t)1234,256,IPC_CREAT|0600);if( -1 == shmid ){exit(1);}//映射char* s = shmat(shmid,NULL,0);if( (char*)-1 == s ){exit(1);}sem_init();while( 1 ){printf("input\n");char buff[128] = {0};fgets(buff,128,stdin);sem_p(0);strcpy(s,buff);//向共享内存写入数据(指针s)sem_v(1);if( strncmp(buff,"end",3) == 0 ){break;}}exit(0);
}
test.c是读
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/shm.h>
#include "sem.h"int main()
{int shmid = shmget((key_t)1234,256,IPC_CREAT|0600);//创建或者获取已经存在的共享内存if( -1 == shmid ){exit(1);}char* s = shmat(shmid,NULL,0);//将共享内存映射到进程地址空间,返回指针if( (char*)-1 == s ){exit(1);}sem_init();while( 1 ){sem_p(1);if( strncmp(s,"end",3) == 0 ){break;}printf("s=%s\n",s);//使用指针打印共享内存的数据sem_v(0);}sem_destroy();if( shmctl(shmid,IPC_RMID,NULL) == -1 ){printf("del shm err\n");}exit(0);
}
运行结果