Linux下通过信号量保证共享内存方式通信同步

1 共享内存示意图


共享内存
1. 共享内存概述
共享内存是允许两个不相关的进程访问同一个逻辑内存的进程间通信方法,是在两个正
在运行的进程之间共享和传递数据的一种非常有效的方式。
不同进程之间共享的内存通常安排为同一段物理内存。进程可以将同一段共享内存连接
到它们自己的地址空间中,所有进程都可以访问共享内存中的地址,就好像它们是由用 C
语言 malloc()分配的内存一样。两个进程使用共享内存通信机制如图 所示。

POSIX 共享内存区涉及四个主要步骤:

 指定一个名字参数调用 shm_open,以创建一个新的共享内存区对象(或打开一个
以存在的共享内存区对象)
;
 调用 mmap 把这个共享内存区映射到调用进程的地址空间;
 调用 munmap() 取消共享内存映射;
 调用 shm_unlink()函数删除共享内存段。
在编译 POSIX 共享内存应用程序时需要加上-lrt 参数。
2. 打开或创建一个共享内存区
shm_open()函数用来打开或者创建一个共享内存区,两个进程可以通过给 shm_open()
函数传递相同的名字以达到操作同一共享内存的目的。它的原型如下:
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
int shm_open(const char *name, int oflag, mode_t mode);
函数成功返回创建或打开的共享内存描述符,与文件描述符相同作用,供后续操作使用,
失败返回-1。
 参数 name 为指定创建的共享内存的名称,其它进程可以根据这个名称来打开共享
内存;
 参数 oflag 为以下值的或值:

 O_RDONLY:共享内存以只读方式打开;
 O_RDWR:共享内存以可读写方式打开;
 O_CREAT:共享内存不存在才创建;
 O_EXCL:如果指定了 O_CREAT,但共享内存已经存在时返回错误;
 O_TRUNC:如果共享内存已存在则将大小设置为 0;
参数 mode 只有指定 O_CREAT 才有效指出,指出共享内存的权限,与 open()函数
类似。
注意:新创建或打开的共享内存大小默认为 0,需要设置大小才能使用。
3. 删除共享内存
当使用完共享内存后,需要将其删除,以便释放系统资源,可通过 shm_unlink()函数完
成。shm_unlink()函数原型如下:
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
int shm_unlink(const char *name);
函数成功返回 0,否则返回-1。参数 name 为共享内存的名字。

4. 设置共享内存大小
创建一个共享内存后,默认大小为 0,所以需要设置共享内存大小。ftruncate()函数可用
来调整文件或者共享内存的大小,它的原型如下:
#include <unistd.h>
#include <sys/types.h>
int ftruncate(int fd, off_t length);
函数成功返回 0,失败返回-1。
参数 fd 为需要调整的共享内存或者文件,length 为需要调整的大小。
5. 映射共享内存
创建共享内存后,需要将共享内存映射到调用进程的地址空间,可通过 mmap()函数来
完成。mmap()函数原型如下:
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
函数成功返回映射后指向共享内存的虚拟地址,失败返回 MAP_FAILED 值。
参数如下:
 addr:指向映射存储区的起始地址,通常将其设置为 NULL,这表示由系统选择该
映射区的起始地址;
 len:映射的字节数;
 port:对映射存储区的保护要求,对指定映射存储区的保护要求不能超过文件 open
模式访问权限。它可以为以下值的或值:

 PROT_READ:映射区可读);
 PROT_WRITE:映射区可写;
 ,PROT_EXEC:映射区可执行;
 PROT_NONE:映射区不可访问。
flag:映射标志位,可为以下值的或值:
 MAP_FIXED:返回值必须等于 addr。因为这不利于可移植性,所以不鼓励使
用此标志;
 MAP_SHARED:多个进程对同一个文件的映射是共享的,一个进程对映射的
内存做了修改,另一个进程也会看到这种变化;
 MAP_PRIVATE:多个进程对同一个文件的映射不是共享的,一个进程对映射
的内存做了修改,另一个进程并不会看到这种变化。
 fd:要被映射的文件描述符或者共享内存描述符;
 offset:要映射字节在文件中的起始偏移量。
6. 取消共享内存映射
已经建立的共享内存映射,可通过 munmap()函数用来取消。munmap()函数原型如下:

