Linux下多任务间通信和同步-System V信号量

来源:互联网 发布:偶得作诗软件 编辑:程序博客网 时间:2024/06/08 00:43

Linux下多任务间通信和同步-System V信号量

嵌入式开发交流群280352802,欢迎加入!

一.简介

信号量与其他进程间通信方式不大相同,它主要提供对进程间共享资源访问控制机制.相当于内存中的标志,进程可以根据它判定是否能够访问某些共享资源,同时,进程也可以修改该标志.出了用于访问控制外,还可用于进程同步.信号量有以下两种类型:

  1. 二值信号量:最简单的信号量形式,信号量的值只能取0或1;
  2. 计算信号量:信号量的值可以取任意非负值(当然受内核本身的约束).
操作信号量的方法与消息队列类似,主要包括三种类型:
  • 打开或创建信号量;
  • 增加或减少信号量的值;
  • 获得或设置信号量属性.
信号量同步的原理实际上就是操作系统中所用到的PV原语.一次P操作使信号量sem减1,而一次V操作使信号量sem加1.进程(或线程)根据信号量的值来判断是否对共享资源具有访问权限.当sem的值大于等于0时,该进程(或线程)具有公共资源的访问权限;相反,当sem的值小于0时,该进程(或线程)将阻塞直到sem的值大于等于0为止.
信号量有两组系统调用函数.一种叫做System V信号量常用于进程的同步;另一种来源于POSIX,常用于线程同步.两者非常相似,但它们使用的函数调用却各不相同.本文介绍System V信号量的系统调用,后续的博文中介绍POOIX信号量.这组信号量系统调用的名字都以"sem"开头,基本的系统调用有三个:semget,semopt和semctl.

二.semget系统调用

该函数调用返回与键值key相对应的信号量描述子。其原型如下:

#include <sys/types.h>#include <sys/ipc.h>#include <sys/sem.h>int semget(key_t key, int nsmes, intsemflg);
参数key:是标志一个信号量集的标示符,用法与msgget()中的key相同;
参数nsems:指定打开或者新创建的信号量集中将包含信号量的数目;
参数semflg:是一些标志位.如果key所表示的信号量存在,则semget指定了IPC_CREAT|IPC_EXCL标志,那么计数参数nsems与原来信号量的数目不等,返回的也是EEXIST错误;如果semget只指定了IPC_CREAT标志,那么参数nsems必须与原来的值一致.

三.semopt系统调用

该系统调用执行信号量集合上的操作数组,这是个原子操作.其原型:

