Linux 进程间通信 - 信号量

0. 前言

   进程是一个独立的资源管理单元,不同进程间的资源是独立的,不能在一个进程中访问另一个进程的用户空间和内存空间。但是,进程不是孤立的,不同进程之间需要信息的交互和状态的传递,因此需要进程间数据的传递、同步和异步的机制。

    当然,这些机制不能由哪一个进程进行直接管理,只能由操作系统来完成其管理和维护,Linux提供了大量的进程间通信机制,包括同一个主机下的不同进程和网络主机间的进程通信,如下图所示:
mark

  • 同主机间的信息交互
  • 无名管道
    特点:多用于亲缘关系进程间通信,方向为单向;为阻塞读写;通信进程双方退出后自动消失
    问题:多进程用同一管道通信容易造成交叉读写的问题
  • 有名管道
    FIFO(First In First Out),方向为单向(双向需两个FIFO),以磁盘文件的方式存在;通信双方一方不存在则阻塞
  • 消息队列
    可用于同主机任意多进程的通信,但其可存放的数据有限,应用于少量的数据传递
  • 共享内存
    可实现同主机任意进程间大量数据的通信,但多进程对共享内存的访问存在着竞争
  • 同主机进程间同步机制:信号量(Semaphore)
  • 同主机进程间异步机制:信号(Signal)
  • 网络主机间数据交互:Socket(套接字)

1. 信号量的原理

所谓信号量,主要用来实现进程间同步,避免并发访问共享资源;同时,信号量也可以标记资源的个数。

1). 信号量集合 - semid_ds

/*  come from /usr/include/linux/sem.h  */

struct semid_ds 
{
    struct ipc_perm sem_perm;       /* permissions .. see ipc.h */
    __kernel_time_t sem_otime;      /* last semop time */
    __kernel_time_t sem_ctime;      /* last change time */
    struct sem  *sem_base;      /* ptr to first semaphore in array */
    struct sem_queue *sem_pending;      /* pending operations to be processed */
    struct sem_queue **sem_pending_last;    /* last pending operation */
    struct sem_undo *undo;          /* undo requests on this array */
    unsigned short  sem_nsems;      /* no. of semaphores in array */
};

struct sem
{
    int semval;     /*信号量的值*/
    int sempid;     /*最近一次操作的进程PID*/
}

2). 信号量系统信息 - struct seminfo

/*  come from /usr/include/linux/sem.h  */

struct  seminfo 
{
   int semmap;  /* Number of entries in semaphore map; unused within kernel */
   int semmni;  /* Maximum number of semaphore sets */
   int semmns;  /* Maximum number of semaphores in all semaphore sets */
   int semmnu;  /* System-wide maximum number of undo structures; unused within kernel */
   int semmsl;  /* Maximum number of semaphores in a set */
   int semopm;  /* Maximum number of operations for semop(2) */
   int semume;  /* Maximum number of undo entries per process; unused within kernel */
   int semusz;  /* Size of struct sem_undo */
   int semvmx;  /* Maximum semaphore value */
   int semaem;  /* Max. value that can be recorded for semaphore adjustment (SEM_UNDO) */
};

3). 信号量设置的联合体 - union semun

针对信号量的不同操作,semctl的第四个参数有所不同,为了方便起见,一般我们使用一个联合体对所有的可能性进行封装(自行封装,系统无此联合体

union semun
{
    int value;
    struct semid_ds *buf;
    unsigned short *array;
};

4). 函数semop参数结构体 - sembuf

可通过该结构体实现信号量的 P(get)、V(release) 操作,具体看sem_op的值为正(v)还是负(P)

/*  come from /usr/include/linux/sem.h  */

/* semop system calls takes an array of these. */
struct sembuf {
    unsigned short  sem_num;    /* semaphore index in array */
    short       sem_op;     /* semaphore operation */ 信号量操作:正数,增加信号量的值;负数,减少信号量的值
    short       sem_flg;    /* operation flags */ 包括:1.IPC_NOWAIT: 阻塞立刻返回;2.SEM_UNDO: 进程退出后,该进程对sem的操作将会撤销
};

2. 信号量的操作

1). 创建信号量集合 - semget

  • 作用
    创建一个信号量的集合

  • 头文件

      #include<sys/sem.h>
    
  • 函数原型

      int semget(key_t key, int nsems, int semflg)
    
  • 参数

  • key: 有函数ftok产生的返回值
  • nsems: 创建的信号量的个数,以数组的形式存储
  • semflg: 信号量集合的权限,最终值为 perm & ~umask
Macro No. Description
IPC_CREAT 00001000 若key不存在,这创建
IPC_EXCL 00002000 若key存在,返回失败
IPC_NOWAIT 00004000 若需要等待,直接返回错误
  • 返回值
    成功: 返回sem id
    失败: -1

2). 控制信号量(集合) - semctl

  • 作用
    对信号量集合或者对信号量集合中的某个或者某几个信号进程操作

  • 头文件

      #include<sys/sem.h>
    
  • 函数原型

      int semctl(int semid, int semnum, int cmd, ...)
    
  • 参数

  • semid : 由semget函数返回的semaphore id 号
  • semnum : 集合中,信号量处于信号量集合数组的下标
  • cmd: 要执行的操作(以宏的形式出现)