#include <sys/mman.h>
int munmap(void *addr, size_t length);
函数成功返回 0,否则返回-1;
参数 addr 为 mmap()函数返回的地址,length 是映射的字节数。取消映射后再对映射地
址访问会导致调用进程收到 SIGSEGV 信号。
7. 共享内存范例
程序清单 12.14 与程序清单 12.15 两个范例实现了两个无关进程使用共享内存进行通信
的功能。一个进程往共享内存的起始地址写入一个整型数据 18,另一个进程则检测该区域
的数据,如果不是 18 则继续等待,直到该区域的数据变化为 18。
程序清单 12.14 所示代码完成共享内存写操作:
先创建共享内存,
设置大小并完成映射,
随后往共享内存起始地址写入一个值为 18 的整型数据,最后取消和删除共享内存。
程序清单  sample10_write 共享内存写数据
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#define SHMSIZE
10
#define SHMNAME "shmtest"
/* 共享内存大小,10 字节 */
/* 共享内存名称 */
int main()
{
int fd;
char *ptr;
/* 创建共享内存 */
fd = shm_open(SHMNAME, O_CREAT | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR);
if (fd<0) {
perror("shm_open error");
exit(-1);
}
ftruncate(fd, SHMSIZE);
/* 设置大小为 SHMSIZE
/* 设置共享内存大小*/
ptr = mmap(NULL, SHMSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) {
perror("mmap error");
exit(-1);
}

*ptr = 18; /* 往起始地址写入 18 */
munmap(ptr, SHMSIZE); /* 取消映射 */
shm_unlink(SHMNAME); /* 删除共享内存 */
return 0;
}
程序清单 则实现了共享内存的读取操作:首先创建共享内存,设置大小并映射共
享内存后,检测共享内存首字节数据是否为 18,如果不是,继续等待,否则打印显示,并
取消和删除共享内存。
程序清单 共享内存读数据
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#define SHMSIZE
10
/* 共享内存大小,10 字节 */
#define SHMNAME "shmtest"
/* 共享内存名称
*/
int main()
{
int fd;
char *ptr;
fd = shm_open(SHMNAME, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
/*创建共享内存
*/
if (fd < 0) {
perror("shm_open error");
exit(-1);
}
ptr = mmap(NULL, SHMSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);/*映射共享内存*/
if (ptr == MAP_FAILED) {
perror("mmap error");
exit(-1);
}
ftruncate(fd, SHMSIZE);
/* 设置共享内存大小
while (*ptr != 18) {
/* 读起始地址,判断值是否为 18
sleep(1);
*/
*/
/* 不是 18,继续读取 */
printf("ptr : %d\n", *ptr); /* 数据是 18,打印显示 */
munmap(ptr, SHMSIZE); /* 取消内存映射 */
shm_unlink(SHMNAME); /* 删除共享内存 */
}

return 0;
}
共享内存的读进程在后台运行,它不断地等待着写进程对共享内存进行修改,当检测到写进程修改了共
享内存后,读进程即可将变化的内存值获取并打印出来。

 

2

 

 

信号量
1. 信号量概述
多进程编程中需要关注进程间同步及互斥。同步是指多个进程为了完成同一个任务相互
协作运行,而互斥是指不同的进程为了争夺有限的系统资源(硬件或软件资源)而相互竞争
运行。
信号量是用来解决进程间的同步与互斥问题的一种进程间通信机制,它是一个特殊的变
量,变量的值代表着关联资源的可用数量。若等于 0 则意味着目前没有可用的资源。
根据信号量的值可以将信号量分为二值信号量和计数信号量:
 二值信号量:信号量的值只有 0 和 1 值,若资源被锁住,信号量值为 0,若资源可
用则信号量值为 1;
 计数信号量:信号量的值在 0 到一个大于 1 的值(最大 32767)。该计数表示可用
的资源的个数。
信号量只能进行的两个原子操作:P 操作:V 操作。
P 原子操作和 V 原子操作的具体定义如下。
 P 操作:如果有可用的资源(信号量值>0),则占用一个资源(给信号量值减 1);
如果没有可用的资源(信号量值=0),则进程被阻塞直到系统将资源分配给该进程
(进入信号量的等待队列,等到资源后唤醒该进程)

 V 操作:如果在该信号量的等待队列中有进程在等待资源,则唤醒一个阻塞进程;
