1 Linux下的互斥信号量的使用
1)Linux下互斥信号量的作用
互斥信号量主要是用于访问共享资源时保证操作的原子性,即为一个整体的动作不允许被打断。
2)Linux下的文件操作函数的学习方式
man命令学习函数使用,写一个小代码,将函数用起来。
下面就带大家学习下互斥信号量相关的函数,然后用代码将这些函数串联起来,并用并行脚本进行一下验证。
2 Linux下互斥信号量相关的函数
1)ftok函数
ftok函数用于构造键值。
① 函数原型。
- key_t ftok( char * fname, int id )
② 头文件。
- include
-
- include
③ 参数。
fname:文件名在内核中的一种数字表示。
id:项目id号。
键值有fname和项目id号组合产生。
④ 返回值。
成功:返回产生的键值。
失败:-1。
2)semget函数
semget函数用于创建打开信号量。
① 函数原型。
- int semget(key_t key,int nsems,int semflg)
获取信号量集合的标示符。
当key所指定的信号量不存在的时候,并且semflg里包含了IPC_CREAT,这个时候,就会创建一个信号量集。
② 头文件。
- include
-
- include
-
- include
③ 参数。
key:键值。
semflay:标志,可以去IPC_CREAT,对应键值的信号量如果不存在还可以创建信号量。
nsems:创建的这个信号量集合里面包含的信号量数目。
④ 返回值。
成功:返回信号量集合的标示符。
失败:-1。
3)semctl函数
semctl函数在一个信号量集或集合中的单个信号量上执行各种控制操作。
① 函数原型。
- int semctl(int semid, int semnum, int cmd,...)
② 头文件。
- include
-
- include
③ 参数。
semid:要控制的信号量集合的标示符。
semnum:用于标识集合中的具体信号量。
cmd:指定了需执行的操作。
信号量参数枚举如下:
- union semun {
-
- int val; // SETVAL的值
- struct semid_ds *buf; // IPC_STAT, IPC_SET的缓冲
- unsigned short *array; // GETALL, SETALL的数值
- struct seminfo *__buf; // IPC_INFO的缓冲
-
- };
信号量集合结构体如下:
- struct semid_ds {
-
- struct ipc_perm sem_perm; // 权限
- time_t sem_otime; // 上次semop的时间
- time_t sem_ctime; // 上次修改的时间
- unsigned long sem_nsems; // 信号量集中信号量个数
- };
参数说明如下。
<1 常规控制操作.
加入下面参数进行操作都会忽略semnum参数。
IPC_RMID:立即删除信号量集及其关联的semid_ds数据结构。
IPC_STAT:在arg.buf指向的缓冲器中放置一份与这个信号量集相关联的semid_ds数据结构的副本。
IPC SET:使用arg.buf指向的缓冲器中的值来更新与这个信号量集相关联的semid_ds数据结构中选中的字段。
<2 获取和初始化信号量值。
下面的操作可以获取或初始化一个集合中的单个或所有信号量的值。获取一个信号量的值需具备在信号量上的读权限,而初始化该值则需要写权限。
GETVAL:semctl返回由semid指定的信号量集中第semmum个信号量的值。这个操作无需arg参数。
SETVAL:将由semid指定的信号量集中第semnum个信号量的值初始化arg.val。
GETALL:获取由semid指向的信号量集中所有信号量的值并将它们放arg.array指向的数组中。
SETALL:使用arg.array指向的数组中的值初始化semid指向的集合中的所有信号量。这个操作将忽略semnum参数。
注意GETVAL和GETALL返回的信息在调用进程使用它们时可能已经过期了。
<3 获取单个信号量的信息。
下面的操作返回semid引用的集合中第semnum个信号量的信息。所有这些操作都需要在信号量集合中具备读权限,并且无需arg参数。
GETPID:返回上一个在该信号量上执行semopO的进程的进程ID,这个值被称为sempid值。如果还没有进程在该信号量上执行过semopO,那么就返回0。
GETNCNT:返回当前等待该信号量的值增长的进程数,这个值被称为semncnt值。
GETZCNT:返回当前等待该信号量的值变成0的进程数;这个值被称为semzcnt值。
与上面介绍的GETVAL和GETALL操作一样,GETPID、GETNCNT以及GETZCNT操作返回的信息在调用进程使用它们时可能已经过期了。
④ 返回值。
成功:semctl返回的值取决于cmd,如下。
GETVAL:semval的值。
GETPID:sempid的值。
GETNCNT:semncnt的值。
GETZCNT:semzcnt的值。
其他参数:返回0。
否则,semctl返回-1,并设置errno以指示错误。
4)semop函数
semop函数用于操作信号量集合中的信号量。
① 函数原型。
- int semop(int semid, struct sembuf *sops, unsigned nsops)
② 头文件。
- include
-
- include
-
- include
③ 参数。
semid:要操作的信号量集合的标示符。
nsops:要操作多少个信号量。
sops:对信号量执行什么样的操作,执行什么操作由struct sembuf这一结构中量决定。
- struct sembuf{
-
- unsigned short sem_num; // 信号量的数量
- short sem_op; // 要执行的操作
- short semf1g; // 操作标志(IPC_NOMAIT和SEM_UNDO)
- }
当sem_op > 0时,将信号量的值加上sem_op的值。
其结果是:其他等待减小信号量值的进程可能会被唤醒并执行它们的操作。(需要写权限)
当sem_op < 0时,将信号量的值减去sem_op的值。
如果信号量的当前值大于或等于sem_op的绝对值,那么操作会立即结束。否则semop会阻塞直到信号量值增长到在执行操作之后不会导致出现负值的情况为止。(需要写权限)
当sem_op = 0时,就对信号量值进行检查以确定它当前是否等于0。如果等于0,那么操作将立即结束,否则semop就会阻塞直到信号量值变成0为止。(需要读权限)
④ 返回值。
成功:0。
失败:-1。
3 实例代码
下面用一个小程序用一下上面介绍的几个函数。
1)程序原理
首先,通过并行脚本同时运行程序,在不加入互斥信号量的时候,不应该被分开的程序会被打断(插入)。
接着,加入互斥信号量,此时并行程序每个程序都不会被另一个程序打断(插入)。
2)未加入信号量的情况
下面的头文件有些是不必要的,加入信号量需要全部的这些,为了省事,就不去了。
① unsem1.c。
- #include
- #include
- #include
- #include
- #include
-
- void main()
- {
-
- printf("\nThis is unsem1 start!\n");
- sleep(1); //打印完一条消息间隔会有
- printf("\nThis is unsem1 end!\n");
-
- }
② unsem2.c。
- #include
- #include
- #include
- #include
- #include
-
- void main()
- {
- printf("\nThis is unsem2!\n");
- }
③ 3个脚本文件。
- ### 脚本run.sh
- #!/bin/bash
- ./run1.sh&./run2.sh
-
- ### 脚本文件——run1.sh
- #!/bin/bash
- ./unsem1
-
- ### 脚本文件——run2.sh
- #!/bin/bash
- ./unsem2
-
即脚本run.sh运行run1.sh和run2.sh,&可以进行程序的并行运行。
脚本run1.sh运行unsem1.c编译处理的unsem1。
脚本run2.sh运行unsem2.c编译处理的unsem2。
运行结果如下:
因为是并行运行,所以两个程序不一定谁先运行,当unsem2先运行不影响unsem1,但当unsem1先运行时,unsem2的打印会插入到unsem1的两个打印中间。
程序中用sleep就是为了给插入的机会。
3)加入信号量的情况
下面的文件与上面的文件放到了不同的文件夹下,所以脚本名称是一样的并不影响。
① sem1.c。
- #include
- #include
- #include
- #include
-
- #include
- #include
- #include
- #include
-
- #define KEY 1234
-
- union semun
- {
- int val; // 信号量的值
- struct semid_ds *buf;
- unsigned short *arrry;
- };
-
- void main()
- {
-
- key_t key;
- int semid;
- struct sembuf sop;
- int ret;
-
-
- // 创建键值
- // key = ftok("./",1); //在当前目录可以创建出多个键值,此方法没用到
-
- //创建信号量
- semid = semget((key_t)KEY, 1, 0666 | IPC_CREAT); // 利用键值创建一个信号量
-
- union semun sem_union; // 定义给信号量赋值的结构并赋值
- sem_union.val = 1;
-
- ret = semctl(semid,0,SETVAL,sem_union); // 信号量的值设置为1
-
- // ret = semctl(semid,0,GETVAL); // 获得信号量的值,想要感受一下semctl可以放开这两个注释
- // printf("ret value is %d\n",ret);
-
- // 1 获取信号量
- sop.sem_num = 0;//操作第一个信号量,编号为0
- sop.sem_op = -1;//-1为获取信号量
- semop(semid,&sop,1);//由于定义的是变量,参数是指针所以取其地址
-
- // 2 打印起始消息
- printf("\nThis is sem1 start!\n");
-
- // 3 间隔一会
- sleep(1);
-
- // 4 打印结束消息
- printf("\nThis is sem1 end!\n");
-
- // 5 释放信号量
- sop.sem_num = 0;//操作第一个信号量,编号为0
- sop.sem_op = 1;//加1为释放信号量
- semop(semid,&sop,1);//由于定义的是变量,参数是指针所以取其地址
-
- }
-
② sem2.c。
- #include
- #include
- #include
- #include
-
- #include
- #include
- #include
- #include
-
- #define KEY 1234
-
- void main()
- {
-
- key_t key;
- int semid;
- struct sembuf sop;
- int ret;
-
- // 打开与sem1相同的信号量
- semid = semget((key_t)KEY, 1, 0666 | IPC_CREAT); // 如果已经有这个信号量了,就不会创建,就直接打开了
-
-
- ret = semctl(semid,0,GETVAL); // 获得信号量的值
- // printf("ret value is %d\n",ret);
-
-
- //获取信号量
- sop.sem_num = 0; // 操作第一个信号量,编号为0
- sop.sem_op = -1; // -1为获取信号量
- semop(semid,&sop,1); // 由于定义的是变量,参数是指针所以取其地址
-
- // 打印sem2的消息
- printf("\nThis is sem2!\n");
-
- //释放信号量
- sop.sem_num = 0; // 操作第一个信号量,编号为0
- sop.sem_op = 1; // 加1为释放信号量
- semop(semid,&sop,1); // 由于定义的是变量,参数是指针所以取其地址
- }
-
③ 3个脚本文件。
- ### 脚本run.sh
- #!/bin/bash
- ./run1.sh&./run2.sh
-
- ### 脚本文件——run1.sh
- #!/bin/bash
- ./sem1
-
- ### 脚本文件——run2.sh
- #!/bin/bash
- ./sem2
-
运行结果如下:
可以看到不管是sem1先运行还是sem2先运行,sem1的两个打印都不会被打断的。
提示:前面学了文件的操作,这里将终端打印作为共享的资源了,你也可以用操作同一个文件的方式去验证信号量的互斥性哈,去试试吧。
本文转载自微信公众号「嵌入式杂牌军」,可以通过以下二维码关注。转载本文请联系嵌入式杂牌军公众号。