有名信号量——无关进程间同步

1、概述

  上两篇博客中无名信号量可以用于线程间同步或者相关进程间同步,而有名信号量可以在无关进程间同步,因为有名信号量是将信号量存储在文件中,在不同的进程中打开相同的文件即可,有名信号量的文件都存储在/dev/shm目录下。

  无名信号量通过sem_init进行初始化,使用完之后用sem_destroy进行销毁,而有名信号量是通过sem_open创建或打开信号量,用sem_close关闭信号量,用sem_unlink销毁信号量。获取信号量:sem_wait、sem_trywait、sem_timeswait,释放信号量:sem_post,获取信号量的值:sem_getvalue这些函数与无名信号量的用法都是一样的。

2、函数介绍

2.1 sem_open

函数原型

sem_t *sem_open(const char *name, int oflag);

sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);

头文件 fcntl.h、sys/stat.h、semaphore.h
功能 创建/打开信号量
参数

[in]:name:信号量文件的名字。该名字可以在最前面加"/",在之后不能再加"/",但是无论加不加"/",

      指定的文件都是/dev/shm/sem.xxx。比如传入的name为"mysem",那么指定的是/dev/shm/sem.mysem。

[in]:oflag:打开的标志,与open一样,如果需要打开已经存在的信号量文件,则传入O_RDWR即可,不用再传入后两个参

      数。如果需要创建信号量,则传入O_CREAT,如果指定的文件不存在,则创建文件,由后两个参数指定文件权限

      和信号量的初值。如果指定的文件存在,则打开信号量文件,并忽略后两个参数。如果传入的是O_CREAT|O_EXCL

      那么会检查指定的文件是否存在,如果文件已经存在,那么sem_open会返回-1。

[in]:mode:同open,一般传入0666。

[in]:value:信号量初值。

返回 成功返回信号量地址,失败返回SEM_FAILED,其值为((void *)0)

2.2 sem_close

函数原型 int sem_close(sem_t *sem);
头文件 semaphore.h
功能 关闭信号量
参数 [in]:sem:信号量地址
返回 成功返回0,失败返回-1
说明 该函数应该与sem_open成对调用

2.3 sem_unlink

函数原型 int sem_unlink(const char *name);
功能 删除信号量文件。
头文件 semaphore.h
参数 [in]:name:同sem_open
返回 成功返回0,失败返回-1。

3、测试例程

3.1 正常测试例程

named_sem1.c

 1 /**
 2  * filename: named_sem1.c
 3  * author: Suzkfly
 4  * date: 2021-01-31
 5  * platform: Ubuntu
 6  *     配合named_sem2使用,先运行named_sem1,此时程序在第二次打印"sem_wait"时卡
 7  *     住,再另开一个终端,运行named_sem2,此时named_sem1会继续运行。
 8  *     编译时加-lpthread
 9  */
10 #include <stdio.h>
11 #include <fcntl.h>
12 #include <sys/stat.h>
13 #include <semaphore.h>
14 
15 #define SEM_NAME "mysem"    /* 定义信号量的名字 */
16 
17 int main(int argc, const char *argv[])
18 {
19     sem_t *p_sem = NULL;
20     int ret = 0;
21     int value = 0;
22     
23     /* 创建信号量 */
24     p_sem = sem_open(SEM_NAME, O_CREAT | O_EXCL, 0666, 1);  /* 信号量初值为1 */
25     printf("process 1 sem_open p_sem = %p\n", p_sem);
26     if (p_sem == SEM_FAILED) {
27         printf("process 1 sem_open failed\n");
28         return 0;
29     }
30     
31     /* 获取信号量的值 */
32     ret = sem_getvalue(p_sem, &value);
33     printf("process 1 sem_getvalue ret = %d\n", ret);
34     printf("value = %d\n", value);
35     
36     /* 获取信号量 */
37     printf("process 1 sem_wait\n");
38     ret = sem_wait(p_sem);
39     printf("process 1 sem_wait ret = %d\n", ret);
40     
41     /* 获取信号量 */
42     printf("process 1 sem_wait\n");
43     ret = sem_wait(p_sem);
44     printf("process 1 sem_wait ret = %d\n", ret);
45     
46     /* 关闭信号量 */
47     ret = sem_close(p_sem);
48     printf("process 1 sem_close ret = %d\n", ret);
49     
50     /* 删除信号量文件 */
51     ret = sem_unlink(SEM_NAME);
52     printf("process 1 sem_unlink ret = %d\n", ret);
53     
54     return 0;
55 }

