进程间通信(信号量)

信号量:是一种计数器,代表空闲的可用资源的数目。信号量本身也是临界资源,对临界资源进行保护。
由于是用于同步与互斥的,下面来看看同步与互斥的概念。
同步:直接制约关系,指多个进程(或线程)为了合作完成任务,必须按照规定的次序来运行。

互斥:间接制约关系,指系统中的某些共享资源,一次只能允许一个线程访问,当一个线程正在访问该临界资源时,其他的线程必须等待。

临界资源:系统中的某些资源一次只允许一个进程使用,称这些资源为临界资源或互斥资源。
临界区:在进程中涉及到互斥资源的程序段为临界区。

进程同步的前提条件:保证数据安全。

信号量与P、V原语:
P:表示申请资源
V:表示释放资源
.信号量:

 1. 互斥:P、V在同一个进程中。
 2. 同步:P、V不在同一个进程中。

信号量值的含义:

  1. S>0:S表示可用资源的个数。
  2. S=0:表示无可用资源,无等待进程。
  3. S<0:S的绝对值表示等待队列中进程个数。

信号量结构体伪代码:

struct  semaphore
{
    int value;
    pointer_PCB queue;
}

信号量为一个整数,我们设这个信号量为:sem。很显然,我们规定在sem大于等于零的时候代表可供并发进程使用的资源实体数,sem小于零的时候,表示正在等待使用临界区的进程的个数。根据这个原则,在给信号量附初值的时候,我们显然就要设初值大于零。

p操作和v操作是不可中断的程序段,称为原语。P,V原语中P是荷兰语的Passeren,相当于英文的pass,V是荷兰语的Verhoog,相当于英文中的incremnet。

P原语操作的动作是:

(1) sem减1;

扫描二维码关注公众号,回复: 1076219 查看本文章

(2) 若sem减1后仍大于或等于零,则进程继续执行;

(3)若sem减1后小于零,则该进程被阻塞后进入与该信号相对应的队列中,然后转进程调度。

P(s)
{
    s.value=s.value--;//申请了资源
    if(s.value<0)
    {
        该进程状态置为等待状态
        将该进程的PCB插入相应的等待队列s.queue末尾
    }
}

V原语操作的动作是:

(1) sem加1;

(2) 若相加结果大于零,则进程继续执行;

(3)若相加结果小于或等于零,则从该信号的等待队列中唤醒一等待进程,然后再返回原进程继续执行或转进程调度。
V原语:

V(s)
{
    s.value=s.value++;//释放了资源
    if(s.value<=0)
    {
        唤醒相应等待队列s.queue中等待的一个进程
        改变其状态为就绪态
        并将其插入就绪队列
    }
}

信号量集:
POSIX IPC标准对信号量的的要求并不高:信号量(sem_init)、命名信号量(sem_open);
System V IPC要求信号量必须是一个集合,即:信号量集;
信号量集和信号量一样,都是为了控制多个进程对共享资源的同步访问而引入的同步对象;System V IPC中规定:不能只单独定义一个信号量,而是只能定义一个信号量的集合,即:信号量集,其中包含一组信号量,同一信号量集中的多个信号量使用同一个唯一的ID来引用,这样做的目的是为了对多个共享资源进行同步控制的需要。
信号量集结构:

struct semid_ds{
    struct ipc_perm sem_perm;
    time_t          sem_otime;//最后一次semop()操作的时间 */
    time_t          sem_ctime;//最后一次改动此数据结构的时间 */
    unsigned  short sem_nsems;//信号量集(数组)中的信号量的个数 */
};

信号量集函数:

1.semget函数:

  1. 功能:用来创建和访问一个信号量集
  2. 函数原型:int semget(key_t key,int nsems,int semflg);

  3. 参数:

参数 作用
key 信号集的名字
nsems 信号集中信号的个数
semflg 由9个权限标志构成,他们的用法和创建和mode 模式标志一样

4. 返回值:成功返回一个非负整数,即该信号集标识码;失败返回-1.

2.semctl函数:

  1. 功能:用于控制信号量集
  2. 原型:
 #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/sem.h>

int semctl(int semid,int semum,int cmd,...)
3. 参数:

参数 作用
semid 由semget函数返回的信号集标识码
semum 信号集中信号量的序号
cmd 将要采取的动作

如果有第四个参数,它通常是一个union semum结构,定义如下:
union semun {
int val;
struct semid_ds *buf;
unsigned short *arry;
};

4. 返回值:成功返回0,失败返回-1.
5. cmd的可取值:

命令 说明
SETVAL 设置信号量集中的信号量的计数值
GETVAL 获取信号量集中的信号量的计数值
IPC_STAT 把semid_ds结构中的数据设置为信号集的当前关联值
IPC_SET 在进程有足够权限的前提下,把信号集的当前关联值设置为semid_ds数据结构中给出的值
IPC_RMID 删除信号集

