1 system V信号量

1.1 信号量基本定义

信号量:

  • 互斥: P,V在同一个进程中
  • 同步:P,V在不同进程中

信号量值的含义:

  • S>0: S表示可用资源的个数
  • S=0: 表示没有可用资源,无等待进程
  • S<0: S绝对值表示等待队列种进程的个数

1.2 信号量PV操作

  1. //linux系统信号量结构体的定义
  2. struct semaphore
  3. {
  4. int value;//s的个数
  5. pointer_PCB queue; //s等待队列,等待获取信号量
  6. }
  7. //P获取信号量的基本操作流程
  8. P(s)
  9. {
  10. s.value -= 1;
  11. if (s.value < 0)
  12. {
  13. //改进程状态设置为等待状态
  14. //讲改进程的PCB插入到相应的等待队列s.queue末尾
  15. }
  16. }
  17. //V归还信号量的基本操作流程
  18. V(s)
  19. {
  20. s.value += 1;
  21. if (s.value >= 0)
  22. {
  23. //唤醒等待队列中等待的一个进程
  24. //改变其状态为就绪态,将其插入就绪队列
  25. }
  26. }

1.3 信号量集函数

统一头文件:

  1. #include <sys/types.h>
  2. #include <sys/ipc.h>
  3. #include <sys/sem.h>

semget

函数定义: int semget(key_t key, int nsems, int semflg);
功能: 创建或打开一个信号量集
返回值: 成功返回信号量集的标识码,失败返回-1
参数:

  • key: 信号量集名称
  • nsems:信号量个数
  • semflg:九个权限标志,和创建文件的mode一样

    semctl

    函数定义: int semctl(int semid, int semnum, int cmd,...);
    功能: 修改或删除信号量集
    返回值: 成功返回0,失败返回-1
    参数:

  • semid: 信号量集标识码

  • semnum: 信号量的序号
  • cmd: 要采取的操作

imgclip7.png

  • 最后一个参数根据命令不同而不同

    semop

    函数定义: int semop(int semid, struct sembuf* sops, unsigned nsops);
    功能: 对信号量集进程PV操作
    返回值: 成功返回0,失败返回-1
    参数:

  • sops:指向一个结构体的指针

    1. struct sembuf{
    2. short sem_num; //信号量编号
    3. short sem_op;//信号量一次PV操作时加减的数值, +1,-1
    4. short sem_flg;//IPC_NOWAIT, SEM_UNDO
    5. };
  • nsops: 信号量的个数

