system V共享内存
作用:用于多个进程间数据共享
特性:最快的进程间通信方式,共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据
原理:开辟出一块物理内存地址,然后多个进程都映射到自己的虚拟地址空间中,通过虚拟地址直接访问物理内存中的数据
相当于是进程直接看到的数据。举个例子,管道就是从写端的数据拷贝到管道缓冲区,再从管道缓冲区拷贝到自己的进程空间,相对于共享内存是多了两次拷贝
共享内存示意图
共享内存数据结构
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:共享内存大小,一般是PAGE_SIZE的整数倍(4096字节)
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1
shmflg: IPC CREAT | IPC EXCL | 0664
IPC_ CREAT
如果共享内存不存在则创建打开,若已经存在则直接打开
IPC_ EXCL
与IPC_ CREAT搭配使用,共享内存不存在则创建打开,若存在则报错返回 ,一个程序只能启动一次的程序
mode_ flags
共享内存的访问权限0664
shmat函数
功能:
将共享内存段连接到进程地址空间
原型
void *shmat(int shmid, const void shmaddr, int shmflg);
参数
shmid: 共享内存标识 shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY(只读,前提是具备读的权限)
返回值:成功返回一个指针,指向共享内存第一个节;失败返回(void)-1
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
注意:这里常用的是IPC_RMID,但是并非直接删除,而是做一个标记,删除最终还是由操作系统进行
这里我们思考一个问题:
多个进程,访问同一个共享内存,突然有一个进程要删除了共享内存,如何避免其他进程不会出现错误呢?
其实共享内存是有个当前的映射连接计数(表示现在有多少进程正在访问)所以这里的RMID叫做标记删除,并不是真的删除,而是标记一下, 被标记的共享内存将不再接受新的映射而是等当前的映射连接计数为0时,再实际删除删除由系统完成,进程所做的删除操作,其实只是标记一下。
简单举例:
举例2
测试代码结构
# ls client.c comm.c comm.h Makefile server.c # cat Makefile .PHONY:all all:server client client:client.c comm.c gcc -o $@ $^ server:server.c comm.c gcc -o $@ $^ .PHONY:clean clean: rm -f client server
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
comm.c
#include "comm.h" static int commShm(int size, int flags) { key_t key = ftok(PATHNAME, PROJ_ID); if (key < 0) { perror("ftok"); return -1; } int shmid = 0; if ((shmid = shmget(_key, size, flags)) < 0) { perror("shmget"); return -2; } return shmid; } int destroyShm(int shmid) { if (shmctl(shmid, IPC_RMID, NULL) < 0) { perror("shmctl"); return -1; } return 0; } int createShm(int size) { return commShm(size, IPC_CREAT | IPC_EXCL | 0666); } int getShm(int size) { return commShm(size, IPC_CREAT); }
server.c
#include "comm.h" int main() { 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; }
client.c
#include "comm.h" int main() { int shmid = getShm(4096); sleep(1); char* addr = shmat(shmid, NULL, 0); sleep(2); int i = 0; while (i < 26) { addr[i] = 'A' + i; i++; addr[i] = 0; sleep(1); } shmdt(addr); sleep(2); return 0; }
结果演示
注意:共享内存没有进行同步与互斥!
特性:
1.最快的进程间通信方式
2.生命周期随内核(并不会随着打开的进程退出而被释放)
注意事项:
共享内存的访问操作存在安全问题(竞争访问出现数据二义问题)
补充:
1.共享内存的本质就是开辟一块物理内存, 让多个进程映射同-块物理内存到自己的地址空间进行访问,实现数据共享的。
2.共享内存的操作是非进程安全的,多个进程同时对共享内存读写是有可能会造成数据的交叉写入或读取,造成数据混乱
3.共享内存的删除操作并非直接删除,而是拒绝后续映射,只有在当前映射链接数为0时,表示没有进程访问了,才会真正被删除
4.共享内存生命周期随内核,只要不删除,就一-直存在于内核中,除非重启系统(当然这里指的是非手动操作,可以手动删除)
消息队列
详细可以参考这篇博客,我这里是简单总结了一下消息队列
功能:实现进程间的数据传输
本质:内核中的一个优先级队列
实现:多个进程通过访问同一一个消息队列,以添加数据节点和获取数据节点实现通信
总结:
1、采用消息队列通信比采用管道通信具有更多的灵活性,通信的进程不但没有血缘上的要求,也不需要进行同步处理。
2、消息队列是一种先进先出的队列型数据结构;
3、消息队列将输出的信息进行了打包处理,可以保证以消息为单位进行接收;
4、消息队列对信息进行分类服务,根据消息的类别进行分别处理。
5、提供消息数据自动拆分功能,同时不能接受两次发送的消息。
6、消息队列提供了不完全随机读取的服务。 7、消息队列提供了完全异步的读写服务。
信号量
(这里做简单介绍)
作用:用于实现进程间的同步与互斥
本质:是一个计数器+ pcb等待队列
P操作:对计数器进行-1操作,判断计数是否大于等于0,正确则返回;失败则阻塞,其中阻塞就是把它置位可中断休眠状态,挂到pcb等待对联中,直到被唤醒
V操作:对计数器进行+ 1操作,若计数器小于等于0,则唤醒一个等待的进程
同步实现:
通过计数器对共享资源进行计数,在获取资源之前,则进行P操作,计数满足访问条件则访问,若不满足则阻塞当产生一个资源,则进行V操作,唤醒阻塞的进程
互斥实现:
初始化计数器为1,表示资源只有一一个。
访问资源之前进行P操作;访问资源完毕之后进行V操作