named_sem2.c

 1 /**
 2  * filename: named_sem2.c
 3  * author: Suzkfly
 4  * date: 2021-01-31
 5  * platform: Ubuntu
 6  *     配合named_sem1使用,先运行named_sem1,此时程序在第二次打印"sem_wait"时卡
 7  *     住,再另开一个终端,运行named_sem2,此时named_sem1会继续运行。
 8  *     编译时加-lpthread
 9  */
10 #include <stdio.h>
11 #include <fcntl.h>
12 #include <sys/stat.h>
13 #include <semaphore.h>
14 
15 #define SEM_NAME "mysem"    /* 定义信号量的名字 */
16 
17 int main(int argc, const char *argv[])
18 {
19     int ret = 0;
20     sem_t *p_sem = NULL;
21     
22     /* 创建信号量 */
23     p_sem = sem_open(SEM_NAME, O_RDWR);
24     printf("process 2 sem_open p_sem = %p\n", p_sem);
25     if (p_sem == SEM_FAILED) {
26         printf("process 2 sem_open failed\n");
27         return 0;
28     }
29     
30     /* 释放信号量 */
31     ret = sem_post(p_sem);
32     printf("process 2 sem_post ret = %d\n", ret);
33     
34     /* 关闭信号量 */
35     ret = sem_close(p_sem);
36     printf("process 2 sem_close ret = %d\n", ret);
37     
38     return 0;
39 }

 

运行结果:

  先运行named_sem1,打印信息如下:

有名信号量——无关进程间同步

 

  此时named_sem1将在第二次打印“process 1 sem_wait”之后等待,此时使用命令ls /dev/shm查看,结果如下:

有名信号量——无关进程间同步

 

   可以看到在/dev/shm目录下有了sem.mysem这个文件。再运行named_sem2,此时named_sem1继续打印后面的消息,如下图:

有名信号量——无关进程间同步

 

   named_sem2打印信息如下图:

有名信号量——无关进程间同步

 

  此时/dev/shm/sem.mysem文件被删除。

 代码分析:

  在named_sem1中初始化信号量的值为1,之后获取了两次信号量,由于初值为1,因此第一次获取会立刻返回,第二次获取时由于信号量的值变为了0,因此阻塞等待。在named_sem2中释放了信号量,因此named_sem1能够继续运行。从代码可以看出这是一个很正常的测试用例,sem_open和sem_close是成对出现的,在信号量用完之后调用sem_unlink删除信号量文件,所有函数的返回值都为0,表明所有函数都没有出错。

3.2 非正常测试例程

  一个程序员的价值并不体现在他会什么东西,而是体现在他能解决什么问题,而解决问题的能力除了要养成好的解决问题的习惯以外还应该有丰富的经验,见识过各种各样的非正常用法。我在编写测试用例的过程中就想到很多非正常用法,我将想到的非正常用法都测试一遍,并将结果写出来。

3.2.1 关于sem_open的文件名

  1. 如果指定路径名为完整路径名,如“/dev/shm/sem.mysem”,那能不能打开文件?

  答:不能打开,sem_open将返回SEM_FAILED。传入的文件名只允许在最前面加“/”。

 

  2. 在两个程序中打开的信号量文件名可不可以一个加“/”,另一个不加?

  答:可以,比如程序1传入的文件名为"/mysem",程序2中传入的文件名为"mysem",实际上指的都是“/dev/shm/sem.mysem”

 

  3. 信号量文件名可不可以在前面多写几个"/"?

  答:可以,你可以传入的文件名为"/////////////////////mysem",实际上指的还是“/dev/shm/sem.mysem”

 

  4. 信号量文件名可不可以加"."?

  答:可以,比如传入的信号量文件名为"mysem.a.b.c",那么指定的文件就是"/dev/shm/sem.mysem.a.b.c"

