进程间的通信——信号量

进程间的通信——管 道https://blog.csdn.net/q496958148/article/etails/79948367
进程间的通信——消息队列https://blog.csdn.net/q496958148/article/details/79951727
进程间的通信——共享内存https://blog.csdn.net/q496958148/article/details/79953349

说道信号量,则必须要提到俩个名词:“同步””互斥”

进程同步:

同步:就是多个进程相互协作共同完成一个任务。
例如:

  • 一个理发店有一个理发师,一把剪子,和多个座位。
  • 如果没有顾客,那么理发师就会休息
  • 只要有一个顾客来了,就必须叫醒理发师
  • 再有多余的顾客来了,那么他只能在座位上等待,如果没座位只能离开了。

进程互斥:

互斥:在同一时间某个资源只允许一个进程访问
例如:

  • 有一个超市,入口处只有一个篮子
  • 当第一个顾客进入时取一个篮子,而这时有第二个顾客来了,看到没篮子,只能坐在一旁等待第一个顾客买完归还篮子,第二个顾客取了篮子他才能进去购物。

而在上述例子中,这个超市在某一个时间段要么没有人,要么只能存在一个人。而这时超市这个资源被称为临界资源或者互斥资源。

信号量与P 、V:

信号量:
    互斥:P V 在同一个进程内
    同步:P V 在不同进程中
信号量值的含义:
    S>0:S代表可用的资源
    S=0:表示无可用资源,无等待队列
    S<0:S的数值表示等待队列的进程个数

P、V操作:

S为一个信号量(互斥模型中S通常设置为1,同步模型中S通常设置为0)
P原语操作步骤:
(1)S=S-1;
(2)若S>=0,则该进程继续运行;
(3)若S<0.则该进程被阻塞,并将它插入该信号量的等待队列中。
V原语操作步骤:
(1)S=S+1;
(2)若S>0,则该进程继续运行;
(3)若S<=0.则从信号量的等待队列中唤醒第一个进程,
使其变为就绪状态,然后再返回原进程继续执行

互斥模型:(S通常为1)

p1进程                    p2进程

P(S);                   P(S);
F();                                    F();    
V(S);                               V(S);   

分析如下:

  • 首先p1进程先执行P操作这候S=S-1=0;
  • 然后执行F();这时候p2进程来了,先进行P操作S=S-1=0-1=-1;这时候 p2进程被阻塞,进入等待队列。
  • 等到p1进程执行完F();进行V操作S=S+1=-1+1=0;这时候唤醒消息队列的第一个位置。
  • p2进程被唤醒,执行F();之后如果再来个p3进程,类似上述做法。

同步模型:(S通常为0)

p1进程                    p2进程

P(S);                   V(S);   

分析如下:

  • 首先进程p1先进行P操作S=S-1=-1;这时候p1进程进入等待队列
  • 这时候p2进程来了,进行V操作S=S+1=-1+1=0;唤醒等待队列的第一个进程p1
  • 就这样不断进行同步操作。

信号量结构体:

 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 */
           };

信号量有关的函数:

  • 创建和访问一个信号量集
 int semget(key_t key, int nsems, int semflg);
 //key 还是和之前的一样 “暗号”
 //nsems 信号集中信号量的个数
 //semflg IPC_CREAT:创建新的消息队列。 IPC_EXCL:与IPC_CREAT一同使用,表示如果要创建的消息队列已经存在,则返回错误。 IPC_NOWAIT:读写消息队列要求无法满足时,不阻塞。
//返回值成功返回信号量集的标识码,失败返回-1
  • 控制信号量集
 int semctl(int semid, int semnum, int cmd, ...);
