目录

信号量

信号量及信号量上的操作是 E.W.Dijkstra 在 1965 年提出的一种解决同步互斥问题的较通用的方法,并在很多操作系统中得以实现,Linux 改进并实现了这种机制。1

流程

一般流程2如下:

  1. 通过 ftok 获得确定的 IPC Key
  2. 使用 semget 获得用于信号量的特定 IPC ID
  3. 使用 semop 操作信号量
  4. 使用 semctl 移除信号量

函数

semget

建立/获得信号量。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semget(key_t key, int nsems, int semflg)
  • key:键值,一般使用 ftok 获得
  • nsems:在新创建的集合中信号量的个数,最大值 2503
  • semflg:使用 | 连接
    • 权限:可使用 0666 等表示,也可使用 openmode
    • IPC_CREAT:若不存在则创建
    • IPC_EXCL:若存在则返回错误(和 IPC_CREAT 一起使用)
  • return:-1,错误;非负数,信号量 ID

  1. 系统中查看存在的信号量可通过 ipcs -s 命令
  2. 最大值可通过 ipcs -ls 查看「max semaphores per array」,echo "256 32000 32 128" > /proc/sys/kernel/sem 修改 4

semctl

控制信号量集合,设置、获取、删除。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semctl(int semid, int semnum, int cmd, ...)
  • semid:semget 获得 ID
  • semnum:集合中哪个信号量,从 0 开始
  • cmd:常用如下5
    • IPC_STAT:从信号量集合上检索 semid_ds 结构,并存到 semun 联合体参数的成员 buf 的地址中
    • IPC_SET:设置一个信号量集合的 semid_ds 结构中 ipc_perm 域的值,并从 semun 的 buf 中取出值
    • IPC_RMID:移除信号量集合
    • GETALL:从信号量集合中获得所有信号量的值,并把其整数值存到 semun 联合体成员的一个指针数组中
    • GETNCNT:返回当前等待资源的进程个数
    • GETPID:返回最后一个执行系统调用 semop 进程的 PID
    • GETVAL:返回信号量集合内单个信号量的值
    • GETZCNT:返回当前等待 100% 资源利用的进程个数
    • SETALL:与 GETALL 正好相反
    • SETVAL:用联合体中 val 成员的值设置信号量集合中单个信号量的值
  • args:union semun
  • return:-1,错误


系统中存在的信号量可通过 ipcrm -s/S 删除

semop/semtimedop

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semop(int semid, struct sembuf *sops, unsigned nsops)
int semtimedop(int semid, struct sembuf *sops, unsigned nsops, struct timespec *timeout)
  • semid:semget 获得 ID

  • sops:数组,结构如下

      struct sembuf {
      	unsigned short sem_num;  /* 信号量集合索引 */
      	short          sem_op;   /* 操作数 */
      	short          sem_flg;  /* 操作标识 */
      }
    
    • sem_op:
      • 零:如果 semval 为 0,则进行下一步,如果 semval 不为 0,则等待(IPC_NOWAIT 跳过)
      • 正数:信号量的值加上该值
      • 负数:semval 大于等于该值绝对值则相加,semval 小于该值绝对值则等待
    • sem_flag:
      • IPC_NOWAIT:非阻塞
      • SEM_UNDO:进程退出自动解锁信号量,防止死锁
  • nsops:执行数组操作个数

理解

  1. 保护资源时,可以用 sem=0 时表示空闲;那么加锁动作即 [0,1],等待 sem 空闲并加一;解锁动作 [-1],sem 减一返回空闲状态。
  2. 同步或生产消费问题,可以用 sem 加减表示,sem 加一表示生产一件或该操作完成可以进行下一步,消费方则等待减一 sem 又回到 0 状态。

实例

互斥锁

使用信号量来保护关键部分6

#include <sys/types.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <string.h> /* For strerror(3c) */
#include <errno.h> /* For errno */
#include <unistd.h> /* rand(3c) */
#include <stdio.h>
#include <stdlib.h>

