进程间通信之信号量通信

概念

信号量

  • 信号量和P、V原语由Dijkstra(迪杰斯特拉)提出
  • 互斥:P、V在同一个进程中
  • 同步:P、V在不同进程中
  • 信号量值含义
    • S>0:S表示可用资源的个数
    • S=0:表示无可用资源,无等待进程
    • S<0:|S|表示等待队列中进程个数
  • 信号量的数据结构–注意不是信号量集合
struct semaphore
{
    int value;
    pointer_PCB queue;
}

P原语

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

V原语

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

相关API

描述信号量集合的数据结构

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

semget函数

  • 功能:用来创建和访问一个信号量集
  • 原型
    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

semop函数

  • 功能:用来创建和访问一个信号量集
  • 原型
    int semop(int semid, struct sembuf *sops, unsigned nsops);
  • 参数
    • semid:是该信号量的标识码,也就是semget函数的返回值
    • sops:是个指向一个结构数值的指针
    • nsops:信号量的个数
  • 返回值:成功返回0;失败返回-1
  • sembuf结构体:
    struct sembuf {
        short sem_num;
        short sem_op;
        short sem_flg;
    };
  • sem_num是信号量的编号。
  • sem_op是信号量一次PV操作时加减的数值,一般只会用到两个值,一个是“-1”,也就是P操作,等待信号量变得可用;另一个是“+1”,也就是我们的V操作,发出信号量已经变得可用
  • sem_flag的两个取值是IPC_NOWAIT或SEM_UNDO
  • 还要注意struct sembuf 里面的sem_flg如果为SEM_UNDO,则会自动撤销之前的p或v操作,是信号量的计数值恢复pv操作之前的值!

示例代码

封装了很多必要的操作,简化了API的调用过程,可以直接在以后的项目中使用!

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>

#include <sys/time.h>
#include <sys/mman.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>

#define SEM_NUM 1


typedef union semun {
   int              val;    /* Value for SETVAL */
   struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
   unsigned short  *array;  /* Array for GETALL, SETALL */
   struct seminfo  *__buf;  /* Buffer for IPC_INFO
                               (Linux-specific) */
}semun_t;

#if 0
typedef struct sembuf{
  unsigned short sem_num;  /* semaphore number */
  short          sem_op;   /* semaphore operation */
  short          sem_flg;  /* operation flags */
}sembuf_t;
#endif

typedef struct sembuf sembuf_t;

//封装创建信号量集合的代码
int sem_create(key_t key)
{
  int sem_id = 0;

  sem_id = semget(key,SEM_NUM,0666|IPC_CREAT|IPC_EXCL);
  if(sem_id == -1)
  {
    perror("semget");
    if(errno == EEXIST)
    {
      printf("Judge by self..exist\n");
    }
    return -1;
  }

  return sem_id;
}

//封装打开已存在的信号量集合
int sem_open(key_t key)
{
  int sem_id = 0;

  sem_id = semget(key,SEM_NUM,0666);
  if(sem_id == -1)
  {
    //perror("semget");
    return -1;
  }
  return sem_id;
}

//设置信号量集合里面的信号量的计数值
int sem_set(int semid,int val)
{
  int ret = 0;

  if(semid < 0)
  {
    return -1;
  }
  semun_t su;
  su.val = val;
  ret = semctl(semid,0,SETVAL,su);
  return ret;
}

//获取信号量集合里面的信号量的计数值
int sem_get(int semid,int* val)
{
  int ret = 0;
  if(semid < 0)
  {
    return -1;
  }
  semun_t su;
  ret = semctl(semid,0,GETVAL,su);
  *val = su.val;
  return ret;
}


//原子p操作
int sem_p(int semid)
{
  int ret = 0;

  sembuf_t buf = {0,-1,0};//一般默认是从第0个信号量开始,第三个参数默认是0表示阻塞模式

  ret = semop(semid,&buf,1 );//只操作一个信号量

  return ret;
}

//原子v操作
int sem_v(int semid)
{
  int ret = 0;

  sembuf_t buf = {0,+1,0};//一般默认是从第0个信号量开始,第三个参数默认是0表示阻塞模式

  ret = semop(semid,&buf,1);//只操作一个信号量

  return ret;
}

int main()
{
  int sem_id = 0;//信号量集ID
  int key;//用来创建信号量集的key
  int tmp_val = 1024;//临时变量--用来测试设置和获取信号量的计数值
  pid_t pid ;  
  int i= 0;
  key = ftok("./",'c');//获取key

  //利用获取的key打开/创建信号量集合
  sem_id = sem_open(key);
  if(sem_id == -1)
    sem_id = sem_create(key);

  //设置信号量集合中的信号量(元素)的计数值
  //设置为1表示一个进程使用临界区--执行p操作以后,还为0--表示没有进程等待--当前进程可以执行临界区代码
  //设置为0表示当前进程也要被阻塞,--因为在执行P操作以后,计数值小于0,会将当前进程放到等待队列
  //实际上这里设置为多少,表示有多少个进程可以并发执行临界区代码
  sem_set(sem_id,1);

  //获取信号量集合中的信号量(元素)的计数值,存储到临时变量tmp_val并打印
  sem_get(sem_id,&tmp_val);
  printf("tmp_val:%d\n",tmp_val);

  pid = fork();
  if(pid == -1)
  {
    perror("fork");
    exit(-1);
  }

  //p操作--类似上锁--保证一个进程结束另一个进程才开始执行
  sem_p(sem_id);

    printf("my pid:%d\t i = %d\n",getpid(),i++);
    printf("my pid:%d\t i = %d\n",getpid(),i++);

  //v操作--类似解锁
  sem_v(sem_id);

  return 0;
}

同时说明,fork以后父子进程的代码段是有猫腻的,有时间在总结,如果按照普通的说法,子进程复制了父进程的代码段、数据段、堆栈段,则子进程和父进程各自拥有自己的代码段,也就不会有临界区一说了!不会发生上述程序的现象!不会发生互斥!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值