Chinaunix首页 | 论坛 | 博客
  • 博客访问: 32776
  • 博文数量: 17
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 165
  • 用 户 组: 普通用户
  • 注册时间: 2022-11-22 23:41
个人简介

将分享技术博文作为一种快乐,提升自己帮助他人

文章分类

全部博文(17)

文章存档

2023年(9)

2022年(5)

我的朋友

分类: LINUX

2023-04-07 22:46:02

一、什么是信号量

多个线程同一时刻对共享资源的读写会引起一些问题,需要通过一种机制,使得在任一时刻只能有一个执行线程访问代码的临界区域。临界区域是指执行数据更新的代码需要独占式地执行。信号量就是这种机制中的一种,其可用来调协进程对共享资源的访问的。信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。一种常见的信号量是二进制信号量,其值只能为0和1。而取多个正整数值的信号量,称为通用信号量。

二、信号量的工作原理

信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),其行为分别如下:
P(sv): 如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行
,进入等待信号量状态;
V(sv): 如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给信号量加1。
例如:两个进程共享一个二进制信号量sv,一旦其中一个进程执行了P(sv)操作,它将得到信号量,并可以进入临界区,使sv减1。而第二个进程将被阻止进入临界区,因为当它试图执行P(sv)时,sv为0,它会被挂起以等待{BANNED}中国第一个进程离开临界区域并执行V(sv)释放信号量,这时第二个进程就可以恢复执行。

三、Linux的信号量机制

Linux提供了一套信号量操作接口,其支持通用信号量,二进制信号量是其特殊情况。下面分别认识下这些接口。
1、semget()函数
该函数作用为根据key值创建一个信号量或获得一个已有的信号量,其原型如下:

点击(此处)折叠或打开

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

  4. int semget(key_t key, int nsems, int semflg);
{BANNED}中国第一个参数key:唯一非0,通过该key值创建一个信号量标识符。不同进程使用相同的key值,通过semget()得到信号量标识符,其他操作函数均通过该标识符操作该信号量。
第二个参数num_sems:指定需要的信号量数目,它的值几乎总是1。
第三个参数sem_flags:是一组标志,其低9bit指定了键值对象操作的用户权限,其指定方式和含义与open()函数类似。此外,还可指定IPC_CREAT和IPC_EXCL,来控制信号量对象已存在时,调用该接口的行为方式。
返回值:成功返回正整数,失败返回-1.

2、semop()函数

该函数的作用是改变信号量的值,其定义如下:

点击(此处)折叠或打开

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

  4. int semop(int semid, struct sembuf *sops, size_t nsops);
semid: 为通过semget()创建的信号量标识符。
sembuf结构定义如下:

点击(此处)折叠或打开

  1. struct sembuf {
  2.     unsigned short sem_num; /* semaphore number */
  3.     short sem_op; /* semaphore operation */
  4.     short sem_flg; /* operation flags */
  5. }
sem_num:若为二进制信号量,它为0。
sem_op: 信号量在依次操作需要改变的数据,通常为两个数。-1:即P等待操作+1,即发送信号操作。
sem_flg可为IPC_NOWAIT和SEM_UNDO。设置操作为SEM_UNDO,使操作系统跟踪信号,并在进程没有释放信号量而终止时,操作系统释放信号量。,

3、semctl()函数
函数原型如下:

点击(此处)折叠或打开

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

  4. int semctl(int semid, int semnum, int cmd, ...);
该函数用来直接控制信号量信息。
semid: 为通过semget()创建的信号量标识符。
semnum:指定集合中的第semnum个信号量。
cmd: 通过cmd指定控制操作。函数有三个还是四个变量,取决于cmd。
当有四个变量时,第四个入参为union semun类型。定义如下:

点击(此处)折叠或打开

  1. union semun {
  2.    int val; /* Value for SETVAL */
  3.    struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
  4.    unsigned short *array; /* Array for GETALL, SETALL */
  5.    struct seminfo *__buf; /* Buffer for IPC_INFO
  6.                              (Linux-specific) */
  7. }
