《Linux应用进程间通信(四) — 信号量》

1.信号量

  信号:是在软件层次上对中断机制的一种模拟,是一种异步通信方式。可以直接进行用户空间进程和内核进程之间的交互。它可以在任何时候发给某一进程,而无需知道该进程的状态。如果该进程未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它。

  信号量:它是一个计数器,用于为多个进程提供对共享数据对象的访问。

 

为了获取共享资源,进程需要执行下列操作

1.测试控制该资源的信号量。

2.若信号量的值大于0,则进程可以使用该资源。在这种情况下,进程会将信号量的值减1,表示它使用了一个资源。

3.若信号量的值为0,则进程进入休眠,直至信号量的值大于0。进程被唤醒后,它返回至步骤1。

内核为每个信号量集合维护着一个semid_ds结构

struct semid_ds{
  struct   ipc_perm    sem_perm;
  unsigned short       sem_nsems;
  time_t               sem_otime;
  time_t               sem_ctime;
  ... 
}

 

2.semget

#include <sys/sem.h>

int semget(key_t key, int num_sems, int sem_flags);
第一个参数key:key是一个整数值,不相关的进程可以通过它访问同一个信号量。
第二个参数num_sems:指定需要的信号量数目。
第三个参数sem_flags:访问权限。
返回值: 成功:正数(非零)值; 失败:-1

  作用:创建一个新的信号量或取一个已有信号量的键。当创建一个新的信号量时,要对semid_ds结构的下列成员赋初值。

  初始化ipc_perm结构,该结构中的mode成员被设置为flag中的相应权限位。

  sem_otime设置为0.

  sem_ctime设置为当前时间。

  sem_nsems设置为num_sems。

  注意:如果是创建新的信号量(一般在服务器进程中),则必须指定num_sems。如果是引用现有信号量,则将num_sems指定为0。

 

 

3.semop

#include <sys/sem.h>

int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops);
第一个参数sem_id:信号量标识符。
第二个参数sem_ops是指向一个结构数组的指针,每个数组元素至少包含以下几个成员:
struct sembuf{
   short sem_num; //信号量编号,除非使用一组信号量,否则它的取值为0
   short sem_op;  //信号量在一次操作中需要改变的数值。通常用到两个值,-1,也就是p操作,它等待信号量变为可用;+1,也就是V操作,它发送信号表示信号量现在已可用。
   short sem_flg; //通过被设置为SEM_UNDO
};
第三个参数num_sem_ops规定该数组中操作得数量。

 

4.semctl

#include <sys/shm.h>

int semctl(int sem_id, int sem_num, int command,...);
第一个参数sem_id:信号量标识符
第二个参数sem_num:信号量编号,当用到成组的信号量,就要用到这个参数。它一般取值为0,表示这是第一个也是唯一的一个信号量
第三个参数command:要采取的动作
有很多不同的值,有两个常用的SETVAL:用来把信号量初始化成一个已知的值,这个值通过union semun中的val成员设置。
              IPC_RMID:用于删除一个已经无需使用的信号量标识符。
第四个参数:可选。是否使用取决于所请求的命令。如果使用该参数,则其类型是semun。
union semun{
  int val;
  struct semid_ds *buf;
  unsigned short *array;
};

《Linux应用进程间通信(四) — 信号量》

 

5.sembuf中sem_flg的设置问题

  通常设置为SEM_UNDO,使操作系统跟踪信号量, 并在进程没有释放该信号量而终止时,操作系统释放信号量 ,例如在二元信号量中,你不释放该信号量 而异常退出,就会导致别的进程一直申请不到信号量,而一直处于挂起状态。

  是否设置sem_flg为SEM_UNDO的区别

《Linux应用进程间通信(四) — 信号量》

 

 

6.实例

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


//这个联合体需要手动定义
union semun
{
    int              val;    /* Value for SETVAL */
    struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
    unsigned short  *array;  /* Array for GETALL, SETALL */
    struct seminfo  *__buf;  /* Buffer for IPC_INFO
                               (Linux-specific) */
};

void ErrExit(const char* reason)
{
    fprintf(stderr, "%s: %d, %s\n", reason, errno, strerror(errno));
    exit(1);
}

int initsem(int key = 0)
{
    int semid = -1;
    if (-1 == (semid = semget(key, 1, 0666 | IPC_CREAT)))
    {
        ErrExit("semget");
    }

    // 信号量初始值为1
    union semun sem_un;
    sem_un.val = 1;
    if (-1 == semctl(semid, 0, SETVAL, sem_un))
    {
        ErrExit("semctl");
    }

    return semid;
}

void destroysem(int semid)
{
    if (-1 == semctl(semid, 0, IPC_RMID))
    {
        ErrExit("semctl del");
    }
}

// -1 为 p 操作
void P(int semid)
{
    struct sembuf op;
    op.sem_num = 0;
    op.sem_op = -1;
    op.sem_flg = SEM_UNDO;
    if (-1 == semop(semid, &op, 1))
    {
        ErrExit("semop p");
    }
}

// 1 为 v 操作
void V(int semid)
{
    struct sembuf op;
    op.sem_num = 0;
    op.sem_op = 1;
    op.sem_flg = SEM_UNDO;
    if (-1 == semop(semid, &op, 1))
    {
        ErrExit("semop v");
    }
}

int main(int argc, char const *argv[])
{
    int semid = initsem();
    pid_t pid = fork();

    if (pid > 0)
    {
        P(semid);
        printf("in parent process...\n");
        sleep(1);
        V(semid);

        waitpid(pid, NULL, 0);
        // 删除信号量集
        destroysem(semid);
    }
    else if (0 == pid)
    {
        P(semid);
        printf("in child process...\n");
        sleep(1);
        V(semid);
    }
    else
    {
        ErrExit("fork");
    }

    return 0;
}

 

《Linux应用进程间通信(四) — 信号量》

上一篇:如何 SSH 到 Linux 服务器里的特定目录及执行命令?


下一篇:《Linux应用进程间通信(四) — 消息队列》