3.semop函数:

  1. 功能:改变信号量的值
  2. 原型:int semop(int semid,struct sembuf*sops,unsigned nsops);
  3. 参数:
参数 作用
semid 由semget函数返回的信号集标识码
sops 是个指向一个结构数值的指针
nsops 信号量的个数
struct sembuf{
    short sem_num; // 信号量的编号
    short sem_op;  // 信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,
                   // 一个是+1,即V(发送信号)操作。
    short sem_flg; // 通常为SEM_UNDO,使操作系统跟踪信号,
                   // 并在进程没有释放该信号量而终止时,操作系统释放信号量
};

返回值:成功返回0,失败返回-1.

代码展示:

comm.h:

  1 #pragma once
  2 #include<stdio.h>
  3 #include <sys/types.h>
  4 #include <sys/ipc.h>
  5 #include <sys/sem.h>
  6 
  7 #define PATHNAME "."
  8 #define PROJ_ID 0x6666
  9 
 10 union semun {
 11          int              val;
 12           struct semid_ds *buf;
 13            unsigned short  *array;
 14               struct seminfo  *__buf;
 15         };
 16 
 17 int commSemset(int nsems,int flags);
 18 int createSemset(int nsems);//创建
 19 int getSemset(int nsems);//获取
 20 int commPV(int semid,int who,int op);
 21 int initSemset(int semid,int semnum,int initval);//初始化
 22 int P(int semid,int who);
 23 int V(int semid,int who);
 24 int destroySemset(int semid,int semum);
~                                                

comm.c:

#include"comm.h"
  2 int commSemset(int nsems,int flags)
  3 {
  4         key_t key=ftok(PATHNAME,PROJ_ID);
  5         if(key<0){
  6                 perror("ftok");
  7                 return -1;
  8         }
  9         int semid=semget(key,nsems,flags);
 10         if(semid<0){
 11                 perror("semget");
 12                 return -1;
 13         }
 14         return semid;
 15 }
 16 int createSemset(int nsems)
 17 {
 18         return commSemset(nsems,IPC_CREAT|IPC_EXCL|0666);
 19 }
 20 int getSemset(int nsems)
 21 {
 22         return commSemset(nsems,IPC_CREAT);
 23 }
 24 int initSemset(int semid,int semnum,int initval)
 25 {
 26         union semun un;
 27         un.val=initval;
 28         int ret=semctl(semid,semnum,SETVAL,un);
 29         if(ret<0){
 30                 perror("semctl");
 31                 return -1;
 32         }
 33         return 0;//sucess
 34 }
 35 int commPV(int semid,int who,int op)
 36 {
 37         struct sembuf buf;
 38         buf.sem_num=who;
 39         buf.sem_flg=0;
 40         buf.sem_op=op;
 41         if(semop(semid,&buf,1)<0){
 42                 perror("semop");
 43                 return -1;
 44         }
 45         return 0;
 46 }
 47 int P(int semid,int who)
 48 {
 49         return commPV(semid,who,-1);
 50 }
 51 int V(int semid,int who)
 52 {
 53         return commPV(semid,who,1);
 54 }
 55 int destroySemset(int semid,int semnum)
 56 {
 57         int ret=semctl(semid,semnum,IPC_RMID);
 58         if(ret<0){
 59                 perror("semctl");
 60                 return -1;
 61         }
 62 }
 63 

sem.c:

#include"comm.h"
  2 
  3 int main()
  4 {
  5         int semid=createSemset(1);
  6         initSemset(semid,0,1);
  7         pid_t pid=fork();
  8         if(pid==0){//child
  9                 int _semid=getSemset(1);//该函数的参数不能大于createSemSet的参数值
 10                 while(1){
 11                         P(_semid,0);
 12                         printf("A");
 13                         fflush(stdout);
 14                         sleep(1);
 15                         printf("A");
 16                         fflush(stdout);
 17                         sleep(1);
 18                         V(_semid,0);
 19                 }
 20         }
 21         else{
 22                         while(1){
 23                         P(semid,0);
 24                         printf("B");
 25                         fflush(stdout);
 26                         sleep(1);
 27                         printf("B");
 28                         fflush(stdout);
 29                         sleep(1);
 30                         V(semid,0);
 31                 }
 32                 wait(NULL);
 33         }
 34         destroySemset(semid,0);
 35         return 0;
 36 }

我们在加上 P V 操作,用二元信号量保护临界资源(显示器),就不会出现交叉现象了。
这里写图片描述

若是去掉P、V操作,则出现交叉的情况:
这里写图片描述

ipcs -s :显示信号量集
ipcrm -s+semid / ipcrm -S+key:删除信号量集

猜你喜欢

转载自blog.csdn.net/xiaodu655/article/details/80210085