#include <sys/types.h>#include <sys/ipc.h>#include <sys/sem.h>int semop(int semid, struct sembuf *sops,unsigned nsops);
Sembuf 结构如下:
struct sembuf{         unsigned  short       sem_num;        //对应信号集中的信号量,包括0         short                       sem_op;           short                       sem_flg;  //可取IPC_NOWIAT以及SEM_UNDO};
         sem_num对应信号集中的信号量,0对应第一个信号量.sgm_flag可取IPC_NOWAIT和SEM_UNDO.如果设置了SEM_UNDO标志,那么在进程结束时,相应的操作将被取消,这是比较重要的一个标志位.如果设置了该标志位,那么在进程没有释放共享资源就退出时,内核将代为释放.如果为一个信号量设置了该标志,内核都要分配一个sem_undo结构来记录它,为的是确保以后资源能够安全释放.
         事实上,如果进程退出了,那么它所占用就释放了,但信号量值却没有改变,此时,信号量值反应的已经不是资源占有的实际情况,在这种个情况下,问题的解决就靠内核来完成.这有点像僵尸进程,进程虽然退出了,资源也都释放了,但内核进程表中仍能然有它的记录,此时,就需要父进程调用waitpid来解决问题了.
         sem_op的值大于0,等于0以及小于0确定了对sem_num指定的信号量进行的三种操作.具体参考相应的man手册.
         这里需要强调的时semop同时操作多个信号量,在实际应用中,对应多种资源的申请或释放.semop保证操作的原子性,这一点尤为重要.尤其对于多种资源的申请来说,要么一次性获得所有资源,要么放弃申请,要么在不占用任何资源情况下继续等待,这样,一方面避免了资源的浪费,另一方面,避免了进程之间由于申请共享资源造成死锁.
         也许从实际含义上更好理解这些操作:信号量的当前值记录相应资源目前可用数目;sem_op>0对应相应进程要释放sem_op数目的共享资源;sem_op=0可以用于对共享资源是否已用完的测试;sem_op<0相当于进程要申请-sem_op个共享资源.再联想操作的原子性,更不难理解该系统调用合适正常返回,何时睡眠等待.

四.semctl系统调用

该系统调用实现对信号量的各种控制操作.其原型:

#include <sys/types.h>#include <sys/ipc.h>#include <sys/sem.h>int semctl(int semid, int semnum,int cmd,union semun arg);
semun联合的结构定义为:
union semun{         int  val;         struct semid_ds *buf;         unsigned short  *array;};
该系统调用详细信息请参见其手册,这里只给出参数cmd所能那个指定的操作:
  • IPC_STAT:获取信号量信息,信息由arg.buf返回;
  • IPC_SET:设置信号量信息,待设置信息保存在arg.buf中
  • GETALL:返回所有信号量的值,结果保存在arg.array中,参数sennnum被忽略;
  • GETNCNT:返回等待semnum所代表信号量的值增加的进程数,相当于目前有多少进程在等待semnum代表的信号量所代表的共享资源;
  • GETPID:返回最后一个对semnum所代表信号量执行semop操作的进程ID;成功时返回值为sempid;
  • GETVAL:返回semnum所代表信号量的值;成功返回值为semval;
  • GETZCNT:返回等待semnum所代表信号量的值变成0的进程数;成功时返回值为semzcnt;
  • SETALL:通过arg.array更新所有信号量的值;同时,更新与本信号集相关的semid_ds结构的sem_ctime成员;
  • SETVAL:设置semnum所代表信号量的值为arg.val.

五.System V信号量的应用实例

/**************************************************************************************//*简介:二进制信号量演示程序 *//*************************************************************************************/#include <unistd.h>#include <stdlib.h>#include <stdio.h>#include <sys/types.h>#include <sys/ipc.h>#include <sys/sem.h>static int set_semvalue(void);static void del_semvalue(void);static int semaphore_p(void);static int semaphore_v(void);static int sem_id;union semun{int val;struct semid_ds *buf;unsigned short *array;};int main(int argc, char *argv[]){    int i;    int pause_time;    char op_char = 'O';    srand((unsigned int)getpid());    sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);    if (argc > 1) {        if (!set_semvalue()) {            fprintf(stderr, "Failed to initialize semaphore\n");            return 1;        }        op_char = 'X';        sleep(2);    }/* 这是一个总共进出关键代码十次的循环语句。在每次循环的开始我们都要先做一次semaphore_p调用,程序将从此开始进人关键代码。 */    for(i = 0; i < 10; i++) {                if (!semaphore_p())             return 1;        printf("%c", op_char);        fflush(stdout);        pause_time = rand() % 3;        sleep(pause_time);        printf("%c", op_char);        fflush(stdout);/*经过一个随机等待时间之后,在进人下一次循环之前要先调用semaphore_v把信号量设置为可用状态。整个循环语句执行完毕后,我们发出del_semaphore调用对代码进行清理。*/        if (!semaphore_v())             return 1;        pause_time = rand() % 2;        sleep(pause_time);    }        printf("\n%d - finished\n", getpid());    if (argc > 1) {            sleep(10);        del_semvalue();    }   return 0;}/* set_semvalue函数通过一个带SETVAL命令的semcti调用初始化信号量。在使用信号量之前必须这样调用该函数*/static int set_semvalue(void){    union semun sem_union;    sem_union.val = 1;    if (semctl(sem_id, 0, SETVAL, sem_union) == -1) return(0);    return(1);}/*del_semvalue函数通过调用一个带IPC_RMID命令的semctl系统调用来删除那个信号量的标识码。*/static void del_semvalue(void){    union semun sem_union;    if (semctl(sem_id, 0, IPC_RMID, sem_union) == -1)        fprintf(stderr, "Failed to delete semaphore\n");}/* semaphore对信号量做”-1“操作(等待): */static int semaphore_p(void){    struct sembuf sem_b;    sem_b.sem_num = 0;    sem_b.sem_op = -1; /* P() */    sem_b.sem_flg = SEM_UNDO;    if (semop(sem_id, &sem_b, 1) == -1) {        fprintf(stderr, "semaphore_p failed\n");        return(0);    }    return(1);}/* semaphore_v把sembuf结构中的sem_op部分设置为"l",从而使信号量变得可用。*/static int semaphore_v(void){    struct sembuf sem_b;        sem_b.sem_num = 0;    sem_b.sem_op = 1; /* V() */    sem_b.sem_flg = SEM_UNDO;    if (semop(sem_id, &sem_b, 1) == -1) {        fprintf(stderr, "semaphore_v failed\n");        return(0);    }    return (1);}
这个简单的程序只允许每个进程只有一个二进制信号量,如果需要用到更多的信号量,可以通过传递信号量变量的方法进行扩展.

通过多次启动这个程序来进行测试.第一次启动时要加上一个参数,表示应用程序由它来负责创建和删除信号量的工作,而后面的程序就不需要程序.下面是一个执行的结果.


原创粉丝点击