macosx 不允许无名信号量_无名信号量——相关进程间同步

本文探讨了如何在macosx中使用无名信号量进行进程间同步,解析了由于fork()导致的信号量问题,并介绍了通过mmap和munmap创建共享内存来解决此问题的方法。示例代码展示了父子进程间的拉锯操作,揭示了无名信号量在进程间同步的限制和应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、概述

无名信号量可以在相关进程间进行同步,所谓相关进程暂时先简单的理解为父子进程,最后再详细的解释一下。在上一篇博客 无名信号量——线程间同步 https://www.cnblogs.com/Suzkfly/p/14336610.html中已经介绍过信号量相关的各个函数,其中sem_init第二个参数,如果传入的值是非0值,就表示使用的信号量是在进程间共享。

按照这个理解,先来写一个简单的范例程序:

1 #include

2 #include

3

4 int main(int argc, const char *argv[])5 {6 int pid = 0;7 sem_t sem;8 int ret = 0;9

10 ret = sem_init(&sem, 1, 1); /*初始化信号量的值为1*/

11 printf("ret = %d\n", ret);12

13 pid =fork();14

15 if (pid == 0) { /*子进程*/

16 printf("I'm child\n");17 printf("child sem_wait...\n");18 sem_wait(&sem);19 printf("child sem_wait ok\n");20 } else if (pid > 0) { /*父进程*/

21 sleep(1);22 printf("I'm parent\n");23 printf("parent sem_wait...\n");24 sem_wait(&sem);25 printf("parent sem_wait ok\n");26 }27

28 return 0;29 }

运行结果:

66280d332d7ef09e85f5dd98dd6b79cb.png

代码分析:

在代码第10行初始化了信号量初值为1,在父进程中睡眠1秒,表示让子进程先行,子进程wait_sem会成功,但是子进程sem_wait之后,信号量的值就变为0了,到了父进程它再执行sem_wait的时候应该不成功,但从结果上来看,父进程sem_wait也成功了。其实稍微分析一下就能理解,因为fork函数会将进程资源复制一份,此时虽然sem地址值是一样的,但实际上它们指向的是不同的sem。

那么要解决这个问题的思路就很清晰了,让两个进程都能找到同一个sem就可以了。

2、 mmap与munmap函数

mmap在framebuffer程序中用到过,它能用来将一个文件映射到内存中,通过操作内存达到操作文件的效果,并且它映射出来的地址是进程共享的。简单介绍一下mmap函数:

函数原型

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

头文件

sys/mman.h

功能

虚拟地址映射

参数

[in]:addr:指定映射的起始地址,如果填入NULL,则表示由系统自动分配地址,一般都填入NULL。

[in]:length:映射的地址空间大小。

[in]:prot:映射区域的权限。

#PROT_READ:可读

#PROT_WRITE:可写

#PRTO_EXEC:可执行

#PRTO_NONE:不能被访问

[in]:flags: 标志位参数,常用标志如下(其他标志也可以在man手册中查到)

#MAP_SHARED:与其他进程共享

#MAP_PRIVATE:不与其他进程共享

MAP_SHARED与MAP_PRIVATE二者必选其一。

#MAP_ANONYMOUS:同MAP_ANON,匿名映射,映射区不与文件关联。

[in]:fd:文件描述符,如果是匿名映射,则该处填入-1。

[in]:offset:文件偏移量,必须是4096的整数倍。

返回

成功返回映射区地址,失败返回MAP_FAILED,其值为((void*)-1),由于mmap很容易失败,因此必须要去接收mmap的返回值。

mmap映射的地址用完之后要使用munmap释放,munmap的函数介绍如下:

函数原型

int munmap(void *addr, size_t length);

头文件

sys/mman.h

参数

[in]:addr:映射区首地址,就是mmap返回的那个地址。

[in]:length:映射区长度,就是mmap传入的长度。

返回

成功返回0,失败返回-1

我不知道如果不使用munmap释放会造成什么后果,我自己写了测试程序,发现无论用不用munmap释放,用free命令查看都看不出区别, 并且用valgrind工具也看不出来。