如果没有进程等待它,则释放一个资源(给信号量值加 1)
POSIX 提供两类信号量:有名信号量和基于内存的信号量(也称无名信号量)。有名
信号量可以让不同的进程通过信号量的名字获取到信号量,而内存的信号量只能放置在进程
间共享内存区域中。有名信号量与无名信号量的初始化和销毁方式与内存的信号量不同,操
作流程区别如图 所示。

图  有名信号量及基于内存的信号量
编译 POSIX 信号量程序需要加上-pthread 参数。
2. 创建或打开有名信号量
使用有名信号量前需要先创建或打开信号量,
可使用 sem_open()函数来完成。 sem_open()
函数的原型如下:
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag,mode_t mode, unsigned int value);
信号量的类型为 sem_t,该结构里记录着当前共享资源的数目。sem_open()函数成功返
回指向信号量的指针,失败返回 SEM_FAILED。参数如下:
 参数 name 为信号量的名字,两个不同的进程可以通过传递相同的名字打开同一个
信号量;
 oflag 可以是为以下值的或值:
 O_CREAT:
如果 name 指定的信号量不存在则创建,
此时必须给出 mode 和 value
值;
 O_EXCL:
如果 name 指定的信号量存在,
而 oflag 指定为 O_CREAT | O_EXCL,
则 sem_open()函数返回错误。
 mode 为信号量的权限位,类似 open()函数;
 value 为信号量的初始化值。
3. 关闭有名信号量
当不再需要使用有名信号量是可以用 sem_close()函数用来关闭,它的原型如下:
#include <semaphore.h>
int sem_close(sem_t *sem);
函数成功返回 0,失败返回-1,参数 sem 为需要关闭的信号量的指针。

4. 初始化基于内存信号量
使用基于内存的信号量之前需要先用 sem_init()函数完成初始化,它的原型如下:
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
函数成功返回 0,失败返回-1;
参数 sem 为需要初始化信号量的指针;pshared 值如果为 0 表示该信号量只能在线程内
部使用,否则为进程间使用。在进程间使用时,该信号量需要放在共享内存处;value 为信
号量的初始化值,代表的资源数。
5. P 操作
信号量的 P 操作由 sem_wait()函数来完成,它的函数原型如下:
#include <semaphore.h>
int sem_wait(sem_t *sem);
如果信号量的值大于 0,sem_wait()函数将信号量值减 1 并立即返回,代表着获取到资
源,如果信号量值等于 0 则调用进程(线程)将进入睡眠状态,直到该值变为大于 0 时才将
信号量减 1 才返回。
函数成功返回 0,否则返回-1;参数 sem 为需要操作的信号量。
6. V 操作
信号量的 V 操作有 sem_post()函数来完成,它的函数原型如下:
#include <semaphore.h>
int sem_post(sem_t *sem);
当一个进程(线程)使用完某个信号灯时,它应该调用 sem_post 来告诉系统申请的资
源已经用完。sem_post()函数与 sem_wait 函数的功能正好相反,它把所指定的信号灯的值加
1,然后唤醒正在等待该信号灯值变为正数的任意进程(线程)。
函数成功返回 0,否则返回-1;参数 sem 为需要操作的信号量。
7. 销毁基于内存的信号量
销毁基于内存的信号量可使用 sem_destroy()函数来完成,它的原型如下:
#include <semaphore.h>
int sem_destroy(sem_t *sem);
该函数只能销毁由 sem_init()初始化的信号量。销毁后该信号量将不能再被使用。
函数成功返回 0,否则返回-1。参数 sem 指出需要销毁的信号量。
8. 删除有名信号量
当相关的进程都已完成对有名信号量的使用时,可以用 sem_unlink()函数用来删除它,
以释放资源。sem_unlink()函数原型如下:
#include <semaphore.h>
int sem_unlink(const char *name);
该函数成功返回 0,失败返回-1。