Macro No. Description Return Argument 4
IPC_RMID 0 删除 以下皆针对整个信号量集合,/usr/include/linux/ipc.h
IPC_SET 1 设置ipc_perm参数 struct semid_ds
IPC_STAT 2 获取ipc_perm参数 struct semid_ds
IPC_INFO 3 获取系统信息 struct seminfo
GETPID 11 获取信号量拥有者的PID 以下皆针对某个信号量 /usr/include/linux/sem.h
GETVAL 12 获取信号量的值,函数返回信号的值 当前信号量的值:进程数; 失败:-1
GETALL 13 获取所有信号量的值 成功:0; 失败:-1 数组地址
GETNCNT 14 获取等待信号量递增的进程数 成功:进程数; 失败:-1
GETZCNT 15 获取等待信号量值递减的进程数 成功:进程数; 失败:-1 int value (信号量的值)
SETVAL 16 设置信号量的值,设置的值在第四个参数中 成功:0; 失败:-1
SETALL 17 设置所有信号量的值 成功:0; 失败:-1 数组地址
  • 返回值
    成功: 0
    失败:-1

3). 信号量操作 - semop

  • 作用
    操作信号量的集合

  • 头文件

     #include <sys/sem.h>
    
  • 函数原型

      int semop(int semid, struct sembuf *sops, unsigned nsops)
    
  • 参数

  • semid: 信号量集合ID(由semget返回)
  • sops: struct sembuf结构体变量,见上面
  • nsops: 进行操作信号量的个数,即sops结构变量的个数,需大于或等于1。最常见设置此值等于1,只完成对一个信号量的操作
  • 返回值
    成功: 0
    失败:-1

3. 示例代码

生产者/消费者问题,经典PV问题,具体过程如下图所示:
mark
本例代码分为两个文件:

  1. customer.c :
  2. producer.c :
  1. producer.c
/*
* Filename: producer.c
*/

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

int sem_id;

int init(void)
{
    key_t key;
    unsigned short int sem_array[2];    //for semaphore set, [0] for produce, [1] for space

    union semun     //used for function semop()
    {
        int val;
        struct semid_ds *buf;
        unsigned short *array;
    }arg;

    key = ftok(".", 0xFF);
    if(-1 == key)
    {
        perror("ftok");
        return -1;
    }    
    
    sem_id = semget(key, 2, IPC_CREAT|0644);
    if(-1 == sem_id)
    {
        perror("semget");
        return -1;
    }    

    sem_array[0] = 0;
    sem_array[1] = 100;
    arg.array = sem_array;
    
    if(-1 == (semctl(sem_id, 0, SETALL, arg)))
    {
        perror("semctl");
        return -1;
    }
        
    printf("produce init is %d\n", semctl(sem_id, 0, GETVAL));
    printf("space init is %d\n", semctl(sem_id, 1, GETVAL));

    return 0;
}

void del()
{
    semctl(sem_id, 0, IPC_RMID);
}

int main()
{
    struct sembuf sops[2];  //for function semop()
    sops[0].sem_num = 0;
    sops[0].sem_op = 1;     //increase 1
    sops[0].sem_flg = 0;
    
    sops[1].sem_num = 1;
    sops[1].sem_op = -1;    //decrease 1
    sops[1].sem_flg = 0;
    
    signal(SIGALRM, NULL);

    if(-1 == init())
    {
        fprintf(stderr,"init error!\n");
        exit(EXIT_FAILURE);
    }

    printf("This is producer:\n");

    while(1)
    {
        alarm(0);   //cancel the previous alarm
        alarm(10);  //set the 10s , if timeout, the process

        printf("Before produce:\n");
        printf("\tThe produce is %d\n", semctl(sem_id, 0, GETVAL));
        printf("\tThe space is %d\n", semctl(sem_id, 1, GETVAL));

        printf("\n\nNow, producing...\n\n");

        semop(sem_id, &sops[0], 1);
        semop(sem_id, &sops[1], 1);

        printf("After produce:\n");
        printf("\tThe produce is %d\n", semctl(sem_id, 0, GETVAL));
        printf("\tThe space is %d\n", semctl(sem_id, 1, GETVAL));

        sleep(2);
    }

    del();

    return 0;
}


  1. customer.c
/*
* Filename: producer.c
*/


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

int sem_id;

int init(void)
{
    key_t key;

    key = ftok(".", 0xFF);
    
    sem_id = semget(key, 2, IPC_CREAT|0644);
    if(-1 == sem_id)
    {
        perror("semget");
        return -1;
    }    

    return 0;
}

void del()
{
    semctl(sem_id, 0, IPC_RMID);
}

int main()
{
    struct sembuf sops[2];  //for function semop()
    sops[0].sem_num = 0;
    sops[0].sem_op = -1;     //increase 1
    sops[0].sem_flg = 0;
    
    sops[1].sem_num = 1;
    sops[1].sem_op = 1;    //decrease 1
    sops[1].sem_flg = 0;
    
    signal(SIGALRM, NULL);

    if(-1 == init())
    {
        fprintf(stderr,"init error!\n");
        exit(EXIT_FAILURE);
    }

    printf("This is customer:\n");

    while(1)
    {
        alarm(0);   //cancel the previous alarm
        alarm(10);  //set the 10s , if timeout, the process
        printf("Before consume:\n");
        printf("\tThe produce is %d\n", semctl(sem_id, 0, GETVAL));
        printf("\tThe space is %d\n", semctl(sem_id, 1, GETVAL));

        printf("\n\nNow, consuming...\n\n");

        semop(sem_id, sops, 2);

        printf("After consume:\n");
        printf("\tThe produce is %d\n", semctl(sem_id, 0, GETVAL));
        printf("\tThe space is %d\n", semctl(sem_id, 1, GETVAL));

        sleep(3);
    }

    del();

    return 0;
}

posted @ 2017-10-20 10:42  Jimmy_Nie  阅读(777)  评论(0编辑  收藏  举报