进程间通信 - 信号量

时间:2022-12-20 15:12:37
信号量
  • 信号量实际上是一个计数器,作用是统计临界资源的多少,保护临界资源。
  • 信号量本身也是临界资源

进程互斥
  • 各进程要求共享资源,而有些资源必须互斥使用,因此进程就会竞争这些资源,称为互斥。
  • 一次只允许一个进程使用的资源叫做临界资源。
  • 互斥资源的的程序段叫做临界区。

进程同步
  • 多个进程需要相互配合完成一项任务。

信号量和P、V原语
  • 信号量
        同步 :P、V在同一进程中
        互斥 :P、V在不同进程中
  • 信号量值的含义
        S > 0 :S表示可用资源的个数。
        S = 0 :表示当前无可用资源,也无进程等待。
        S < 0 :表示当前无可用资源,有 |S| 个进程在等待。 // S < 0 是通用信号量的概念,在system V信号量中,S不会小于0
  • P原语 用于获取临界资源,如果当前没有临界资源,进程的PCB就会插入相应的等待队列
  • V原语 用于释放临界资源,唤醒等待的进程,将其插入就绪队列

信号量集结构
struct semid_ds{
     struct ipc_perm sem_perm; /* Ownership and permissions */ 
        time_t sem_otime; /* Last semop time */ 
        time_t sem_ctime; /* Last change time */ 
        unsigned short sem_nsems; /* No. of semaphores in set */
};

信号量集函数 (信号量集是一组信号的集合)     

shmget - 创建和访问一个信号量集
int semget(key_t key, int nsems, int semflg); 
参数 
    key: 信号集的名字     
    nsems:信号集中信号量的总数
    semflg: 由九个权限标志构成,它们的⽤法和创建⽂件时使⽤的mode模式标志是⼀样的 

返回值:成功返回⼀个⾮负整数,即该信号集的标识码;失败返回-1

shmctl - 控制信号量集
int semctl(int semid, int semnum, int cmd, ...); 
参数 
    semid:由semget返回的信号集标识码 
    semnum:信号集中信号量的序号 
    cmd:将要采取的动作(有三个可取值) 
    最后⼀个变长参数根据命令不同⽽不同 
   
返回值:成功返回0;失败返回-1

  cmd参数值 说明
SETVAL 设置信号量集中信号量的计数值(也就是表示当前信号量集中有多少个信号),需要通过一个联合体中来设置,需要左后一个变长参数,就是当前联合体。
GETVAL 获取信号量集中有多少个信号量。不需要最后一个变长参数,并且函数的返回值表示了当前可用的信号数量。
IPC_STAT 把semid_ds结构体中的数据设置为信号集当前关联值。
IPC_SET 在进程有足够的权限下,吧信号集当前的关联值设置为semid_ds结构体中的值。
IPC_RMID 删除信号即,用法同消息队列和共享内存。
semop - 创建和访问一个信号量集 (原子操作)
int semop(int semid, struct sembuf *sops, unsigned nsops); 
参数 
    semid:是该信号量的标识码,也就是semget函数的返回值 
    sops:是个指向⼀个结构数值的指针 
    nsops:信号量的个数 返回值:

成功返回0;失败返回-1

sops所指向的结构体 - sembuf结构体
struct sembuf{
    short sem_num;  //信号量的编号
    short sem_op;  //试一次P、V操作对应的加减数值,P操作设置为 -1,表示消耗一个临界资源,V操作设置为 1,表示释放了一个临界资源
    short sem_flg; // 有两个取值,IPC_NOWAIT,或 SEM_UNDO(将已申请的信号量还原为初识状态,即刚开始获得这个信号量时的状态)
}

代码演示
两个进程(这里用父子进程演示),父进程在屏幕上输出 A,间隔一段时间在输出A,子进程在屏幕上输出B,间隔一段时间在屏幕上再次输出B。

非原子操作:

进程间通信 - 信号量

原子操作:

进程间通信 - 信号量

代码演示原子操作:

//com.h

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

union semun
{
    int val; //SETVAL的值
    struct semid_ds *buf;
    unsigned short *array;
    struct seminfo *_buf;
};

//nums 表示当前信号集中有多少个信号量
//这里我们为二元信号量(互斥锁),所以num = 0
int CreateSem(int nums);

//semid 是CreateSem的返回值
//index 是设置当前信号集中第几个信号量,从0开始
//initval 是将当前信号量设置为initval值,表示临界资源的可用数目。
//这里我们为二元信号量(互斥锁),所以index = 0,initcval = 1
int InitSem(int semid,int index,int initval);

//获取当前信号量集的第index个信号量
//这里我们为二元信号量(互斥锁),所以index = 0
int GetSem(int index);


//对信号量集中的哪一个信号进行P操作
//这里我们为二元信号量(互斥锁),所以who = 0
int P(int semid,int who);

//对信号量集中的哪一个信号进行V操作
//这里我们为二元信号量(互斥锁),所以who = 0
int V(int semid,int who);

int DestorySem(int semid);

//com.c

#include "com.h"

int Command(int nums,int flags)
{
    key_t key = ftok(".",0666);
    if(key == -1)
    {
        perror("ftok");
        exit(1);
    }
    int semid = semget(key,nums,flags);
    if(semid < 0)
    {
        perror("semget");
        exit(1);
    }
    return semid;
}

int CreateSem(int nums)
{
    return Command(nums,IPC_CREAT | IPC_EXCL | 0666);
}

int InitSem(int semid,int index,int initval)
{
    union semun _un;
    _un.val = initval;
    if(semctl(semid,index,SETVAL,_un) < 0)
    {
        perror("semctl initsem");
        exit(1);
    }
    return 0;
}

int GetSem(int index)
{
    return Command(index,IPC_CREAT);
}

int CommandPV(int semid,int who,int val)
{
    struct sembuf buf;
    buf.sem_num = who;
    buf.sem_op = val;
    buf.sem_flg = 0; //忽略这个操作
    if(semop(semid,&buf,1) < 0)
    {
        perror("semop");
        exit(1);
    }
    return 0;
}

int P(int semid,int who)
{
    return CommandPV(semid,who,-1);
}

int V(int semid,int who)
{
    return CommandPV(semid,who,1);
}

int DestorySem(int semid)
{
    if(semctl(semid,0,IPC_RMID) < 0)
    {
        perror("semctl destrysem");
        exit(1);
    }
    return 0;
}

//Print.c

#include "com.h"
#include <unistd.h>

int main()
{
    int semid = CreateSem(1);
    InitSem(semid,0,1);
    int pid = fork();
    if(pid > 0) //parent
    {
        while(1)
        {
            P(semid,0);
            printf("A");
            usleep(654321);
            printf("A ");
            usleep(123456);
            fflush(stdout);
            V(semid,0);
        }
    }
    else if(pid == 0) //child
    {
        while(1)
        {
            P(semid,0);
            printf("B");
            usleep(123456);
            printf("B ");
            fflush(stdout);
            usleep(321456);
            V(semid,0);
        }

    }
    else
    {
        perror("fork");
        exit(1);
    }
    DestorySem(semid);
    return 0;
}

结果演示:没有出现单个A或者B交错出现的情况,符合预期

进程间通信 - 信号量

信号量也可以使用 ipcs -s 查看

进程间通信 - 信号量

可以使用 ipcrm -s [semid] 来删除

进程间通信 - 信号量