九、进程间通信-信号量

一、概述

1.信号量

  信号量本质上是一个计数器(不设置全局变量是因为进程间是相互独立的,而这不一定能看到,看到也不能保证++引用计数为原子操作),用于多进程对共享数据对象的读取,它和管道有所不同,它不以传送数据为主要目的,它主要是用来保护共享资源(信号量也属于临界资源),使得资源在一个时刻只有一个进程独享。

2.信号量的工作原理

由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:

(1)P(sv)

  如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行

(2)V(sv)

  如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.

在信号量进行PV操作时都为原子操作(因为它需要保护临界资源)

原子操作:单指令的操作称为原子的,单条指令的执行是不会被打断的

3、作用

  • 互斥
  • 同步
  • 若要在进程间传递数据需要结合共享内存

4、二元信号量

  二元信号量(Binary Semaphore)是最简单的一种锁(互斥锁),它只用两种状态:占用与非占用。所以它的引用计数为1。

5.进程如何获得共享资源

(1)测试控制该资源的信号量

(2)信号量的值为正,进程获得该资源的使用权,进程将信号量减1,表示它使用了一个资源单位

(3)若此时信号量的值为0,则进程进入挂起状态(进程状态改变),直到信号量的值大于0,若进程被唤醒则返回至第一步。

 

注:信号量通过同步与互斥保证访问资源的一致性。

二、信号量的用法

1、定义一个唯一key(ftok)

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

 key_t ftok(const char *pathname, int proj_id);

2、构造一个信号量(semget)

int semget(key_t key,int nsems,int semflg) //获取信号量ID

(1)参数:

  • key:信号量键值
  • nsems:信号量数量
  • semflg
    • IPC_CREATE:信号量不存在则创建
    • mode:信号量的权限

(2)返回值:

  • 成功:信号量ID.

  • 失败:-1

3、初始化信号量(semctl SETVA)

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

(1)参数

  • semid:信号量ID

  • semnum:信号量编号

  • cmd

    •  IPC_STAT:获取信号量的属性信息

    • IPC_SET:设置信号量的属性

    • IPC_RMID:删除信号量

    • IPC_SETVAL:设置信号量的值

  • arg:
  union semun
  {
      int val;
      struct semid_ds *buf;
  }

(2)返回值

  • 成功:由cmd类型决定。

  • 失败:-1

4、对信号量进行P/V操作(semop)

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

(1)参数

  • semid :信号量ID

  • sops:信号量操作结构体数组
 struct sembuf
  {
  ​	short sem_num;	//信号量编号
  ​	short sem_op;        //信号量P/V操作
  ​	short sem_flg;	       //信号量行为,SEM_UNDO
  }  
  • nsops:信号量数量

(2)返回值

  • 成功:0

  • 失败: -1

5、删除信号量(semctl RMID)

 #include <sys/types.h>
 #include <sys/ipc.h>
 #include <sys/sem.h>
//信号量控制操作
 int semctl(int semid, int semnum, int cmd, ...);

(1)参数

  • semid:信号量ID.

  • semnum:信号量数量

  • cmd:

    • IPC_STAT : 读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中。

    • IPC_SET   :   设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf参数。

    • IPC_RMID  : 将信号量集从内存中删除。

    • IPC_INFO  :  返回有关系统范围的信号量限制和参数的信息

    • GETALL     :用于读取信号量集中的所有信号量的值

    • GETNCNT :返回正在等待资源的进程数目。

    • GETPID     : 返回最后一个执行semop操作的进程的PID。

    • GETVAL    : 返回信号量集中的一个单个的信号量的值。

    • GETZCNT : 返回正在等待完全空闲的资源的进程数目。

    • SETALL    : 设置信号量集中的所有的信号量的值。

    • SETVAL   : 设置信号量集中的一个单独的信号量的值。

(2)返回值:

  • 失败:返回-1
  • 成功:非负值,返回值由cmd决定

三、实例

信号量用于子进程和父进程的同步

 

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

#define DELAY_TIME 3
union semun{
    int val;
    struct seimd_ds *buf;
};


/*初始化信号量*/
int init_sem(int sem_id,int init_value)
{
    union semun sem_union;
    sem_union.val = init_value;
      /*设置信号量集中的一个单独的信号量的值*/
    if(semctl(sem_id,0,SETVAL,sem_union) == -1) 
    {
        printf("Initialize semaphore failed!\n");
        return -1;
    }
    return 0;
}

/*删除信号量*/
int del_sem(int sem_id)
{
    union semun sem_union;
    if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
    {
        perror("Delete semaohore fail!\n");
        return -1;
    }
    return 0;
}

/*P 操作:执行P操作时,信号量-1*/  

int sem_p(int sem_id)
{
    struct sembuf sops;
    sops.sem_num = 0;
    sops.sem_op  = -1;
    sops.sem_flg = SEM_UNDO;/*系统自动释放将会在系统中残留的信号量*/

    if(semop(sem_id,&sops,1) == -1)
    {
        perror("P operation failed\n");
        return -1;
    }
    return 0;
}

/*V 操作:执行P操作时,信号量+1*/
int sem_v(int sem_id)
{
    struct sembuf sops;
    sops.sem_num = 0;   /*单个信号量的编号应该为 0*/
    sops.sem_op  = 1;    /*表示V操作*/
    sops.sem_flg = SEM_UNDO;  /*系统自动释放将会在系统中残留的信号量*/

    if(semop(sem_id,&sops,1) == -1)
    {
        perror("V operation failed\n");
        return -1;
    }
    return 0;
}

void main(void)
{
    pid_t result;
    int sem_id;

    sem_id = semget((key_t)6666,1,0666|IPC_CREAT);/*创建一个信号量*/

    init_sem(sem_id,0);  //初始化信号量的值为0,只能先执行V操作+1,然后才能执行P操作-1

    /*调用fork()函数*/
    result = fork();
    if(result == -1)
    {
        perror("Fork\n");
    }
    else if(result == 0)
    {
        printf("Child proess will wait for some seconds...\n");
        sleep(DELAY_TIME);  
        printf("the child process is running...\r\n");
        sem_v(sem_id); //如果有其他进程因等待信号量而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.
    }
    else /*返回值大于0代表父进程*/
    {
        sem_p(sem_id); //如果信号量等于0,则进程挂起,直到信号量大于0,执行V操作
        printf("the father process is running...\r\n");
        del_sem(sem_id);
    }
    
    exit(0);
}

执行结果:

 由于信号量的值初始化为0,所以父进程一致挂起不能进行P操作,等待子进程执行完毕V操作后,信号量+1,此时信号量大于0,可以进行P操作执行父进程。执行完父进程后删除信号量。

posted @ 2022-04-27 16:48  轻轻的吻  阅读(272)  评论(0编辑  收藏  举报