struct semid_ds的定义如下:

点击(此处)折叠或打开

  1. struct semid_ds {
  2.    struct ipc_perm sem_perm; /* Ownership and permissions */
  3.    time_t sem_otime; /* Last semop time */
  4.    time_t sem_ctime; /* Last change time */
  5.    unsigned long sem_nsems; /* No. of semaphores in set */
  6. };
cmd通常为以下两个值:
SETVAL: 用来把信号量初始化一个信号量的值,即对信号的量的初始化。
IPC_RMID: 用于删除一个信号量标识符。

四、进程信号量通信测试

点击(此处)折叠或打开

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

  4. #include <unistd.h>
  5. #include <string.h>
  6. #include <fcntl.h>
  7. #include <stdlib.h>
  8. #include <stdio.h>

  9. union semun
  10. {
  11.     int val;
  12.     struct semid_ds *buf;
  13.     unsigned short *arry;
  14. };


  15. int set_semval(int sem_id)
  16. {
  17.     union semun sem_union;
  18.     int ret;

  19.     sem_union.val = 1; // 二进制信号量
  20.     return semctl(sem_id, 0, SETVAL, sem_union);
  21. }

  22. static void del_sem(int sem_id)
  23. {
  24.     union semun sem_union;

  25.     semctl(sem_id, 0, IPC_RMID, sem_union);
  26. }

  27. int sem_p(int sem_id)
  28. {
  29.     // 等待获取信号量,值-1
  30.     struct sembuf sem_b;

  31.     sem_b.sem_num = 0;
  32.     sem_b.sem_op = -1;
  33.     sem_b.sem_flg = SEM_UNDO;

  34.     return semop(sem_id, &sem_b, 1);
  35. }

  36. int sem_v(int sem_id)
  37. {
  38.     // 释放信号量,发送信号
  39.     struct sembuf sem_b;

  40.     sem_b.sem_num = 0;
  41.     sem_b.sem_op = 1;
  42.     sem_b.sem_flg = SEM_UNDO;

  43.     return semop(sem_id, &sem_b, 1);
  44. }

  45. int main(int argc, char *argv[])
  46. {
  47.     char process_log = 'S';
  48.     int sem_id;
  49.     key_t key;

  50.     key = ftok(argv[0], 0x18);
  51.     if (key == -1) {
  52.         fprintf(stderr, "Failed to get key.\n");
  53.         goto out;
  54.     }

  55.     sem_id = semget(key, 1, IPC_CREAT | 0666);
  56.     if (sem_id == -1) {
  57.         fprintf(stderr, "Failed to get sem object.\n");
  58.         goto out;

  59.     }

  60.     // 带参数的为主进程,负责初始化信号量
  61.     if (argc > 1) {
  62.         if (set_semval(sem_id) == -1)
  63.             goto out;

  64.         process_log = argv[1][0];
  65.         sleep(2);
  66.     }

  67.     for (int i = 0; i < 10; i++) {
  68.         if (sem_p(sem_id) == -1)
  69.             goto out;
  70.         printf("%c", process_log);

  71.         fflush(stdout);
  72.         sleep(1);
  73.         fflush(stdout);

  74.         if (sem_v(sem_id) == -1)
  75.             goto out;
  76.         sleep(1);
  77.     }

  78.     printf("\n%d - finished\n", getpid());

  79.     if (argc > 1) {
  80.         sleep(3);
  81.         del_sem(sem_id);
  82.     }

  83.     return 0;

  84. out:
  85.     return -1;
  86. }

带有参数的进程为{BANNED}中国第一个进程,负责初始化信号量为二进制信号量。不带参数的进程打印S,直接使用信号量。结果运行如下:



六、信号量的总结

信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。我们通常通过信号来解决多个进程对同一资源的访问竞争的问题,使在任一时刻只能有一个执行线程访问代码的临界区域,也可以说它是协调进程间的对同一资源的访问权,也就是用于同步进程的。



阅读(2032) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~