信号灯

信号灯(semaphore)是一种用于提供不同进程间或一个给定进程的不同线程的同步手段的原语。

 

信号灯分类

Posix有名信号灯:使用Posix IPC名字标识,可用于进程或线程间的同步

Posix基于内存的信号灯:存放在共享内存区,可用于进程或线程间的同步(用户进程间同步时,信号灯需存放在共享内存区域,且其生命周期到共享内存释放为止)
System V信号灯:在内核中维护,可用于进程或线程间的同步

信号灯实现

Posix 有名信号灯,有名信号灯可能与文件系统中路径的名字来标识的。我们在使用Posix有名信号灯是,可以使用ll /dev/shm查看是否成功打开了信号灯。

 

Posix 基于内存的信号灯,基于共享内存的信号灯和有名信号灯虽然实现机制不一样,但在编程中仅有创建和销毁两个函数不一样。

 

System V信号灯:

 

 

POSIX有名信号灯

函数说明

#include <fcntl.h>           /* For O_* constants */

#include <sys/stat.h>        /* For mode constants */

#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);// 用来创建信号灯

int sem_wait(sem_t *sem);// 获得信号灯,得不到就阻塞;如果获得信号灯,信号灯数量减1

int sem_trywait(sem_t *sem);// 尝试获得,得不到返回失败,errno设置为EAGAIN

int sem_getvalue(sem_t *sem, int *sval);// sem_getvalue返回指定信号灯的当前值,如果该信号灯已上锁,那么返回或为0,或为某个负数,其绝对值就是等待该信号灯解锁的线程数。

int sem_post(sem_t *sem);// 释放信号灯,信号数量加1

int sem_unlink(const char *name);// 删除以name命名的信号灯,只有当系统中所有使用该信号灯的进程都释放,才会真的删除

 

POSIX基于内存的信号灯

POSIX基于内存的信号灯的sem_wait和sem_post和POSIX有名信号灯是同一个实现,唯一不同在于构造和析构是在内存中进行的,而不是基于文件系统的某个路径名。

在sem_init函数中,如果shared为0,那么待初始化的信号灯是在同一进程的各个线程间共享的,否则该信号灯是在进程间共享的。当shared非0时,该信号灯必须存放在即将使用他的所有进程都能访问的某种类型的共享内存区中。

基于内存的信号灯的持续性由它所在的内存持续性决定。

函数说明

int sem_init(sem_t *sem, int pshared, unsigned int value);// 初始化一个信号量

int sem_destory(sem_t *sem)// 释放信号量

 

System V信号灯

函数说明

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

// 创建一个新的信号量或是获得一个已存在的信号量键值

int semget(key_t key, int nsems, int semflg);

key:所创建或打开信号量集的键值。需要是唯一的非零整数。

nsems:创建的信号量集中的信号量的个数,该参数只在创建信号量集时有效。

flag:调用函数的操作类型,也可用于设置信号量集的访问权限。

 

// 用来改变信号量的值(包含P操作和V操作)

struct  sembuf{
    unsigned short sem_num;  /* semaphore number *//*信号灯在信号灯集中的编号*/
    short          sem_op;   /* semaphore operation *//*P操作或者V操作*/
    short          sem_flg;  /* operation flags */
};

int semop(int semid, struct sembuf *sops, unsigned nsops);

int semtimedop(int semid, struct sembuf *sops, unsigned nsops, struct timespec *timeout);

sem_num:是相对应的信号量集中的某一个资源,所以其值是一个从0到相应的信号量集的资源总数(ipc_perm.sem_nsems)之间的整数。除非使用一组信号灯了,否则它的取值一般为0。

sem_op:是信号量在一次操作中需要改变的数值。通常只会用到两个值:-1---P操作,1---V操作。

sem_flg:说明函数semop的行为。通常被设置为SEM_UNDO。它将使得操作系统跟着当前进程对这个信号量的修改情况,如果这个进程在没有释放该信号量的情况下终止,操作系统将自动释放该进程持有的信号量。用一个通俗的说法:IPC_UNDO标志保证进程终止后,它对信号量的修改都撤销,好像它从来没有操作过信号量一样。这个标志要特别注意,使用不当容易造成一些诡异的问题。

这里需要强调的是semop同时操作多个信号灯,在实际应用中,对应多种资源的申请或释放。semop保证操作的原子性,这一点尤为重要。尤其对于多种资源的申请来说,要么一次性获得所有资源,要么放弃申请,要么在不占有任何资源情况下继续等待,这样,一方面避免了资源的浪费;另一方面,避免了进程之间由于申请共享资源造成死锁。关于这一点,可以参考http://www.cnblogs.com/linuxbug/p/4840148.html里面的银行家算法,semop就是银行家算法的一个实现。

也许从实际含义上更好理解这些操作:信号灯的当前值记录相应资源目前可用数目;sem_op > 0对应相应进程要释放sem_op数目的共享资源;sem_op=0可以用于对共享资源是否已用完的测试;sem_op<0相当于进程要申请-sem_op个共享资源。再联想操作的原子性,更不难理解该系统调用何时正常返回,何时睡眠等待。

 

// 允许信号量信息的直接控制(包含初始化信号灯和删除信号灯)

// 这个联合体需要在程序声明,用于semctl函数的SETVAL选项的传值,作为第四个参数
union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
}

int semctl(int semid, int semnum, int cmd, …/*union semun arg*/);

 

IPC_STAT

获取信号灯信息,信息由arg.buf返回;

IPC_SET

设置信号灯信息,待设置信息保存在arg.buf中(在manpage中给出了可以设置哪些信息);

GETALL

返回所有信号灯的值,结果保存在arg.array中,参数sennum被忽略;

GETNCNT

返回等待semnum所代表信号灯的值增加的进程数,相当于目前有多少进程在等待semnum代表的信号灯所代表的共享资源;

GETPID

返回最后一个对semnum所代表信号灯执行semop操作的进程ID;

GETVAL

返回semnum所代表信号灯的值;

GETZCNT

返回等待semnum所代表信号灯的值变成0的进程数;

SETALL

通过arg.array更新所有信号灯的值;同时,更新与本信号集相关的semid_ds结构的sem_ctime成员;

SETVAL

设置semnum所代表信号灯的值为arg.val;

posted @ 2019-07-02 14:46  soqu36  阅读(311)  评论(0)    收藏  举报