介绍这两个函数是为了得到一块共享的匿名地址区域。

3、代码修改

改后代码如下:

1 #include

2 #include

3 #include

4 #include

5

6 int main(int argc, const char *argv[])7 {8 sem_t *p_sem =NULL;9 int pid = 0;10 int ret = 0;11

12 p_sem = mmap(NULL, /*由系统分配地址*/

13 sizeof(sem_t), /*申请的地址大小*/

14 PROT_READ | PROT_WRITE, /*读写权限*/

15 MAP_SHARED | MAP_ANON, /*进程共享,映射区与文件不关联*/

16 -1, /*文件描述符,匿名映射传入-1*/

17 0); /*偏移量*/

18

19 printf("p_sem = %p\n", p_sem);20

21 ret = sem_init(p_sem, 1, 1); /*进程间使用的信号量,初值为1*/

22 printf("ret = %d\n", ret);23

24 pid =fork();25

26 if (pid == 0) { /*子进程*/

27 printf("I'm child\n");28 printf("child sem_wait...\n");29 sem_wait(p_sem);30 printf("child sem_wait ok\n");31 } else if (pid > 0) { /*父进程*/

32 sleep(1);33 printf("I'm parent\n");34 printf("parent sem_wait...\n");35 sem_wait(p_sem);36 printf("parent sem_wait ok\n");37 }38

39 return 0;40 }

执行结果:

c31c395a7a6dc25131164fe7b00630f9.png

此时由于子进程获取到了信号量并且没有释放,因此父进程将一直阻塞。该程序中mmap映射的区域不会因为fork而被一分为二,因此p_sem指向的区域其实是同一个东西。malloc有类似的功能,但是malloc出来的地址空间在fork之后也会变为两份。

那么问题来了,现在我知道了p_sem的地址,我如果另外编写一个程序去调用sem_post会怎样呢。设改程序为程序A,另一个测试程序称为程序B,程序B的代码如下:

1 #include

2 #include

3

4 int main(int argc, const char *argv[])5 {6 sem_t *p_sem = (sem_t *)0xb7758000; /*该地址为p_sem的地址*/

7

8 sem_post(p_sem);9

10 return 0;11 }

注意上面程序传入的p_sem的值根据另一个程序打印出来的结果为准。在程序A的父进程等待信号量的时候执行程序B,程序A能继续执行下去吗,程序B的运行结果如下:

252fbc11e07b1901e6cc5206577e6537.png

段错误!

我想如果程序B能运行下去,并且让程序A成功获得信号量,那才怪事了,这正是虚拟地址存在的意义啊。这也解释了无名信号量在进程将的同步应该是父子进程的原因。

下面附上在父子进程中实现拉锯操作的代码:

1 #include

2 #include

3 #include

4 #include

5

6 int main(int argc, const char *argv[])7 {8 sem_t *p_sem =NULL;9 int pid = 0;10

11 p_sem = mmap(NULL, /*由系统分配地址*/

12 2 * sizeof(sem_t), /*申请的地址大小*/

13 PROT_READ | PROT_WRITE, /*读写权限*/

14 MAP_SHARED | MAP_ANON, /*进程共享,映射区与文件不关联*/

15 -1, /*文件描述符,匿名映射传入-1*/

16 0); /*偏移量*/

17

18 sem_init(&p_sem[0], 1, 0); /*进程间使用的信号量,初值为0*/

19 sem_init(&p_sem[1], 1, 1); /*进程间使用的信号量,初值为1*/

20

21 pid =fork();22

23 if (pid == 0) { /*子进程*/

24 while (1) {25 sem_wait(&p_sem[0]);26 printf("I'm child\n");27 sem_post(&p_sem[1]);28 }29 } else if (pid > 0) { /*父进程*/

30 while (1) {31 sem_wait(&p_sem[1]);32 printf("I'm parent\n");33 sem_post(&p_sem[0]);34 }35 }36

37 return 0;38 }

执行结果为交替打印“I'm parent”和“I'm child”,如果地18和第19行sem_init第二个参数传入0,那么结果会是只打印一遍“I'm parent”和“I'm child”。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值