1.4 代码示例

  1. //创建信号量集
  2. int sem_create(key_t key)
  3. {
  4. int semid;
  5. semid = semget(key, 1, IPC_CREAT | IPC_EXCL | 0666);
  6. if (semid == -1)
  7. ERR_EXIT("semget create failed");
  8. return semid;
  9. }
  10. //打开一个已经存在的信号量集
  11. int sem_open(key_t key)
  12. {
  13. int semid;
  14. semid = semget(key, 0, 0);
  15. if (semid == -1)
  16. ERR_EXIT("get sem failed");
  17. return semid;
  18. }
  19. //设置当前计数值
  20. int sem_setval(int semid, int val)
  21. {
  22. union semun s;
  23. s.val = val;
  24. int ret = semctl(semid, 0, SETVAL, s);
  25. if (ret == -1)
  26. ERR_EXIT("semctl setval failed");
  27. return 0;
  28. }
  29. //获取当前计数值
  30. int sem_getval(int semid)
  31. {
  32. int ret = semctl(semid, 0, GETVAL, 0);
  33. if (ret == -1)
  34. ERR_EXIT("semctl getval failed");
  35. return ret;
  36. }
  37. //删除信号量集
  38. int sem_d(int semid)
  39. {
  40. if (semctl(semid, 0, IPC_RMID, 0) == -1)
  41. ERR_EXIT("semctl delete failed");
  42. return 0;
  43. }
  44. //PV操作
  45. int sem_p(int semid)
  46. {
  47. sembuf s = {0, -1, 0};//可以使用二维数组定义多个信号量的处理
  48. int ret = semop(semid, &s, 1);//这里1表示处理一个信号量
  49. if (ret == -1)
  50. ERR_EXIT("semop P failed");
  51. return ret;
  52. }
  53. int sem_v(int semid)
  54. {
  55. sembuf s = {0, 1, 0};
  56. int ret = semop(semid, &s, 1);
  57. if (ret == -1)
  58. ERR_EXIT("semop V failed");
  59. return ret;
  60. }
  61. //PV操作,同时操作两个信号量
  62. int sem_p(int semid, int left, int right)
  63. {
  64. sembuf s[2] = {
  65. {left, -1, 0},
  66. {right, -1, 0}
  67. };
  68. int ret = semop(semid, s, 2);
  69. if (ret == -1)
  70. ERR_EXIT("semop P failed");
  71. return ret;
  72. }
  73. int sem_v(int semid, int left, int right)
  74. {
  75. sembuf s[2] = {
  76. {left, 1, 0},
  77. {right, 1, 0}
  78. };
  79. int ret = semop(semid, s, 2);
  80. if (ret == -1)
  81. ERR_EXIT("semop V failed");
  82. return ret;

2 POSIX信号量

头文件

  1. #include <fcntl.h> /* For O_* constants */
  2. #include <sys/stat.h> /* For mode constants */
  3. #include <semaphore.h>

在 POSIX 标准中,信号量分两种,一种是无名信号量,一种是有名信号量
无名信号量一般用于线程间同步或互斥,而有名信号量一般用于进程间同步或互斥
它们的区别和管道及命名管道的区别类似,无名信号量则直接保存在内存中,而有名信号量要求创建一个文件

2.1 有名信号量创建删除

  • sem_open:创建信号量,会在/dev/shm中生成一个同名文件
  • sem_close:关闭信号量
  • sem_unlink:删除信号量

2.2 无名信号量创建删除

  • sem_init:创建无名信号量
  • sem_destroy:删除无名信号量

2.3 信号量PV操作

  • sem_wait: P操作
  • sem_post: V操作

2.4 代码示例

有名信号量示例

  1. #include <stdio.h>
  2. #include <semaphore.h>
  3. #include <fcntl.h>
  4. #include <unistd.h>
  5. #include <sys/stat.h>
  6. #include <sys/types.h>
  7. #include <sys/wait.h>
  8. void printer(sem_t *sem, char *str)
  9. {
  10. sem_wait(sem); //信号量减一
  11. while(*str!='\0')
  12. {
  13. putchar(*str);
  14. fflush(stdout);
  15. str++;
  16. sleep(1);
  17. }
  18. printf("\n");
  19. sem_post(sem); //信号量加一
  20. }
  21. int main(int argc, char *argv[])
  22. {
  23. pid_t pid;
  24. sem_t *sem = NULL;
  25. pid = fork(); //创建进程
  26. if (pid<0)
  27. { //出错
  28. perror("fork error");
  29. }
  30. else if(pid == 0)
  31. {
  32. //子进程
  33. //跟open()打开方式很相似,不同进程只要名字一样,那么打开的就是同一个有名信号量
  34. sem = sem_open("name_sem", O_CREAT | O_RDWR, 0666, 1); //信号量值为 1
  35. if(sem == SEM_FAILED)
  36. {
  37. //有名信号量创建失败
  38. perror("sem_open");
  39. return -1;
  40. }
  41. char str1[6] = "hello";
  42. printer(sem, str1); //打印
  43. sem_close(sem); //关闭有名信号量
  44. _exit(1);
  45. }
  46. else if(pid > 0)
  47. {
  48. //父进程
  49. //跟open()打开方式很相似,不同进程只要名字一样,那么打开的就是同一个有名信号量
  50. sem = sem_open("name_sem", O_CREAT|O_RDWR, 0666, 1); //信号量值为 1
  51. if(sem == SEM_FAILED)
  52. {//有名信号量创建失败
  53. perror("sem_open");
  54. return -1;
  55. }
  56. char str2[6] = "world";
  57. printer(sem, str2); //打印
  58. sem_close(sem); //关闭有名信号量
  59. wait(NULL); //等待子进程结束
  60. }
  61. sem_unlink("name_sem");//删除有名信号量
  62. return 0;
  63. }

无名信号量示例

  1. #include <stdio.h>
  2. #include <pthread.h>
  3. #include <unistd.h>
  4. #include <semaphore.h>
  5. sem_t sem; //信号量
  6. void printer(char *str)
  7. {
  8. sem_wait(&sem);//减一
  9. while(*str)
  10. {
  11. putchar(*str);
  12. fflush(stdout);
  13. str++;
  14. sleep(1);
  15. }
  16. printf("\n");
  17. sem_post(&sem);//加一
  18. }
  19. void *thread_fun1(void *arg)
  20. {
  21. char str1[6] = "hello";
  22. printer(str1);
  23. }
  24. void *thread_fun2(void *arg)
  25. {
  26. char str2[6] = "world";
  27. printer(str2);
  28. }
  29. int main(void)
  30. {
  31. pthread_t tid1, tid2;
  32. sem_init(&sem, 0, 1); //初始化无名信号量,初始值为 1
  33. //创建 2 个线程
  34. pthread_create(&tid1, NULL, thread_fun1, NULL);
  35. pthread_create(&tid2, NULL, thread_fun2, NULL);
  36. //等待线程结束,回收其资源
  37. pthread_join(tid1, NULL);
  38. pthread_join(tid2, NULL);
  39. sem_destroy(&sem); //销毁信号量
  40. return 0;
  41. }