经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C » 查看文章
c/c++ linux 进程间通信系列5,使用信号量
来源:cnblogs  作者:小石王  时间:2018/10/21 20:27:11  对本文有异议

linux 进程间通信系列5,使用信号量

信号量的工作原理:

由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:

P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行

V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.

举个例子,就是两个进程共享信号量sv,一旦其中一个进程执行了P(sv)操作,它将得到信号量,并可以进入临界区,使sv减1。而第二个进程将被阻止进入临界区,因为当它试图执行P(sv)时,sv为0,它会被挂起以等待第一个进程离开临界区域并执行V(sv)释放信号量,这时第二个进程就可以恢复执行。

1,semget()函数

  1. int semget(key_t key, int num_sems, int sem_flags);
  • 第二个参数nsems指明要创建的信号量集包含的信号量个数。如果只是打开信号量,把nsems设置为0即可。该参数只在创建信号量集时有效。

  • 第三个参数semflg为操作标识,可取如下值:

    • 0:取信号量集标识符,若不存在则函数会报错

    • IPC_CREAT:当semflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的信号量集,则新建一个信号量集;如果存在这样的信号量集,返回此信号量集的标识符

    • IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的信号量集,则新建一个消息队列;如果存在这样的信号量集则报错

      上述semflg参数为模式标志参数,使用时需要与IPC对象存取权限(如0600)进行|运算来确定信号量集的存取权限

      错误代码:

      EACCESS:没有权限

      EEXIST:信号量集已经存在,无法创建

      EIDRM:信号量集已经删除

      ENOENT:信号量集不存在,同时semflg没有设置IPC_CREAT标志

      ENOMEM:没有足够的内存创建新的信号量集

      ENOSPC:超出限制

2,semop()函数

  1. int semop(int sem_id, struct sembuf *sops, size_t nsops);
  • semid:信号量集标识符

  • sops:指向进行操作的信号量集结构体数组的首地址,此结构的具体说明如下:

    如果sembuf里的semnum超过了集合中信号量的最大个数,在执行semop时,会报出:FIle too large。

    1. struct sembuf {
    2. short semnum; /*信号量集合中的信号量编号,0代表第1个信号量*/
    3. short val;/*若val>0进行V操作信号量值加val,表示进程释放控制的资源 */
    4. /*若val<0进行P操作信号量值减val,若(semval-val)<0(semval为该信号量值),则调用进程阻塞,直到资源可 用;若设置IPC_NOWAIT不会睡眠,进程直接返回EAGAIN错误*/
    5. /*若val==0时阻塞等待信号量为0,调用进程进入睡眠状态,直到信号值为0;若设置IPC_NOWAIT,进程不会睡眠,直接返回EAGAIN错误*/
    6. short flag; /*0 设置信号量的默认操作*/
    7. /*IPC_NOWAIT设置信号量操作不等待*/
    8. /*SEM_UNDO 选项会让内核记录一个与调用进程相关的UNDO记录,如果该进程崩溃,则根据这个进程的UNDO记录自动恢复相应信号量的计数值*/
    9. };
  • nsops:进行操作信号量的个数,即sops结构变量的个数,需大于或等于1。最常见设置此值等于1,只完成对一个信号量的操作

错误代码:

E2BIG:一次对信号量个数的操作超过了系统限制

EACCESS:权限不够

EAGAIN:使用了IPC_NOWAIT,但操作不能继续进行

EFAULT:sops指向的地址无效

EIDRM:信号量集已经删除

sops为指向sembuf数组,定义所要进行的操作序列。下面是信号量操作举例。

struct sembuf sem_get={0,-1,IPC_NOWAIT}; /将信号量对象中序号为0的信号量减1/

struct sembuf sem_get={0,1,IPC_NOWAIT}; /将信号量对象中序号为0的信号量加1/

struct sembuf sem_get={0,0,0}; /进程被阻塞,直到对应的信号量值为0/

flag一般为0,若flag包含IPC_NOWAIT,则该操作为非阻塞操作。若flag包含SEM_UNDO,则当进程退出的时候会还原该进程的信号量操作,这个标志在某些情况下是很有用的,

比如某进程做了P操作得到资源,但还没来得及做V操作时就异常退出了,此时,其他进程就只能都阻塞在P操作上,于是造成了死锁。若采取SEM_UNDO标志,就可以避免因为进程异常退出而造成的死锁。