int main (int argc, char **argv)
{
    key_t ipckey;
    int semid;
    struct sembuf sem[2]; /* sembuf defined in sys/sem.h */

    /* Generate the ipc key */
    ipckey = ftok("main.c", ':');

    /* Set up the semaphore set. 4 == READ, 2 == ALTER */
    semid = semget(ipckey, 1, 0666 | IPC_CREAT);
    if (semid < 0) {
        printf("Error - %s\n", strerror(errno));
        _exit(1);
    }

    /* These never change so leave them outside the loop */
    sem[0].sem_num = 0;
    sem[1].sem_num = 0;
    sem[0].sem_flg = SEM_UNDO; /* Release semaphore on exit */
    sem[1].sem_flg = SEM_UNDO; /* Release semaphore on exit */
    int loop = 10;
    while(loop--) { /* loop forever */
        printf("[%s] Waiting for the semaphore to be released\n", argv[1]);
        /* Set up two semaphore operations */
        sem[0].sem_op = 0; /* Wait for zero */
        sem[1].sem_op = 1; /* Add 1 to lock it*/
        semop(semid, sem, 2);
        printf("[%s] I have the semaphore\n", argv[1]);

        sleep(rand() % 3); /* Critical section, sleep for 0-2 seconds */

        sem[0].sem_op = -1; /* Decrement to unlock */
        semop(semid, sem, 1);
        printf("[%s] Released semaphore\n", argv[1]);

        sleep(rand() % 3); /* Sleep 0-2 seconds */
    }
}

执行程序:

./daemon a & ./daemon b & ./daemon c &

执行结果:

[a] I have the semaphore
[b] Waiting for the semaphore to be released
[c] Waiting for the semaphore to be released
[a] Released semaphore
[b] I have the semaphore
[b] Released semaphore
[c] I have the semaphore
[a] Waiting for the semaphore to be released
[b] Waiting for the semaphore to be released
[c] Released semaphore
[a] I have the semaphore
[a] Released semaphore
[b] I have the semaphore
[b] Released semaphore
[b] Waiting for the semaphore to be released
[b] I have the semaphore
[c] Waiting for the semaphore to be released
[a] Waiting for the semaphore to be released
[b] Released semaphore

同步

生产者:

#include <sys/types.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main (void)
{
    key_t ipckey;
    int semid;
    struct sembuf sem[2];

    ipckey = ftok("/tmp/test", ':');

    semid = semget(ipckey, 1, 0600|IPC_CREAT|IPC_EXCL);
    if (semid < 0) {
        printf("Sem Create Error - %s\n", strerror(errno));
        exit(1);
    }

    sem[0].sem_num = 0;
    sem[1].sem_num = 0;
    sem[0].sem_flg = SEM_UNDO;
    sem[1].sem_flg = SEM_UNDO;
    int loop = 10;
    while(loop--) {
        sem[0].sem_op = 0;
        sem[1].sem_op = 1;
        semop(semid, sem, 2);
        printf("I produce\n");
        sleep(3);
    }
}

消费者:

#include <sys/types.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main (void)
{
    key_t ipckey;
    int semid;
    struct sembuf sem[1];

    ipckey = ftok("/tmp/test", ':');

    semid = semget(ipckey, 1, 0600|IPC_CREAT);
    if (semid < 0) {
        printf("Sem Create Error - %s\n", strerror(errno));
        exit(1);
    }

    sem[0].sem_num = 0;
    sem[0].sem_flg = SEM_UNDO;
    int loop = 10;
    while(loop--) {
        sem[0].sem_op = -1;
        semop(semid, sem, 1);
        printf("I consume\n");
        sleep(0);
    }
    if (semctl(semid, 0, IPC_RMID) == -1) {
        printf("Sem Delete Error - %s\n", strerror(errno));
        exit(1);
    }
    printf("Sem Delete!\n");
}

执行程序:

./produce & ./consume &

执行结果:

I produce
I consume
I produce
I consume
I produce
I consume

  1. 深入分析Linux内核源代码,7.3.1 ↩︎

  2. 使用 UNIX System V IPC 机制共享应用程序数据 ↩︎

  3. 深入分析Linux内核源代码,7.3.1.2,P279 ↩︎

  4. REDHAT AS4内核调优之kernel.sem的设置(原创) ↩︎

  5. 深入分析Linux内核源代码,表7.2,P282 ↩︎

  6. 使用 UNIX System V IPC 机制共享应用程序数据,清单5 ↩︎