一、信号量分类
(1)Posix有名信号量,可用于进程和同一进程不同线程间同步
(2)Posix基于内存的信号量,存放在共享内存区中,可用于进程和同一进程不同线程间同步
(3)System V信号量,在内核中维护,可用于进程和同一进程不同线程间同步。
二、信号量、互斥锁、条件变量的差异
(1)互斥锁必须总是由给他上锁的线程解锁,信号量的挂出(post)却不必由执行过他等待操作(wait)的线程执行。
(2)互斥锁要么被锁住要么被解锁。
(3)既然信号量有一个与之关联的状态(计数值)那么信号量的挂出操作总是被记住。然而当向一个条件变量发送信号时,如果没有线程等待在该条件变量上,那么该条件变量将丢失。
三、有名信号量
(1)由sem_open来创建一个新的信号量或打开一个已存在的信号量。其格式为:
sem_t *sem_open(const char *name,int oflag,mode_t mode,unsigned int value);
返回:若成功则为指向信号量的指针,若出错则为SEM_FAILED
其中,第三、四个参数可以没有,主要看第二个参数如何选取。
oflag参数:可以是0、O_CREAT或O_CREAT|O_EXCL。如果指定O_CREAT标志而没有指定O_EXCL,那么只有当所需的信号量尚未存在时才初始化它。但是如果所需的信号量已经存在也不会出错。 但是如果在所需的信号量存在的情况下指定O_CREAT|O_EXCL却会报错。mode参数:指定权限位。value参数:指定信号量的初始值。该初始值不能超过SEM_VALUE_MAX(这个常值必须至少为32767).二值信号量的初始值通常为1,计数信号量的初始值则往往大于1。用sem_close来关闭该信号量。
(2)使用sem_close来关闭信号量
int sem_close(sem_t* sem);
一个进程终止时系统对其上面已经打开的所有有名信号量执行这样的操作。
(3)使用sem_unlink删除信号量:
int sem_unlink(const char *name);
返回:成功返回0,出错返回-1
每个信号量都有一个引用计数器记录当前信号量打开的次数,当计数大于0时sem就能从系统中删除,只是其析构要等到最后一个被sem_close之后才发生。
(4)获取信号量的当前值:
int sem_getvalue(sem_t *sem,int *valp);
返回:成功返回0,出错返回-1
sem_getvalue在由valp指向的整数中返回所指定信号量的当前值。如果信号量当前已上锁,那么返回值或为0,或为某个负数,绝对值即为等待等待该信号量解锁的线程数。
(5)信号量的等待:(P操作,也称为递减down 或 上锁lock)
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
返回:成功返回0,出错返回-1
sem_wait函数测试所指定信号量的值,如果该值大于0,就将它的值减1并立即返回;如果该值等于0,调用线程就被投入睡眠中,直到该值变为大于0,这时再将它减1,函数随后返回。“测试并减1”操作必须是原子的。sem_wait和sem_trywait的差别是:当所指定信号量的值已经是0时,后者并不将调用的进程投入睡眠。相反,它返回一个EAGAIN错误。如果被某个信号中断,sem_wait就可能过早的返回,返回的错误为EINTR。
(6)信号量挂出(V操作,也称为递增up 或解锁unlock)
int sem_post(sem_t *sem);
返回:成功返回0,出错返回-1 将所指定的信号量值加1
互斥锁是为上锁而优化的,条件变量是为等待而优化的,信号量即可用于上锁也可用于等待,所以可能导致更多的开销和更高的复杂性。
四、Posix基于内存的信号量
Posix有名信号量创建时候是用一个name参数标识,它通常指代文件系统中的某个文件。而基于内存的信号量是由应用程序分配信号量的内存空间,即分配一个sem_t数据类型的内存空间,然后由系统初始化它们的值。操作函数如下:
#include
int sem_init(sem_t *sem, int pshared, unsigned int value); //初始化内存信号量int sem_destroy(sem_t *sem); //摧毁信号量
如果shared=0,那么待初始化的信号量是在同一进程的各个线程间共享的,否则该信号量是在进程间共享的,此时该信号量必须存放在某种类型的共享内存区中,使得用它的进程能够访问该共享内存区。value是该信号量的初始值。
五、信号量的限制
SEM_NSEMS_MAX:一个进程可以同时打开着的最大信号量个数
SEM_VALUE_MAX:一个信号量的最大值。
六、System V信号量
System V 信号量在内核中维护,其中包括二值信号量 、计数信号量、计数信号量集。
二值信号量 : 其值只有0、1 两种选择,0表示资源被锁,1表示资源可用;
计数信号量:其值在0 和某个限定值之间,不限定资源数只在0 1 之间;
计数信号量集 :多个信号量的集合组成信号量集内核维护的信号量集结构信息如下:定义在头文件
struct semid_ds { struct ipc_perm sem_perm; struct sem *sem_base; ushort sem_nsems; time_t sem_otime; time_t sem_ctime; };
其中ipc_perm 结构是内核给每个进程间通信对象维护的一个信息结构,其成员包含所有者用户id,所有者组id、创建者及其组id,以及访问模式等;semid_ds结构体中的sem结构是内核用于维护某个给定信号量的一组值的内部结构,其结构定义:
struct sem { int semval; int sempid; struct list_head sem_pending; };
其中senval变量代表当前信号量的值,sempid 为最后一个成功操作该信号量的进程id,该结构体在内核以双向链表进行 维护semid_ds结构体中的sem_nsems成员代表该信号量标示符的信号量个数主要函数介绍:
(1)创建一个信号量或访问一个已经存在的信号量集。
int semget(key_t key, int nsems, int semflg);
该函数执行成功返回信号量标示符,失败返回-1参数key是通过调用ftok函数得到的键值,nsems代表创建信号量的个数,如果只是访问而不创建则可以指定该参数为0,我们一旦创建了该信号量,就不能更改其信号量个数,只要你不删除该信号量,你就是重新调用该函数创建该键值的信号量,该函数只是返回以前创建的值,不会重新创建;semflg 指定该信号量的读写权限,当创建信号量时不许加IPC_CREAT ,若指定IPC_CREAT |IPC_EXCL则创建是存在该信号量,创建失败。
(2)打开一个信号量集后,对其中一个或多个信号量的操作。
int semop(int semid, struct sembuf *sops, unsigned nsops);
该函数执行成功返回0,失败返回-1;第一个参数semid 为信号量标示符;nops为第二个参数的操作数组的个数,第二个参数sops为一个结构体数组指针,结构体定义在sys/sem.h中,结构体如下
struct sembuf { unsigned short sem_num; short sem_op; short sem_flg; };
sem_num 操作信号的下标,其值可以为0 到nopssem_flg为该信号操作的标志:其值可以为0、IPC_NOWAIT 、 SEM_UNDO0 在对信号量的操作不能执行的情况下,该操作阻塞到可以执行为止;IPC_NOWAIT 在对信号量的操作不能执行的情况下,该操作立即返回;SEM_UNDO当操作的进程推出后,该进程对sem进行的操作将被取消;sem_op取值 >0 则信号量加上它的值,等价于进程释放信号量控制的资源sem_op取值 =0若没有设置IPC_NOWAIT, 那么调用进程将进入睡眠状态,直到信号量的值为0,否则进程直接返回sem_op取值 <0则信号量加上它的值,等价于进程申请信号量控制的资源,若进程设置IPC_NOWAIT则进程再没有可用资源情况下,进程阻塞,否则直接返回。
(3)对信号量执行各种控制操作。
int semctl(int semid, int semnum, int cmd, ...);
该函数执行成功返回非负值,失败返回-1参数semid为信号集的标识符,参数 semnum标识一个特定信号,该参数仅用于 SETVAL、GETVAL、GETPID命令cmd控制类型,...说明函数参数是可选的,通过该共用体变量semun选择操作参数,各字段如下:
union semun { int val; struct semid_ds __user *buf; unsigned short __user *array; struct seminfo __user *__buf; void __user *__pad; };
IPC_STAT读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中。IPC_SET设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf参数。IPC_RMID将信号量集从系统中删除GETALL用于读取信号量集中的所有信号量的值,存于semnu的array中SETALL 设置所指定的信号量集的每个成员semval的值GETPID返回最后一个执行semop操作的进程的PID。LSETVAL把的val数据成员设置为当前资源数GETVAL把semval中的当前值作为函数的返回,即现有的资源数,返回值为非负数。