semops函数详细说明参考:semops

3,semctl()函数

  1. int semctl(int semid, int semnum, int cmd, ...);

作用:根据cmd的不同,操作创建成功的semid,比如设置信号量集合里的每个信号,同时可以访问的进程数,通过cmd:SETALL,来设置,并且后面需要跟一个指向数组的指针。

  • semid:信号量集合的标识符。
  • semnum:信号量集合中的第几个信号量。当cmd为SETALL时,这个参数感觉没有实际意义,多少都可以。
  • cmd:根据您cmd的不同,做不同的操作。

下面的例子1创建了一个信号量的集合,可以用【ipcs -s】查看;例子2去访问例子1创建的信号量集合。

例子1里的关键点:

  • 信号集合里信号的个数为16个
  • 因为这句代码【semun_array[i] = 1;】,所以,每个信号,只能同时有且只有一个进程访问。如果【semun_array[i] = 2;】,则,每个信号,同时有2个进程可以访问这个信号。
  • smectl的第一个参数,感觉设置多少都可以。

例子2里的关键点:

  • sb.sem_num = 15;//指定要访问的信号量集合中的哪个信号量
    sb.sem_op = -1;//semop前信号量的值都为1,这里指定的是-1,所以减一后为0,由于为0了,所以下个进程再想去访问,就需要排队,等现在访问这个信号量的进程结束后,才能访问。
  • SEM_UNDO的作用为,访问信号量的进程结束后,会自动回复这个信号量被访问前的状态,也就是说,某个进程访问前,信号量的状态是1,这个进程退出后,把信号量的状态回复回1.

例子1

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <sys/ipc.h>
  4. #include <sys/sem.h>
  5. #define NSEMS 16
  6. int main(){
  7. int semid;
  8. unsigned short semun_array[NSEMS];
  9. int i;
  10. semid = semget(IPC_PRIVATE, NSEMS, 0600);
  11. if(semid < 0){
  12. perror("semget");
  13. return 1;
  14. }
  15. for(i =0; i < NSEMS; ++i){
  16. semun_array[i] = 1;
  17. }
  18. if(semctl(semid, 1000, SETALL, &semun_array) != 0){
  19. perror("semctl");
  20. return 1;
  21. }
  22. printf("semid:%d\n", semid);
  23. return 0;
  24. }

github源代码

例子2:

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <sys/ipc.h>
  4. #include <sys/sem.h>
  5. #include <stdlib.h>
  6. #define NSEMS 16
  7. int main(int argc, char* argv[]){
  8. int semid;
  9. sembuf sb;
  10. if(argc != 2){
  11. printf("arg is wrong\n");
  12. return 1;
  13. }
  14. semid = atoi(argv[1]);
  15. sb.sem_num = 15;//指定要操作的信号量是信号集合中的号码为0信号量
  16. sb.sem_op = -1;
  17. sb.sem_flg = SEM_UNDO;
  18. printf("before semop()\n");
  19. if(semop(semid, &sb, 1) != 0){
  20. perror("semop");
  21. return 1;
  22. }
  23. printf("after semop()\n");
  24. printf("press enter to exti\n");
  25. getchar();
  26. return 0;
  27. }

github源代码

例子3:多线程之间,使用信号量。

第一个进程:

  • step1:创建有16个信号量的信号量集合的结果是成功的,所以结果semid >= 0,进入if分支。
  • step2:把每个信号量可同时访问的进程数目设置为1,在代码的22-28行。
  • step3:准备访问16个信号量,首先在31-35行,把访问设置为减一,并且UNDO。
  • step4:按回车后,开始访问16个信号量。

