目录
1、信号量
1.1、Posix信号量的选择
1.2、基于内存的信号量的持续性
2、信号量分类
3、特性:
4、Posix信号量
4.1、sem_open()函数
4.2、sem_close()函数
4.3、sem_unlink()函数
4.4、sem_wait()函数
4.5、sem_trywait()函数
4.6、sem_post()函数
4.7、sem_getvalue()函数
4.8、sem_init()函数
4.9、sem_destroy()函数
4.10、Posix信号量限制
5、System V信号量
5.1、System V特点
5.2、缺陷
5.3、smeget()函数
5.4、semop()函数
5.5、semctl()函数
1、信号量
信号量是一种用于提供不同进程间或一个从给定进程的不同线程间同步手段的原语。
| Posix有名信号量 | 使用Posix IPC名字标识 |
| Posix基于内存的信号量 | 存放在共享内存区 |
| System V信号量 | 在内核中维护 |
1.1、Posix信号量的选择
1)单个进程的各个线程共享,可以使用基于内存的信号量。
2)彼此无亲缘关系的不同进程需使用信号量时,通常使用有名信号量。
1.2、基于内存的信号量的持续性
1)如果某个基于内存的信号量是由单个进程的各个线程共享的(sem_init的shared的参数为0),那么该信号量具有随进程的持续性,当该进程终止时它也消失。
2)如果某个基于内存的信号量是在不同进程间共享的(sem_init的shared的参数为1),那么该信号量必须存放在共享内存区中,因而只要该共享内存区任然存在,该信号量也就继续存在。
2、信号量分类
| Posix | 二值信号量 | 其值为0或1的信号量 |
| 计数信号量 | 其值在 0和某个限制值 | |
| System V | 计数信号量集 | 一个或多个信号量(构成一个集合),其中每个都是计数信号量。 |
注:当谈论System V信号量时,所指的是计数信号量集。当谈论Posix信号量时,所指的是单个计数信号量。
3、特性:
1)互斥锁必须总是由锁住它的线程解锁,信号量的挂出却不必由执行过它的等待操作的同一线程执行。
2)信号量有一个与之关联的状态(计数值),信号量的挂出操作总是被记住。向一个条件变量发送信号时,如果没有线程等待在该条件变量上,那么该信号将丢失(即如果某个线程调用pthread_cond_signal,不过当时没有任何线程阻塞在pthread_cond_wait调用中,那么发往相应条件变量的信号将丢失)。
3)当持有某个信号量锁的进程没有释放该锁就终止时,内核没有自动挂出该信号量。这与记录锁不一样,当持有某个记录锁的进程没有释放它就终止时,内核会自动释放。
4、Posix信号量
4.1、sem_open()函数
创建一个新的有名信号量或打开一个已存在的有名信号量。
注:sem_open不需要类似于shared的参数或类似于PTHREAD_PROCESS_SHARED的属性,因为有名信号量总是可以在不同进程间共享。
#include#include #include sem_t *sem_open(const char *name, int oflag); sem_t *sem_open(const char *name, int oflag,mode_t mode, unsigned int value);
参数 name:有名信号量名字。
参数 oflag:0、O_CREAT或O_CREAT|O_EXCL。
参数 mode:指定权限位(指定O_CREAT时才需要)。
参数 value:指定信号量的初值(指定O_CREAT时才需要)。
返回值:若成功则为指向信号量的指针,若出错则为SEM_FAILED。
4.2、sem_close()函数
关闭由sem_open()打开的有名信号量。
#includeint sem_close(sem_t *sem);
参数 sem:要关闭的有名信号量。
返回值:若成功则为0,若出错则为-1.
注:关闭一个信号量并没有将它从系统中删除。即使当前没有进程打开着某个信号量,他的值仍然保持。
4.3、sem_unlink()函数
从系统中删除有名信号量。
#includeint sem_unlink(const char *name);
参数 name:有名信号量名字
返回值:若成功则为0,若出错则为-1.
注:当引用计数还大于0时,name就能从文件系统中删除,然而其信号量的析构(不同于将它的名字从文件系统删除)却要等最后一个sem_close发生为止。
4.4、sem_wait()函数
等待信号量,如果该值大于0,那就将它减1并立即返回。如果该值等于0,调用线程就被投入睡眠,知道该值变为大于0,此时再将它减1,函数随后返回。
#includeint sem_wait(sem_t *sem);
参数 sem:等待的信号量。
返回值:若成功则为0,若出错则为-1.
4.5、sem_trywait()函数
当所指定信号量为0时,不将调用线程投入睡眠。而是返回一个EAGAIN错误。
#includeint sem_trywait(sem_t *sem);
参数 sem:等待的信号量。
返回值:若成功则为0,若出错则为-1.
注:如果被某个信号中断,sem_wait就可能过早的返回错误EINTR。
4.6、sem_post()函数
把所指定的信号量加1,然后唤醒正在等待该信号量变为正数的任意线程。
#includeint sem_post(sem_t *sem);
参数 sem:等待的信号量。
返回值:若成功则为0,若出错则为-1.
注:在各种各样的同步技巧中(互斥锁、条件变量、读写锁、信号量)中、能够从信号处理程序中安全调用的唯一函数时sem_post。
4.7、sem_getvalue()函数
#include
int sem_getvalue(sem_t *sem, int *sval);
参数 sem:等待的信号量。
参数 savl:保存所指定信号量的当前值。如果该信号量当前已上锁,那么返回或为0, 或为某个负数(其决定值就是等待该信号量解锁的线程数)
返回值:若成功则为0,若出错则为-1。
4.8、sem_init()函数
基于内存的信号量初始化。
#includeint sem_init(sem_t *sem, int pshared, unsigned int value);
参数 sem:要初始化的内存信号量。
参数 shared:如果为0,那么待初始化的信号量是在同一进程的各个线程间共享的,否则该信号量是在进程间共享的。
注:当shared为非0时,该信号量必须存放在某种类型的共享内存中,而即将使用它的所有进程都要能访问该共享内存区。
参数 value:信号量的初始值。
返回值:若出错则为-1。
4.9、sem_destroy()函数
摧毁基于内存的信号量。
#includeint sem_destroy(sem_t *sem);
参数 sem:要摧毁的内存信号量。
返回值:若成功则为0,若出错则为-1.
4.10、Posix信号量限制
| 名称 | 说明 | 获取方法 |
| SEM_NSEMS_MAX | 一个进程可同时打开着的最大信号量数 | sysconf(_SC_SEM_NSEMS_MAX) |
| SEM_VALUE_MAX | 一个信号量的最大值 | sysconf(_SC_SEM_VALUE_MAX) |
5、System V信号量
5.1、System V特点
| 1 | 创建一个System V信号量集需要技巧,因为创建该集合并随后初始化其各个值需要两个操作,从而可能导致竞争状态 |
| 2 | System V信号量当指定应用到某个信号量集的一组信号量操作是,那么所有操作都执行,要么所有操作都不执行。 |
| 3 | System V信号量提供“复旧”特性,该特性保证在进程终止时逆转某个信号量操作。 |
| 4 | Posix信号量只允许-1(sem_wait)和+1(sem_post)这两个操作。System V信号量允许信号量的值增长或减少不光是1,而且允许等待信号量的值变为0. |
对于系统的每个信号集,内核维护一个semid_ds结构体。
#includestruct semid_ds { struct ipc_perm sem_perm; struct sem *sem_base; ushort sem_nsems; time_t sem_otime; time_t sem_ctime; }; struct sem{ unsigned short semval; pid_t sempid; unsigned short semncnt; unsigned short semzcnt; }; struct ipc_perm { uid_t uid; gid_t gid; uid_t cuid; gid_t cgid; unsigned short mode; unsignedshort seq; key_t key; };
注:sem_base含有指向某个sem结构数组的指针。当前信号量集中的每个信号量对应其中一个数组元素。
5.2、缺陷
在System V信号量的设计中,创建一个信号量集(semget)并将它初始化(semctl)需要两次系统调用。
一个不完备的解决方法是:在调用semget时指定IPC_CREAT|IPC_EXCL标志,这样只有一个进程(首先调用semget的那个进程)创建所需的信号量,该进程随后初始化该信号量,其他进程会收到来自semget的一个EEXIST错误,于是再次调用semget,不过这次调用既不指定IPC_CREAT标志也不指定IPC_EXCL标志。
上述问题不完备的点在于,首先调用semget的那个进程在调用semctl初始化信号量集前,内核有可能在这2个步骤之间把上下文切换到另一个进程。而新切换的进程可能使用该信号量,但是信号量的值尚未被初始化!!!
绕过上述竞争状态的方法:当semget创建一个新的信号量集时,其semid_ds结构的sem_otime成员保证被置为0,该成员只是在semop调用成功时才被设置为当前值。因此别的进程再次成功的调用semget后,必须以IPC_STAT命令调用semctl。然后等待sem_otime变为非0值,到时就可断定该信号量已被初始化,而且对它进行初始化的那个进程已成功调用semop。这意味着创建该信号量的那个进程必须初始化它的值,而且必须在任何其他进程可以使用该信号量前调用semop.
注:Posix有名信号量通过让单个函数(sem_open)创建并初始化信号量来避免上述问题。而且即使指定O_CREATE标志,信号量也只是在尚未存在的前提下才被初始化。
5.3、smeget()函数
创建一个信号量集或访问一个已存在的信号量集。
注:一旦创建完差一个信号量集,就不能改变其中的信号量数。
#includeint semget(key_t key,int nsems,int oflag);
参数 key:IPC键值。
参数 nsems:指定集合中信号量数。如果不创建一个新信号量集,而只是访问一个已存在的集合,那就可以把参数指定为0。
参数 oflag:SEM_R或SEM_A的组合(R代表读-read,A代表改-alter)。可以与IPC_CREAT或IPC_CREAT|IPC_EXCL按位或。
| 数字值(八进制) | 信号量 | 说明 |
| 0400 | SEM_R | 由用户(属主)读 |
| 0200 | SEM_W | 由用户(属主)写 |
| 0040 | SEM_R>>3 | 由(属)组成员读 |
| 0020 | SEM_W>>3 | 由(属)组成员写 |
| 0004 | SEM_R>>6 | 由其他用户读 |
| 0002 | SEM_W>>6 | 由其他用户写 |
返回值:若成功则为非负信号量标识符,若出错则为-1。
当实际操作为创建一个新的信号量集时,相应的semid_ds结构的以下成员将被初始化。
1)sem_perm结构的uid和cuid成员被置位调用进程的有效用户ID,gid和cgid成员被置为调用进程的有效ID。
2) oflag参数中的读写权限位存入sem_perm.mode。
3) sem_otime被置为0,sem_ctime则被置为当前时间。
4) sem_nsems被置为nsems参数的值。
5)与该集合中每个信号量关联的各个sem结构并不初始化。由SET_VAL或SETALL命令调用semctl时初始化
5.4、semop()函数
对信号量进行操作。
注:semadj是指定信号量针对调用进程的调整值。只有在对于本操作的sembuf结构的sem_flg成员中指定SEM_UNDO标志后,semadj才会更新。这是一个概念性的变量,它由内核为在其某个信号量操作中指定了SEM_UNDO标志的各个进程维护,不必存在名为semadj的变量。
#includeint semop(int semid,struct sembuf* opsptr,size_t nops);
参数 semid:信号量标识符
参数 opsptr:struct sembuf结构体指针。
struct sembuf {
short sem_num;
short sem_op;
short sem_flg;
}
注:由内核保证传递给semop函数的操作数组(opsptr)被原子的执行。这意味着要么所有操作都被执行,要么一个操作也不执行。
参数 nops:要操作的sembuf 结构体中元素的数量。
返回值:若成功则为0,若出错则为-1。
调用进程终止时,semadj加到相应信号量的semval之上。要是调用进程对信号量的全部操作都指定SEM_UNDO标志,那么该进程终止时,该信号量的值就会变得根本没有运行过该进程一样,这就是复旧(undo)的本意。
| sem_op操作 | 说明 |
| >0 | 其值加到semval上。这对应释放由某个信号量控制的资源。 如果指定了SEM_UNDO标志,那就从相应信号量的semadj值减掉sem_op的值。 |
| =0 | 表示调用者希望等待到semval变为0。如果已经是0,则立刻返回。 如果semval不为0,相应信号量的semzcnt值就加1,调用线程则被阻塞到semval变为0(这时,相应的信号量的semzcnt再减1)。 如果指定了IPC_NOWAIT标志,调用线程就不会被投入睡眠,而是将返回EAGAIN错误。 注:如果某个被捕获的信号中断了引起睡眠的semop函数,那么函数将返回EINTR错误;如果相应的信号量被删除了,那么函数返回EIDRM错误。 |
| <0 | 表示调用者希望等待到semval变为大于或等于sem_op绝对值。这对应于分配资源。 如果semval大于或等于sem_op的绝对值,那就从semval中减掉sem_op的绝对值(如果指定了SEM_UNDO标志,那么sem_op的绝对值就加到相应信号量的semadj值上)。 如果semval小于sem_op的绝对值,相应信号量的semcnt值就加1,调用线程则被阻塞到semval变为大于或等于sem_op的绝对值。此时该线程将取消阻塞,并将从semval中减掉sem_op的绝对值,相应信号量的semncnt将减1(如果指定了SEM_UNDO标志,那么sem_op的绝对值就加到相应信号量的semadj值上) 如果指定了IPC_NOWAIT标志,调用线程就不会被投入睡眠,而是将返回EAGAIN错误。 注:如果某个被捕获的信号中断了引起睡眠的semop函数,那么函数将返回EINTR错误;如果相应的信号量被删除了,那么函数返回EIDRM错误。 |
5.5、semctl()函数
对信号量执行各种控制操作。
#includeint semctl(int semid,int semnum,int cmd, ... );
参数 semid:标识其操作待控制的信号量集
参数 semnum:标识该信号集内的特定成员,0到nsem-1(nsem为semget创建信号量集时的第二个参数)。仅用于GETVAL、SETVAL、GETPID、GETNCNT和GETZCNT命令
参数 cmd:
| GETVAL | 把semval值当作函数返回值返回。 |
| SETVAL | 设置semval值。如果操作成功,那么相应信号量在所有进程中的信号量调整值(semadj)将被置为0. |
| GETPID | 把sempid的当前值作为函数返回值 |
| GETNCNT | 把semccnt的当前值作为函数返回值 |
| GETZCNT | 把semzcnt的当前值作为函数返回值 |
| GETALL | 返回所指定信号集内每个成员的semval值。通过arg.array指针返回,函数本身返回0。 |
| SETALL | 设置所指定信号集内每个成员的semval值。这些值通过arg.array指针指定。 |
| IPC_RMID | 把由semid指定的信号量集从系统中删除。 |
| IPC_SET | 设置所指定信号集的semid_ds结构中的以下3个成员:sem_perm.uid、sem_perm.gid和sem_perm.mode,这些值来自arg.buf参数指向的结构中的对应成员。semid_ds结构中的sem_ctime成员也被设置为当前时间。 |
| IPC_STAT | 通过arg.buf参数返回所指定信号集当前的semid_ds结构。 |
参数 arg:可选,取决于第三个参数cmde。
union semun{
int val;
struct semid_ds *buf;
ushort *array
}
注:这个联合并没有出现在任何系统头文件中,因而必须由应用程序声明。
返回值:若成功则为非负值,若出错则为-1。



