Linux —— 进程间通信之信号量

1. 什么是信号量

  信号量的本质是数据操作锁,它本身不具有数据交换的功能,而是通过控制其他的通信资源(文件,外部设备)来实现进程间通信,它本身只是一种外部资源的标识。信号量在此过程中负责数据操作的互斥、同步等功能。

  信号量其实就是一个计数器,它记录着资源的数量。当进行P操作时,消耗资源,信号量减少;当进行V操作时,释放资源,信号量增加。

2. 信号量的操作

1. semget函数

函数原型:

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

int semget(key_t key, int nsems, int semflg);

参数:

  1. 参数key :

    1. 键值是IPC_PRIVATE,该值通常为0,意思就是创建一个仅能被进程给我的信号量。
    2. 键值不是IPC_PRIVATE,我们可以指定键值,例如1234;也可以一个ftok()函数来取得一个唯一的键值。
  2. 参数nsems :
      当semget创建新的信号量集合时,必须指定集合中信号量的个数(即num_sems),通常为1;如果是引用一个现有的集合,则将num_sems指定为 0 。

  3. 参数semflg:
    信号量的创建方式或权限。有IPC_CREAT,IPC_EXCL

    1. 设为 0: 取信号量集标识符,若不存在则函数会报错
    2. 设为IPC_CREAT:如果内核中不存在键值与key相等的信号量集,则新建一个信号量集;如果存在这样的信号量集,返回此信号量集的标识符
    3. IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的信号量集,则新建一个信号量集;如果存在这样的信号量集则报错

返回值:

  1. 成功返回信号量的标识码ID。失败返回-1;
  2. 控制信号量的函数

2. semctl函数

函数原型:

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

int semctl(int semid, int semnum, int cmd, union semun arg);

参数:

  1. 参数semid :
    信号量的标志码(ID),也就是semget()函数的返回值;

  2. 参数semnum:
    要操作信号在信号集中的编号。编号从0开始。

  3. 参数cmd :
    命令,表示要进行的操作。

命令 解释
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成员的值设置信号量集合中单个信号量的值

4. 参数arg

union semun {
    short val;              /*SETVAL用的值,一般写这一个就够了*/
     struct semid_ds* buf;  /*IPC_STAT、IPC_SET用的semid_ds结构*/
    unsigned short* array;  /*SETALL、GETALL用的数组值*/
     struct seminfo *buf;   /*为控制IPC_INFO提供的缓存*/
} arg;

3. semop函数

函数原型:

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

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

功能:
  对信号量集标识符为semid中的一个或多个信号量进行P操作或V操作。

参数:

  1. 参数semid
    信号量集标识符

  2. sops
    指向进行操作的信号量集结构体数组的首地址

  3. nsops
      进行操作信号量的个数,即sops结构变量的个数,需大于或等于1。最常见设置此值等于1,只完成对一个信号量的操作

返回值:
成功:返回信号量集的标识符
出错:-1,错误原因存于error中

sembuf结构体:

struct sembuf {

    short sem_num; /*信号量集合中的信号量编号,0代表第1个信号量*/

    short sem_op;
    /*sem_op>0进行V操作,信号量值加sem_op,表示进程释放控制的资源 */
    /*sem_op<0进行P操作,信号量值减sem_op,若(semval-sem_op)<0(semval为该信号量值),则调用进程阻塞,直到资源可用;*/
    /*若sem_op==0时阻塞等待信号量为0,调用进程进入睡眠状态,直到信号值为0;若设置IPC_NOWAIT,进程不会睡眠,直接返回EAGAIN错误*/

    short flag;  
    /*0 设置信号量的默认操作*/
    /*IPC_NOWAIT设置信号量操作不等待*/
    /*SEM_UNDO 选项会让内核记录一个与调用进程相关的UNDO记录,如果该进程崩溃,则根据这个进程的UNDO记录自动恢复相应信号量的计数值*/
};

3. 哲学家就餐问题

题目:
  五位哲学家围坐在一张圆形餐桌旁,做以下两件事情之一:吃饭,或者思考。吃东西的时候,他们就停止思考,思考的时候也停止吃东西。餐桌中间有一大碗意大利面,每两个哲学家之间有一只餐叉。因为用一只餐叉很难吃到意大利面,所以假设哲学家必须用两只餐叉吃东西。他们只能使用自己左右手边的那两只餐叉。

解题思路:

  1. 对每个哲学家和筷子进行编号:0~4
  2. 给每个叉子都设置一个信号量(初值为1)
  3. 当哲学家要吃面时,拿他左右两边的叉子,即编号num和 num+1,并且这是个原子操作(要么拿到两个叉子,要么不拿,不存在只拿一个叉子的情况)。
  4. 当吃完后归还两个叉子,这也是原子操作。

< Code >

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

int id;

union semun{ 
    int val;
};

void take2fork(int num)
{
    struct sembuf sb[2] = {
        {num, -1, 0},
        {(num+1)%5, -1, 0}
    };
    semop(id, sb, 2);
}

void put2fork(int num)
{
    struct sembuf sb[2] = {
        {num, 1, 0},
        {(num+1)%5, 1, 0}
    };
    semop(id, sb, 2);
}

void Eat(int num)
{
    while(1){
        printf("哲学家%d正在思考中\n", num);
        sleep(1);
        printf("哲学家%d的肚子饿了\n", num);
        take2fork(num);
        printf("哲学家%d拿筷子吃饭\n", num);
        sleep(1);
        printf("哲学家%d吃饱放筷子\n\n", num);
        put2fork(num);
    }
}

int main()
{
    id = semget(111, 5, IPC_CREAT|0644);
    if(id == -1) exit(1);

    union semun su = {1};
    int i = 0;
    for(; i < 5; ++i){
        semctl(id, i, SETVAL, su);
    }

    int num = 0;
    for(i = 1; i < 5; ++i){
        pid_t pid = fork();
        if(pid == 0){
            num = i;
            break;
        }
    }
    Eat(num);
    return 0;
}

< TestResult>

[tian@localhost sem]$ ./a.out 
哲学家0正在思考中
哲学家4正在思考中
哲学家3正在思考中
哲学家2正在思考中
哲学家1正在思考中
哲学家0的肚子饿了
哲学家0拿筷子吃饭
哲学家4的肚子饿了
哲学家3的肚子饿了
哲学家3拿筷子吃饭
哲学家2的肚子饿了
哲学家1的肚子饿了
哲学家0吃饱放筷子
哲学家0正在思考中
哲学家3吃饱放筷子
哲学家3正在思考中
哲学家1拿筷子吃饭
哲学家4拿筷子吃饭
哲学家0的肚子饿了
哲学家3的肚子饿了
哲学家1吃饱放筷子
哲学家1正在思考中
哲学家4吃饱放筷子
哲学家4正在思考中
哲学家2拿筷子吃饭
哲学家0拿筷子吃饭
哲学家1的肚子饿了
哲学家4的肚子饿了
哲学家2吃饱放筷子
哲学家2正在思考中
哲学家0吃饱放筷子
哲学家0正在思考中
哲学家3拿筷子吃饭
哲学家1拿筷子吃饭

猜你喜欢

转载自blog.csdn.net/tianzez/article/details/80221886