第二个进程:

  • step1:因为是用同一个key去创建信号量集合,所以是失败的,进入else分支。
  • step2:去拿,已经被创建过了的信号量集合的id,semid = semget(MYIPCKEY, NSEMS, 0600);
  • step3:进入while循环,观察sem_otime是否为0,不为0后,跳出循环,准备访问第0号信号量和第1号信号量。如果某个进程访问了信号量集合,sem_otime就从0变为非0.
  • step4:准备访问第0号信号量和第1号信号量。
  • step5:访问第0号信号量和第1号信号量,但是发现信号量的值为-1(因为第一个进程访问是给他减一了),说明已经有个进程(第一个进程)正在访问,所以就一直等待。
  • step6:按回车,让第一个进程结束。信号量会从-1变为0。
  • step7:因为访问的进程退出了(信号量会从-1变为0),在step5处的等待就结束了,才可以访问第0号信号量和第1号信号量。

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. #include <errno.h>
  6. #include <sys/ipc.h>
  7. #include <sys/sem.h>
  8. #include <sys/types.h>
  9. #include <sys/wait.h>
  10. #define MYIPCKEY 0xabcdabcd
  11. #define NSEMS 16
  12. int sem_init(){
  13. int semid;
  14. unsigned short semun_array[NSEMS];
  15. sembuf sb[NSEMS];
  16. int i;
  17. semid = semget(MYIPCKEY, NSEMS, 0600 | IPC_CREAT | IPC_EXCL);
  18. if(semid >= 0){
  19. for(i = 0; i < NSEMS; ++i){
  20. semun_array[i] = 1;
  21. }
  22. if(semctl(semid, NSEMS, SETALL, &semun_array) != 0){
  23. perror("semctl");
  24. return 1;
  25. }
  26. printf("[pid:%d] new semaphore set, semid=%d\n", getpid(), semid);
  27. for(i = 0; i < NSEMS; ++i){
  28. sb[i].sem_num = i;
  29. sb[i].sem_op = -1;
  30. sb[i].sem_flg = SEM_UNDO;
  31. }
  32. printf("IF[pid:%d] before semop()\n", getpid());
  33. printf("IF[pid:%d] press enter to start semop()\n", getpid());
  34. getchar();
  35. if(semop(semid, sb, NSEMS)){
  36. perror("semop");
  37. return 1;
  38. }
  39. printf("IF[pid:%d] press enter to exit this process\n", getpid());
  40. getchar();
  41. exit(0);
  42. }
  43. else{
  44. if(errno != EEXIST){
  45. perror("semget");
  46. return 1;
  47. }
  48. else{
  49. printf("in else\n");
  50. semid_ds sds;
  51. semid = semget(MYIPCKEY, NSEMS, 0600);
  52. if(semid < 0){
  53. perror("semget 1");
  54. return 1;
  55. }
  56. printf("ELSE[pid:%d] before semctl()\n", getpid());
  57. while(true){
  58. //IPC_STAT的时候,忽略第二个参数
  59. if(semctl(semid, 0, IPC_STAT, &sds) != 0){
  60. perror("semctl 1");
  61. return 1;
  62. }
  63. printf("###########################\n");
  64. printf("sem_perm.mode:%d,sem_perm.__seq:%d\n",sds.sem_perm.mode,sds.sem_perm.__seq);
  65. printf("otime:%ld\n",sds.sem_otime);/* Last semop time */
  66. printf("ctime:%ld\n",sds.sem_ctime);/* Last change time */
  67. printf("sem_nsems:%ld\n",sds.sem_nsems);/* No. of semaphores in set */
  68. printf("###########################\n");
  69. if(sds.sem_otime != 0){
  70. break;
  71. }
  72. printf("ELSE[pid:%d] waiting otime change...\n", getpid());
  73. sleep(2);
  74. }
  75. sb[0].sem_num = 0;
  76. sb[0].sem_op = -1;
  77. sb[0].sem_flg = SEM_UNDO;
  78. sb[1].sem_num = 0;
  79. sb[1].sem_op = -1;
  80. sb[1].sem_flg = SEM_UNDO;
  81. printf("ELSE[pid:%d] before semop()\n", getpid());
  82. if(semop(semid, sb, 2) != 0){
  83. perror("semop 1");
  84. return 1;
  85. }
  86. printf("ELSE[pid:%d] after semop()\n", getpid());
  87. }
  88. }
  89. return 0;
  90. }
  91. int main(){
  92. pid_t pid;
  93. pid = fork();
  94. if(sem_init() < 0){
  95. printf("[pid:%d] sem_init() failed\n", getpid());
  96. }
  97. }

github源代码

注意:执行一次后,再次执行前,必须用下面的命令删除信号量集合。

1,首先找到信号量集合的ID:

  1. ipcs -s

2,删除信号量集合:

  1. ipcrm -s id

c/c++ 学习互助QQ群:877684253

本人微信:xiaoshitou5854

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号