system V****共享内存
共享内存区是最快的IPC 形式。 一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据
共享内存示意图
共享内存数据结构
cpp
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函数
功能:用来创建共享内存
原型
int shmget(key_t key, size_t size, int shmflg);
参数
key: 这个共享内存段名字
size: 共享内存大小
shmflg: 由九个权限标志构成,它们的用法和创建文件时使用的 mode 模式标志是一样的
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回 -1
shmat函数
功能:将共享内存段连接到进程地址空间
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
shmid: 共享内存标识
shmaddr: 指定连接的地址
shmflg: 它的两个可能取值是 SHM_RND 和 SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节;失败返回 -1
- shmaddr为NULL,核心自动选择一个地址
- shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
- shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr - (shmaddr % SHMLBA)
- shmflg=SHM_RDONLY,表示连接操作用来只读共享内存
shmdt函数
功能:将共享内存段与当前进程脱离
原型
int shmdt(const void *shmaddr);
参数
shmaddr: 由 shmat 所返回的指针
返回值:成功返回 0 ;失败返回 -1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
shmctl函数
功能:用于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid: 由 shmget 返回的共享内存标识码
cmd: 将要采取的动作(有三个可取值)
buf: 指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回 0 ;失败返回 -1
cmd
共享内存实测代码
cpp
// comm.h
#ifndef COMM_H #define COMM_H
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define PATHNAME "."
#define PROJ_ID 0x6666
int createShm(int size);
int destroyShm(int shmid);
int getShm(int size);
#endif
cpp
// comm.c
#include "comm.h"
static int commShm(int size, int flags)
{
// 通过ftok函数,生成对应 PATHNAME 和 PROJ_ID 的键值 _key
key_t _key = ftok(PATHNAME, PROJ_ID);
if (_key < 0)
{
perror("ftok");
return -1;
}
// 获取 _key 对应的共享内存段的描述符,内存段大小为 size 字节
int shmid = 0;
if ((shmid = shmget(_key, size, flags)) < 0)
{
perror("shmget");
return -2;
}
return shmid;
}
int destroyShm(int shmid)
{
// 通过 IPC_RMID 删除共享内存段
if (shmctl(shmid, IPC_RMID, NULL) < 0)
{
perror("shmctl");
return -1;
}
return 0;
}
int createShm(int size)
{
// IPC_CREAT: 如果不存在key值的共享存储空间,且权限不为0,则创建共享存储空间,并返回一个共享存储标识符。如果存在,则直接返回共享存储标识符。
// IPC_CREAT | IPC_EXCL: 如果不存在key值的共享存储空间,且权限不为0,则创建共享存储空间,并返回一个共享存储标识符。如果存在,则产生错误。
// 0666: 设置权限为0666所有用户可读可写,具体查询linux权限相关内容
return commShm(size, IPC_CREAT | IPC_EXCL | 0666);
}
int getShm(int size)
{
return commShm(size, IPC_CREAT);
}
cpp
// server.c
#include "comm.h"
int main()
{
// 获取大小为4096的新的共享内存段
int shmid = createShm(4096);
// 将共享内存段连接到进程地址空间
char *addr = shmat(shmid, NULL, 0);
sleep(2);
// 通信主体逻辑
int i = 0;
while (i++ < 26)
{
printf("client# %s\n", addr);
sleep(1);
}
// 通信完成后,将共享内存段与当前进程脱离
shmdt(addr);
sleep(2);
// 销毁生成的共享内存段
destroyShm(shmid);
return 0;
}
cpp
// client.c
#include "comm.h"
int main()
{
// 获取此前执行 server.c 后生成的共享内存段
int shmid = getShm(4096);
sleep(1);
// 将 server.c 生成的共享内存段连接到进程地址空间
char *addr = shmat(shmid, NULL, 0);
sleep(2);
// 通信主体逻辑:往共享内存段写入数据,让 server.c 接收并返回输出
int i = 0;
while (i < 26)
{
addr[i] = 'A' + i;
i++;
addr[i] = 0;
sleep(1);
}
// 完成通信后,将共享内存段与当前进程脱离
shmdt(addr);
sleep(2);
return 0;
}
system V****信号量
信号量主要用于同步和互斥的,在操作系统中,使用System V共享内存时,共享内存本身并不提供同步和互斥的机制。这意味着多个进程可以同时访问同一块共享内存空间,如果进程对内存的访问没有同步和互斥的控制,这可能会引起一些进程的资源竞争,数据损坏的问题。
进程互斥
- 由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥。
- 系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源。
- 在进程中涉及到互斥资源的程序段叫临界区。
- 特性方面
- IPC资源必须删除(最好通过进程删除),否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核。