一、基本概念
顾名思义就是允许两个不相关的进程访问同一个逻辑内存, ,共享内存是两个正在运行的进 程之间共享和传递数据的一种非常有效的方式。 不同进程之间共享的内存通常为同一段物理内存。进程可 以将同一段物理内存连接到他们自己的地址空间中,所有的进程都可以访问共享内存中的地址。如果某个 进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。
共 享内存能够实现让两个或多个进程访问同一段物理内存空间,达到数据交互的效果。
![](https://i-blog.csdnimg.cn/blog_migrate/7ffe1d05b303adf2acbe5259917d2819.png)
共享内存和其他进程间数据交互方式相比,有以下几个突出特点: 1. 速度快,因为共享内存不需要内核控制,所以没有系统调用。而且没有向内核拷贝数据的过程, 所以效率和前面几个相比是最快的,可以用来进行批量数据的传输,比如图片。 2. 没有同步机制,需要借助 Linux 提供其他工具来进行同步,通常使用信号灯。
使用共享内存的步骤: 1.调用 shmget()创建共享内存段 id, 2.调用 shmat()将 id 标识的共享内存段加到进程的虚拟地址空间, 3.访问加入到进程的那部分映射后地址空间,可用 IO 操作读写。
int shmget(key_t key, size_t size, int shmflg)
key:由ftok生成的key标识,标识是系统的唯一ipc资源
size:需要申请共享内存的大小,
在操作系统中,申请内存的最小单位为页,一页 是 4k 字节,为了避免内存碎片,我们一般申请的内存大小为页的整数倍。
shmflg:如果要创建新的共享内存,需要使用 IPC_CREAT,IPC_EXCL,如果是已经存在 的,可以使用 IPC_CREAT 或直接传
key_t ftok(const char *pathname, int proj_id)
pathname:文件路径以及文件名
prof_id:字符
建立 IPC 通讯(如消息队列、共享内存时)必须指定一个 ID 值。通常情 况下,该 id 值通过 ftok 函数得到
void *shmat(int shmid, const void *shmaddr, int shmflg)
shmid:共享内存的标识符,也就是shmget的返回值
shmaddr:映射到的地址,一般写NULL系统为我们自动完成映射
shmflg:通常为0,标识共享内存可写可读或者为 SHM_RDONLY,表示共享内存可 读可写
int shmdt(const void *shmaddr)
shmaddr:共享内存映射后的地址
int shmctl(int shmid, int cmd, struct shmid_ds *buf)
shmid:要删除的共享内存标识符
cmd:IPC_STAT (获取对象属性) IPC_SET (设置对象属性) IPC_RMID(删除对象) buf:指定 IPC_STAT (获取对象属性) IPC_SET (设置对象属性) 时用来保存或者设置 的属性
优点:我们可以看到使用共享内存进行进程之间的通信是非常方便的,而且函数的接口也比较简单, 数据的共享还使进程间的数据不用传送,而是直接访问内存,加快了程序的效率。 缺点:共享内存没有提供同步机制,这使得我们在使用共享内存进行进程之间的通信时,往往需要借 助其他手段来保证进程之间的同步工作
二、ipcs
1. 获取 key 值,内核会将 key 值映射成 IPC 标识符,获取 key 值常用方法: (1)在 get 调用中将 IPC_PRIVATE 常量作为 key 值。 (2)使用 ftok()生成 key
2. 执行 IPC get 调用,通过 key 获取整数
![](https://i-blog.csdnimg.cn/blog_migrate/3e27a0c61b2f36917697805deb5c0d76.png)
3. 通过 id 访问 IPC 对象
![](https://i-blog.csdnimg.cn/blog_migrate/b57e161b156db1e829fcc828241b26b1.png)
4. 通过 id 控制 IPC 对象
![](https://i-blog.csdnimg.cn/blog_migrate/13430aa03841b08e58328c787713e797.png)
Linux 内核为每个消息队列对象维护一个 msqid_ds,每个 msqid_ds 对应一个 id,消息以链表形式存储, 并且 msqid_ds 存放着这个链表的信息。
消息队列的特点: 1.发出的消息以链表形式存储,相当于一个列表,进程可以根据 id 向对应的“列表”增加和获取消息。 2.进程接收数据时可以按照类型从队列中获取数据。
消息队列的使用步骤: 1. 创建 key; 2. msgget()通过 key 创建(或打开)消息队列对象 id; 3. 使用 msgsnd()/msgrcv()进行收发; 4. 通过 msgctl()删除 ipc 对象 通过 msgget()调用获取到 id 后即可使用消息队列访问 IPC 对象,消息队列常用 API 如下:
int msgget(key_t key, int msgflg)
key:和消息队列相关的key值
msgflg:访问权限
发送数据
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg)
msqid:消息队列id
msgp:指向消息类型的指针
msgsz:发送的消息的字节数
msgflg:
如果为 0,直到发送完成函数才返回,即阻塞发送 IPC_NOWAIT:消息没有发送完成, 函数也会返回,即非阻塞发送
nt msgctl(int msqid, int cmd, struct msqid_ds *buf)
msqid:消息队列id
cmd:
IPC_STAT:读取消息队列的属性,然后把它保存在 buf 指向的缓冲区。 IPC_SET:设置消息队列的属性,这个值取自 buf 参数 IPC_RMID:删除消息队列 buf:消息队列缓冲区
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, intmsgflg)
msqid:对象id
msgp:消息指针
msgsz:字段大小
msgtyp:消息类型
msgflg:位掩码
接收消息
三、信号量:
信 号与信号量是不同的两种事物。
为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我 们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区 域。
让 一个临界区同一时间只有一个线程在访问它,也就是说信号量是用来调协进程对共享资源的访问的。
信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(即 P(信号变量))和发 送(即 V(信号变量))信息操作。最简单的信号量是只能取 0 和 1 的
由于信号量只能进行两种操作等待和发送信号,即 P(sv)和 V(sv),他们的行为是这样的
P(sv):如果 sv 的值大于零,就给它减 1;如果它的值为零,就挂起该进程的执行 V(sv):如果有其他进程因等待 sv 而被挂起,就让它恢复运行,如果没有进程因等待 sv 而挂起,就给它加 1
举个例子,就是两个进程共享信号量 sv,一旦其中一个进程执行了 P(sv)操作,它将得到信号量,并可 以进入临界区,使 sv 减 1。而第二个进程将被阻止进入临界区,因为当它试图执行 P(sv)时,sv 为 0,它会 被挂起以等待第一个进程离开临界区域并执行 V(sv)释放信号量,这时第二个进程就可以恢复执行。
信号灯 相当于一个值大于或等于 0 计数器,信号灯值大于 0,进程就可以申请资源,信号灯值-1,如果信号灯值为 0,一个进程还想对它进行-1,那么这个进程就会阻塞,直到信号灯值大于 1
使用 System V 信号灯的步骤如下: 1. 使用 semget()创建或打开一个信号灯集。 2. 使用 semctl()初始化信号灯集,。 3. 使用 semop()操作信号灯值,即进行 P/V 操作。 P 操作:申请资源,申清完后信号灯值-1; V 操作:释放资源,释放资源后信号灯值+1
int semget(key_t key, int nsems, int semflg)
key:信号量键值
nsems:信号量的数量
semflg:标识
创建一个新信号量或取得一个已有的信号量
int semctl(int semid, int semnum, int cmd, union semun arg)
semid:信号量id
semnum:信号量编号
cmd:IPC_STAT(获取信号量的属性) IPC_SET(设置信号量的属性)IPC_RMID (删除信号量) SETVAL(设置信号量的值) arg:
union semun { int val; struct semid_ds *buf; unsigned short *array; struct seminfo *__buf; } 初始化信号灯集合
int semop(int semid, struct sembuf *sops, size_t nsops)
semid:信号量id
sops:信号量结构体数组
nsops:要操作信号量的数量
要操作信号量的数量 struct sembuf{ unsigned short sem_num; //要操作的信号量的编号 short sem_op; //P/V 操作,1 为 V 操作,释放资源。-1 为 P 操作,分配资源。0 为等待,直到信号量的值变成 0 short sem_flg; //0 表示阻塞,IPC_NOWAIT 表示非阻塞 }
在信号量上执行一个或多个操作。
![](https://i-blog.csdnimg.cn/blog_migrate/a26a439493bd3ea6cdfc52a9a0dad725.png)
![](https://i-blog.csdnimg.cn/blog_migrate/8748f44494edfdb2b2f9321b283c82bb.png)
![](https://i-blog.csdnimg.cn/blog_migrate/652e7768ea76ebdd2b78801d7b729859.png)
![](https://i-blog.csdnimg.cn/blog_migrate/28a832a2988351e65ef5c0c1ca4b356a.png)
我们通常通过信号来解决多个进程对同一资源的访问竞争的问题,使在任一 时刻只能有一个执行线程访问代码的临界区域,也可以说它是协调进程间的对同一资源的访问权,也就是 用于同步进程的。