//semid 由emget得到的标识码
//semnum 信号集的信号量的序号
//cmd 将要采取的动作,SETVAL设置信号量集中信号量的计数值,GETVAL获取信号量集中的信号量计数值,IPC_STAT把msqid_ds结构的数据设置为信号量集的当前关联值, IPC_SET在权限允许的情况下,把信号量集的当前关联值设置为msqid_ds数据结构中给出的值,IPC_RMID删除信号量集
//最后参数,根据命令不同而不同
//返回值成功为0,失败为-1
  • 操作一个或者一组信号
 int semop(int semid, struct sembuf *sops, unsigned nsops);
 //semid 信号量集标识符
 //sops 指向结构体数值的指针
 //nsops 信号量的个数
 //返回值成功为0,失败为-1
  • sembuf结构体
struct sembuf{
           short          sem_num;  /* semaphore number */
           short          sem_op;   /* semaphore operation */
           short          sem_flg;  /* operation flags */
};
//sem_num是信号量的编号
//sem_op是信号量进行一次PV操作减的数值,一般为-1,+1分别对应P操作和V操作
//sem_flg的俩个值是IPC_NOWAIT和IPC_UNDO

代码实例:

sem.h:

#pragma once

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

#define TAPH "."
#define NUM 0x6666

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

int createSem(int nums);

int getSem(int nums);

int initSem(int semid,int nums,int initval);

int P(int semid,int who);

int V(int semid,int who);

int destroySem(int semid);

sem.c:

#include "sem.h"

int createSem(int nums)
{
    key_t key = ftok(TAPH,NUM);
    if(key < 0)
    {
        perror("ftok");
        return -1;
    }

    int semid = semget(key,nums,IPC_CREAT|IPC_EXCL|0666);
    if(semid < 0)
    {
        perror("semget");
        return -1;
    }
    return semid;
}

int getSem(int nums)
{
    key_t key = ftok(TAPH,NUM);
    if(key < 0)
    {
        perror("ftok");
        return -1;
    }

    int semid = semget(key,nums,IPC_CREAT);
    if(semid < 0)
    {
        perror("semget");
        return -1;
    }
    return semid;
}

int initSem(int semid,int nums,int initval)
{
    union semun _un;
    _un.val = initval;
    if(semctl(semid,nums,SETVAL,_un) < 0)
    {
        perror("semctl");
        return -1;
    }
    return 0;
}

static int commPV(int semid,int who,int op)
{
    struct sembuf buf;
    buf.sem_num = who;
    buf.sem_op = op;
    buf.sem_flg = 0;
    if(semop(semid,&buf,1) < 0)
    {
        perror("semop");
        return -1;
    }
    return 0;
}

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

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

int destroySem(int semid)
{
    if(semctl(semid,0,IPC_RMID) < 0)
    {
        perror("semctl");
        return -1;
    }
    return 0;

}

test.c:

#include "sem.h"

int main()
{
    int semid = createSem(1);
    initSem(semid,0,1);
    pid_t id = fork();
    if(id == 0)
    {
        int semid1 = getSem(0);
        while(1)
        {
            P(semid1,0);
            printf("A");
            fflush(stdout);
            usleep(123456);
            printf("A ");
            fflush(stdout);
            usleep(312456);
            V(semid1,0);
        }
    }
    else
    {
        while(1)
        {
            P(semid,0);
            printf("B");
            fflush(stdout);
            usleep(234561);
            printf("B ");
            fflush(stdout);
            usleep(121134);
            V(semid,0);
        }
        wait(NULL);
    }
    destroySem(semid);
    return 0;
}

Makefile:

test:test.c sem.c
    gcc $^ -o $@;

.PHONY:clean
clean:
    rm -f test 

没有PV操作前:

这里写图片描述

进行PV操作后:

这里写图片描述

PV操作规范了进程的执行,有了先后之分,相当与制定了规则,让所有的所需要对这块内存进行操作的进程有序且正常运行。

如果出现一下情况:
这里写图片描述

解决方法如下:
这里写图片描述

猜你喜欢

转载自blog.csdn.net/q496958148/article/details/79977093