3.2.2 关于sem_open的oflag

  1. man手册上说如果oflag指定了O_CREAT,那么第3和第4个参数必须要写,如果不写会怎么样?

  答:如果信号量文件不存在,sem_open会返回SEM_FAILED,如果文件存在,那么会正常打开信号量文件。

 

  2. 如果oflag为0会怎样?

  答:如果文件不存在则sem_open会返回SEM_FAILED,如果文件存在,那么根据指定的文件权限而定,文件权限请看3.2.3节。

3.2.3 关于sem_open的mode

  1. 解释一下该参数。

  答:该参数在创建文件时传入,用于指定文件的权限,通过ls -l命令查看,如下图:

有名信号量——无关进程间同步

 

   其中第1列中10个字符的意义可以参见https://www.cnblogs.com/jj81/p/9141271.html

  需要注意的是,即使传入的参数为0777(表示所有人都有读写和执行的权限),但其他用户还是没有写权限,如下图:

有名信号量——无关进程间同步

 

   虽然可执行的权限能够指定,但它作为信号量文件,去讨论它的可执行权限是没有意义的。

 

  2. 如果传入的mode有缺少的权限会怎么样?

  答:按照正常的理解,如果缺少读权限,那么将无法使用sem_wait和sem_getvalue,如果缺少写权限将无法使用sem_post,但事实上却不是这样的。真实情况是,创建该文件的程序对信号的操作与传入的mode无关,即使mode传入的是0,它也可以正常使用所有的函数,但是其他进程只要缺少读或写的其中一项,那么将无法打开文件。不过,创建好的(没有权限)的文件可以使用chmod命令让它变的有权限。

3.2.4 关于sem_open,sem_close和sem_unlink的调用顺序

  1. 如果sem_open和sem_close没有成对调用,能不能调用sem_unlink?

  答:网上很多人说sem_open和sem_close必须成对调用之后才能调用sem_unlink,但实际测试的时候发现sem_unlink这个函数只是用来删除磁盘上的信号量文件而已,它与sem_open和sem_close的调用次数没有关系。甚至是调用sem_unlink之后还能够正常调用其他函数(sem_wait,sem_post等),因为打开的文件是保存在内存里的,调用sem_wait和sem_post等函数操作的是内存中的内容。它也可以用来删除遗留的信号量文件。但是如果一个程序调用了sem_unlink删掉了信号量文件,此时其他的程序调用sem_open时将返回SEM_FAILED(磁盘中都没有这个文件了,那当然无法打开)。如果某个程序调用了sem_unlink,删掉了信号量文件,即使另一个程序又用sem_open创建了相同名字的信号量文件,此时在程序中操作的信号量是不同的信号量。

 

  2. sem_open和sem_close成对调用的意义是什么?

  答:在一个程序中,可以多次调用sem_open,也可以多次调用sem_close,只有当sem_open成功的次数大于sem_close成功的次数,该进程才可以使用信号量,否则会发生段错误。比如sem_open成功两次,sem_close成功1次,此时仍可以在程序中操作信号量,但是如果sem_open成功2次,sem_close也成功2次,那么再操作信号量则会发生段错误(这里的操作指的是sem_post、sem_wait等)。并且该机制只在同一个进程中有效,不能跨进程,比如进程1成功了3次sem_open,进程2成功了1次sem_open,但是进程2在调用第2次sem_close的时候会失败。

 

总结:

  1. 信号量文件的名字可以以n个"/"开头,多个进程打开同一个信号量文件写法可以不一样,比如进程1写"mysem",进程2写"////////mysem",指的都是/dev/shm/sem.mysem

  2. sem_open的oflag如果传入了O_CREAT,那么第3个参数mode和第4个参数value必须要传。如果mode不能同时有读写权限,那么其他程序打开该信号量文件时会失败,但是创建该文件的程序即使mode传入的是0,它也可以正常操作信号量。

  3. sem_unlink用来删除磁盘上的信号量文件,不会影响到已经打开的信号量文件,即使打开了文件之后立刻调用sem_unlink,程序也可以对信号量进行正常的操作。

  4. 在一个进程中sem_open成功的次数大于sem_close成功的次数时才可以进行信号量操作,否则会发生段错误。

  5. 打开一个已经存在的信号量文件,其实第2个参数oflag不需要传O_RDWR,传0都可以。

上一篇:Semaphore--控制并发线程数


下一篇:互联网理论即应用一课一文