System V信号量和POSIX信号量

一、函数上的区别

信号量有两种实现:传统的System V信号量和新的POSIX信号量。它们所提供的函数很容易被区分:对于所有System V信号量函数,在它们的名字里面没有下划线。例如,应该是semget()而不是sem_get()。然而,所有的的POSIX信号量函数都有一个下划线。下面列出了它们提供的所有函数清单:

Systm V POSIX
semctl() sem_getvalue()
semget() sem_post()
semop() sem_timedwait()
sem_trywait()
sem_wait()
sem_destroy()
sem_init()
sem_close()
sem_open()
sem_unlink()

二、使用上的区别

1、XSI system V的信号量是信号量集,可以包括多个信号灯(有个数组),每个操作可以同时操作多个信号灯
posix是单个信号灯,POSIX有名信号灯支持进程间通信,无名信号灯放在共享内存中时可以用于进程间通信。
2、POSIX信号量在有些平台并没有被实现,比如:SUSE8,而SYSTEM V大多数LINUX/UNIX都已经实现。两者都可以用于进程和线程间通信。但一般来说,system v信号量用于 进程间同步、有名信号灯既可用于线程间的同步,又可以用于进程间的同步、posix无名用于同一个进程的不同线程间,如果无名信号量要用于进程间同步,信号量要放在共享内存中。
3、POSIX有两种类型的信号量,有名信号量和无名信号量。有名信号量像system v信号量一样由一个名字标识。
4、POSIX通过sem_open单一的调用就完成了信号量的创建、初始化和权限的设置,而system v要两步。也就是说posix 信号是多线程,多进程安全的,而system v不是,可能会出现问题。
5、system V信号量通过一个int类型的值来标识自己(类似于调用open()返回的fd),而sem_open函数返回sem_t类型(长整形)作为posix信号量的标识值。
6、对于System V信号量你可以控制每次自增或是自减的信号量计数,而在Posix里面,信号量计数每次只能自增或是自减1。
7、Posix无名信号量提供一种非常驻的信号量机制。
8、相关进程: 如果进程是从一已经存在的进程创建,并最终操作这个创建进程的资源,那么这些进程被称为相关的。

三、注意事项

1、Posix有名信号灯的值是随内核持续的。也就是说,一个进程创建了一个信号灯,这个进程结束后,这个信号灯还存在,并且信号灯的值也不会改变。当持有某个信号灯锁的进程没有释放它就终止时,内核并不给该信号灯解锁
2、posix有名信号灯是通过内核持续的,一个进程创建一个信号灯,另外的进程可以通过该信号灯的外部名(创建信号灯使用的文件名)来访问它。posix基于内存的无名信号灯的持续性却是不定的,如果基于内存的信号灯是由单个进程内的各个线程共享的,那么该信号灯就是随进程持续的,当该进程终止时它也会消失。如果某个基于内存的信号灯是在不同进程间同步的,该信号灯必须存放在共享内存区中,这要只要该共享内存区存在,该信号灯就存在。

四、总结

1、System V的信号量一般用于进程同步, 且是内核持续的, api为:semget、semctl、semop
2、Posix的有名信号量一般用于进程同步, 有名信号量是内核持续的. 有名信号量的api为:sem_open、sem_close、sem_unlink
3、Posix的无名信号量一般用于线程同步, 无名信号量是进程持续的, 无名信号量的api为:sem_init、sem_destroy

System V信号量的使用

创建或打开信号量:

int semget(key_t key,int nsems,int flags)

  • key: key:一个整型值对应内核中一个信号量对象,可以自己指定,不同信号量的key值不一样。不相关的进程可以通过它访问同一个信号量。程序对所有信号量的访问都是间接的,它先提供一个键,再由系统生成一个响应的信号标识符。
  • nsems:信号量集中信号量的个数
  • sem_flags:设置一组标志,与open函数的标志非常相似,包括信号量的权限等。IPC_CREAT标志是创建或者使用已有的信号量。而IPC_CREATE和IPC_EXCL结合使用可以确保创建出的是一个新的、唯一的信号量,如果该信号量已存在,它将返回一个错误。创建时给出的权限可以是:0600。

具体代码:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <sys/ipc.h>
  4. #include <sys/sem.h>
  5. int main() {
  6. int id = semget(123, 1, IPC_CREAT | 0644);
  7. if (id == -1) {
  8. perror("id");
  9. exit(1);
  10. }
  11. printf("semget success!\n");
  12. }

验证是否创建成功:ipcs -s,其中的nsems表示信号量集中有多少个信号量

设置信号量初值:

semctl(int semid,int semnum,int cmd,su)

  • sem_id:信号量id。
  • sem_num:信号量的下标,从0开始。
  • command:具体的操作命令,有SETVAL(设置初值)、IPC_RMID(移除信号量)

最后一个参数可以有也可以没有,如果有的话。它将会是一个union semun结构,包含以下几个成员:

  1. union semun
  2. {
  3. int val;
  4. struct semid_ds *buf;
  5. usigned short *array;
  6. }

一般只使用val这个成员,来为信号量赋初值。当信号量值为0时,进程会阻塞运行。
具体代码:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <sys/ipc.h>
  4. #include <sys/sem.h>
  5. union semun {
  6. int val;
  7. };
  8. int main() {
  9. int id = semget(123, 0, 0);
  10. if (id == -1) {
  11. perror("id");
  12. exit(1);
  13. }
  14. union semun su;
  15. su.val = 5;
  16. semctl(id, 0, SETVAL, su);
  17. }

查看信号量的值:

int semctl (int semid,int semnum,int cmd,0)

  • semnum:信号量集中的第几个信号量
  • cmd:命令,这里为GETVAL
  • 0:无需再重复定义联合体
  • 返回值为当前信号量的值

具体代码:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <sys/ipc.h>
  4. #include <sys/sem.h>
  5. int main() {
  6. int id = semget(123, 0, 0);
  7. if (id == -1) {
  8. perror("id");
  9. exit(1);
  10. }
  11. int val = semctl(id, 0, GETVAL, 0);
  12. printf("val=%d", val);
  13. }

PV操作:

semop (int id,struct sembuf sb[],int len)

  • len:数组的长度

    1. struct sembuf
    2. short sem_num, //信号量的下标
    3. short sem_op, // 1表示V操作,-1表示P操作
    4. //一般填0,表示如果信号量为0就阻塞,
    5. //IPC_NOWAIT 对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息。
    6. //SEM_UNDO 程序结束时(不论正常或不正常),保证信号值会被重设为semop()调用前的值。
    7. //这样做的目的在于避免程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定。
    8. short sem_flg
    9. };

P操作具体代码:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <sys/ipc.h>
  4. #include <sys/sem.h>
  5. void p(int id) {
  6. struct sembuf sb[1];
  7. sb[0].sem_num = 0;
  8. sb[0].sem_op = -1;
  9. sb[0].sem_flg = 0;
  10. semop(id, sb, 1);
  11. }
  12. int main() {
  13. int id = semget(123, 0, 0);
  14. if (id == -1) {
  15. perror("semget");
  16. exit(1);
  17. }
  18. p(id);
  19. }

V操作具体代码:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <sys/ipc.h>
  4. #include <sys/sem.h>
  5. v(int id) {
  6. struct sembuf sb[1];
  7. sb[0].sem_num = 0;
  8. sb[0].sem_op = 1;
  9. sb[0].sem_flg = 0;
  10. semop(id, sb, 1);
  11. }
  12. int main() {
  13. int id = semget(123, 0, 0);
  14. if (id == -1) {
  15. perror("semget");
  16. }
  17. v(id);
  18. }