Linux 进程通信之共享内存

共享内存

共享内存按照页为基本单位分配的,一页是 4K
共享内存无同步与互斥,生命周期随内核
共享内存无同步与互斥,生命周期随内核
共享内存无同步与互斥,生命周期随内核
共享内存区是最快的 IPC 形式,一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据:
mark

共享内存的数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
struct shmid_ds {
struct ipc_perm shm_perm; /* operation perms */
int shm_segsz; /* size of segment (bytes) */
__kernel_time_t shm_atime; /* last attach time */
__kernel_time_t shm_dtime; /* last detach time */
__kernel_time_t shm_ctime; /* last change time */
__kernel_ipc_pid_t shm_cpid; /* pid of creator */
__kernel_ipc_pid_t shm_lpid; /* pid of last operator */
unsigned short shm_nattch; /* no. of current attaches */
unsigned short shm_unused; /* compatibility */
void *shm_unused2; /* ditto - used by DIPC */
void *shm_unused3; /* unused */
};

shmget

得到一个共享内存标识符或创建一个共享内存对象并返回共享内存标识符

1
2
3
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg)

key: 此值来源于 ftok 返回的 IPC 键值
size 大于 0 的整数:新建的共享内存大小,以字节为单位
shmflg 取共享内存标识符,若不存在则函数会报错

  • IPC_CREAT:如果内核中不存在键值与 key 相等的共享内存,则新建一个共享内存;如果存在这样的共享内存,返回此共享内存的标识符
  • IPC_CREAT|IPC_EXCL:如果内核中不存在键值与 key 相等的共享内存,则新建一个消息队列;如果存在这样的共享内存则报错

return: 成功返回共享内存的标识符,出错返回 - 1,错误原因存于 error 中
错误代码:
EINVAL:参数 size 小于 SHMMIN 或大于 SHMMAX
EEXIST:预建立 key 所指的共享内存,但已经存在
EIDRM:参数 key 所指的共享内存已经删除
ENOSPC:超过了系统允许建立的共享内存的最大值 (SHMALL)
ENOENT:参数 key 所指的共享内存不存在,而参数 shmflg 未设 IPC_CREAT 位
EACCES:没有权限
ENOMEM:核心内存不足

shmat

把共享内存区对象映射到调用进程的地址空间,其实就是将共享内存绑定到当前进程

1
2
3
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg)

msqid: 共享内存标识符
shmaddr 指定共享内存出现在进程内存地址的什么位置,直接指定为 NULL 让内核自己决定一个合适的地址位置
shmflg:SHM_RDONLY:为只读模式,其他为读写模式,默认写 0 即可
return: 成功:附加好的共享内存地址,出错:-1,错误原因存于 error 中

错误代码
EACCES:无权限以指定方式连接共享内存
EINVAL:无效的参数 shmid 或 shmaddr
ENOMEM:核心内存不足
注意:fork 后子进程继承已连接的共享内存地址。exec 后该子进程与已连接的共享内存地址自动脱离。进程结束后,已连接的共享内存地址会自动脱离

shmdt

与 shmat 函数相反,解除当前进程与共享内存的绑定

1
2
3
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr)

msqid: 共享内存标识符
return: 成功:0 出错:-1,错误原因存于 error 中

错误代码
EINVAL:无效的参数 shmaddr
函数调用并不删除所指定的共享内存区,而只是将之前绑定的共享内存解除绑定

shmctl

控制共享内存

1
2
3
#include <sys/types.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

msqid: 共享内存标识符
cmd: 对共享内存的控制选项

  • IPC_STAT:得到共享内存的状态,把共享内存的 shmid_ds 结构复制到 buf 中
  • IPC_SET:改变共享内存的状态,把 buf 所指的 shmid_ds 结构中的 uid、gid、mode 复制到共享内存的 shmid_ds 结构内
  • IPC_RMID:删除这片共享内存

buf: 共享内存管理结构体,不关心则设置为 NULL
return: 成功:0, 出错:-1,错误原因存于 error 中

错误代码
EACCESS:参数 cmd 为 IPC_STAT,确无权限读取该共享内存
EFAULT:参数 buf 指向无效的内存地址
EIDRM:标识符为 msqid 的共享内存已被删除
EINVAL:无效的参数 cmd 或 shmid
EPERM:参数 cmd 为 IPC_SET 或 IPC_RMID,却无足够的权限执行

ipcs 与 ipcrm

与消息队列一致,由于共享内存的生命周期随内核,要么使用共享内存控制函数将其释放,那么通过命令手动释放,ipcs -m 就可以查看 IPC 资源中的共享内存,iprm -m shmid 就可以释放对应 shmid 号的共享内存
mark

通信示例

writer.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <memory.h>

int main() {
key_t k = ftok (".", 0x7777);//. 表示当前目录

if(k < 0){
printf("ftok error\n");
return 1;
}

// 申请共享内存
int shmid = shmget (k, 4096,IPC_CREAT|IPC_EXCL|0666);

if(shmid < 0){
printf("shmget error\n");
}

// 绑定共享内存
char *buf = shmat (shmid, NULL, 0);
if(buf == NULL){
printf("shmar error\n");
}

int i =0;
memset(buf, '\0', 4096);
while(i < 26){
sleep (1);
buf [i] = 'A'+i;
i++;
}
// 取消绑定共享内存
shmdt (shmid);

// 释放共享内存
shmctl (shmid, IPC_RMID, NULL);
return 0;
}

reder.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>

int main() {
key_t k = ftok (".", 0x7777);//. 表示当前目录

if(k < 0){
printf("ftok error\n");
return 1;
}

// 申请共享内存
int shmid = shmget (k, 4096,IPC_CREAT);

if(shmid < 0){
printf("shmget error\n");
}

// 绑定共享内存
char *buf = shmat (shmid, NULL, 0);
if(buf == NULL){
printf("shmar error\n");
}

while(1){
sleep (1);
printf("% s\n", buf);
}


// 取消绑定共享内存
shmdt (shmid);
return 0;
}