9. 信号量范例
上一节介绍了通过共享内存进行进程间通信的示例,使用循环等待的方式来等待共享内
存中值的改变,此方式存在浪费处理器资源等弊端,而信号量的同步功能可解决这个问题。
下面给出一个范例,服务端和客户端通过共享内存通信,服务端接收客户端传递的数据,用
信号量实现两进程同步。
服务端程序如程序清单  所示,程序从启动参数获取共享内存名称,先创建共享内
存,设置大小后完成映射;然后创建信号量并等待客户端的通知。
对象和信号量对象,其中信号量的初始值为 0。在共享内存映射后使用 sem_wait()函数
等待客户端完成对共享内存的写入操作。
程序清单 sample11_server 有名信号量服务端程序
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <semaphore.h>
#include <string.h>
#include <errno.h>
#define MAPSIZE
100
/* 共享内存大小,100 字节
*/
int main(int argc,char **argv)
{
int shmid;
char *ptr;
sem_t *semid;
if (argc != 2) {
/* 参数 argv[1]指定共享内存和信号量的名字
*/
printf("usage: %s <pathname>\n", argv[0]);
return -1;
}
shmid = shm_open(argv[1], O_RDWR|O_CREAT, 0644);
/* 创建共享内存对象
*/
if (shmid == -1) {
printf( "open shared memory error\n");
return -1;
}
ftruncate(shmid, MAPSIZE);
/* 设置共享内存大小
/* 将共享内存进行映射
*/
*/
ptr = mmap(NULL, MAPSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shmid, 0);
strcpy(ptr,"\0");
semid = sem_open(argv[1], O_CREAT, 0644, 0);
/* 创建信号量对象
if (semid == SEM_FAILED) {
278

printf("open semaphore error\n");
return -1;
}
sem_wait(semid);
/* 信号量等待操作,等待客户端修改共享内存 */
printf("server recv:%s",ptr);
/* 从共享内存中读取值 */
munmap(ptr, MAPSIZE); /* 取消对共享内存的映射 */
close(shmid); /* 关闭共享内存 */
sem_close(semid); /* 关闭信号量 */
sem_unlink(argv[1]); /* 删除信号量对象 */
shm_unlink(argv[1]); /* 删除共享内存对象 */
strcpy(ptr,"\0");
return 0;
}
客户端如程序清单 所示,程序获取参数打开指定的共享内存,并设置大小和完成
映射;然后打开服务端已经创建的信号量,从标准输入读入一串字符串后写入共享内存区,
然后使用 sem_post()函数通知服务端。
程序清单 sample11_client 有名信号量客户端程序
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <semaphore.h>
#include <string.h>
#include <errno.h>
#define MAPSIZE
100
/* 共享内存大小,100 字节
*/
int main(int argc,char **argv)
{
int shmid;
char *ptr;
sem_t *semid;
if (argc != 2) {
printf("usage: %s <pathname>\n", argv[0]);
/* 参数 argv[1]指定共享内存和信号量的名字 */
/* 打开共享内存对象 */
return -1;
}
shmid = shm_open(argv[1], O_RDWR, 0);
if (shmid == -1) {
printf( "open shared memory error.\n");

return -1;
}
ftruncate(shmid, MAPSIZE);
/* 设置共享内存大小 */
/* 将共享内存进行映射 */
ptr = mmap(NULL, MAPSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shmid, 0);
semid = sem_open(argv[1], 0);
/* 打开信号量对象 */
fgets(ptr, MAPSIZE, stdin); /* 从标准输入读取需要写入共享内存的值 */
sem_post(semid); /* 通知服务端 */
munmap(ptr, MAPSIZE); /* 取消对共享内存的映射
if (semid == SEM_FAILED) {
printf("open semaphore error\n");
return -1;
}
printf("client input:");
*/
close(shmid);
sem_close(semid);
return 0;
}
图 为 sample11_server 和 sample11_client 的执行结果,
服务端和客户端携带相同的
参数运行,然后在客户端输入数据并按回车,此时服务端将获取该数据并打印出来,完成通
信后两个进程退出。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Linux中,共享内存是一种用于进程间通信的机制,多个进程可以访问同一块共享内存区域。使用ipcs -m命令可以查看当前系统中的共享内存信息,而使用ipcrm -m shmid命令可以删除指定的共享内存信号量是一种用于进程同步和互斥的机制,可以用来保证共享内存的正确访问。在Linux中,可以通过信号量来实现共享内存同步共享内存本身不提供任何保护资源,因此需要利用其他机制来保证数据的正确性。通过使用信号量,可以实现多个进程之间对共享内存同步和互斥操作,从而避免数据访问的竞争问题。 在Linux中,要创建一个共享内存,可以使用shmget函数,该函数的原型为: int shmget(key_t key, size_t size, int shmflg); 通过指定key、size和shmflg参数,可以创建一个指定大小和属性的共享内存区域。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [Linux——进程间通信(2)——共享内存、信号、信号量](https://blog.csdn.net/jason_stanson/article/details/117156055)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [Linux--信号量共享内存](https://blog.csdn.net/m0_59052131/article/details/127970222)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

#君君#

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值