进程间通信---信号量

信号量的有关概念:

信号量:主要用于同步与互斥。为了防止出现因多个进程访问临界资源而引发的一系列问题,信号量可以提供这样一种访问机制,在任一时刻只能有一个执行线程访问代码的临界区域,也就是说信号量是用来协调进程对临界资源的访问。

信号量的操作:信号量是一种特殊的变量,对信号量的访问必须是原子操作,信号量的操作只有两种:P操作(-1,申请资源)和V操作(+1,释放资源)。最简单的信号量只有两种取值0和1,称这样的信号量为二元信号量。可以取值为正整数N的信号量称为多元信号量,它允许多个线程并发的访问资源。

临界资源:能被多个进程共享,但一次只能允许一个进程使用的资源称为临界资源。

临界区:涉及到临界资源的部分代码,称为临界区。

互斥:亦称间接制约关系,在一个进程的访问周期内,另一个进程就不能进行访问,必须进行等待。当占用临界资源的进程退出临界区后,另一个进程才允许去访问此临界资源。

例如,在仅有一台打印机的系统中,有两个进程A和进程B,如果进程A需要打印时, 系统已将打印机分配给进程B,则进程A必须阻塞。一旦进程B将打印机释放,系统便将进程A唤醒,并将其由阻塞状态变为就绪状态。

同步:亦称直接制约关系,它是指为完成某种任务而建立的两个或多个进程,这些进程因为需要在某些位置上协调它们的工作次序而等待、传递信息所产生的制约关系。进程间的同步就是源于它们之间的相互合作。所谓同步其实就是两个进程间的制约关系。

例如,输入进程A通过单缓冲向进程B提供数据。当该缓冲区空时,进程B不能获得所需数据而阻塞,一旦进程A将数据送入缓冲区,进程B被唤醒。反之,当缓冲区满时,进程A被阻塞,仅当进程B取走缓冲数据时,才唤醒进程A。

原子性:对于进程的访问,只有两种状态,要么访问完了,要么不访问。当一个进程在访问某种资源的时候,即便该进程切出去,另一个进程也不能进行访问。


小结:
  • 信号量本质上是一个能被两个进程或多个进程都能看到的计数器。这个计数器用来表示临界资源的多少,计数器不以传输数据为目的,以保护临界资源为目的。
  • 信号量是进程间通信的一种,本身是一种临界资源,所以必须也要保护信号量的安全性。
  • 信号量的PV操作必须是原子操作。P操作:-1,表示申请资源成功;V操作:+1,表示释放资源成功。
  • 两个进程共享一个二元信号量sem,它的过程是这样的:其中一个进程执行了P操作(sem-1),它将得到信号量,并可以进入临界区,此时sem=0。另一个进程请求临界资源(P操作)的时候,他将被阻止进入临界区,并挂起等待,当第一个进程离开临界区并执行了V操作(sem+1)释放了资源的时候,第二个进程才可以恢复执行。
信号量结构体的伪代码:
struct semaphore{
    int value;
    pointer_PCB queue;
 };
信号量集结构:usr/include/linux/sem.h
struct semid_ds {
	struct ipc_perm	sem_perm;		/* permissions .. see ipc.h */
	__kernel_time_t	sem_otime;		/* last semop time */
	__kernel_time_t	sem_ctime;		/* last change time */
	struct sem	*sem_base;		/* ptr to first semaphore in array */
	struct sem_queue *sem_pending;		/* pending operations to be processed */
	struct sem_queue **sem_pending_last;	/* last pending operation */
	struct sem_undo	*undo;			/* undo requests on this array */
	unsigned short	sem_nsems;		/* no. of semaphores in array */
};


信号量集函数:

semget函数:用来创建和访问一个信号量集

头文件:

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

 函数原型:
    int semget(key_t key, int nsems, int semflg);
参数:
    key:建值,信号量集的名字
    nsems:信号量集中信号量的个数
    semflg:标识函数的行为及权限。取值如下:        
           IPC_CREAT:如果不存在就创建
           IPC_EXCL:和IPC_CREAT搭配使用,如果已经存在,则返回失败
           权限位:设置共享内存的访问权限  
返回值:成功返回一个非负整数,信号量集的标识码;失败返回-1.

shmctl函数:用于控制信号量集

头文件:

#include <sys/types.h>

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

函数原型:
     int semctl(int semid, int semnum, int cmd, ...);
参数:
    semid:由semget返回的信号量集的标识码
    semnum:信号量集中信号量的序号
    cmd: 将要采取的动作,取值如下:
        SETVAL:设置信号量集中信号量的计数值
        GETVAL:获取信号量集中信号量的计数值
        IPC_STAT:把semid_ds结构中的数据设置为信号量集的当前关联值
        IPC_SET:在进程有足够权限的前提下,把信号集的当前关联值设置为semid_ds数据结构中给出的值
        IPC_RMID:删除信号量集
    最后一个参数根据命令的不同而不同
返回值:成功返回0;失败返回-1

semop函数:用来创建和访问一个信号量集

头文件:

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

函数原型:
     int semop(int semid, struct sembuf *sops, unsigned nsops);
参数:
    semid:由semget返回的信号量集的标识码
    sops:指向一个信号量结构体的指针
    nsops:信号量的个数
返回值:成功返回0;失败返回-1.

struct sembuf{   
     unsigned short sem_num;  //信号量的编号
     short          sem_op;   //信号量一次PV操作时加减的数值,一般只会用到两个值:
                              //-1:P操作,等待信号变得可用;+1:V操作,发出信号量已经变得可用
     short          sem_flg;  //标志,有两个取值:
                              //IPC_NOWAIT:表示队列满不等待,非阻塞,返回EAGAIN错误。
                              //IPC_UNDO: 使操作系统跟踪信号,并在进程没有释放该信号量而终止时,操作系统释放信号量 

};

信号量实例:

使用二元信号量对临界资源进行保护。此时的临界资源为显示屏,两个进程同时打印,一个进程打印的同时,另一个进程就不能打印,所以打印出的是成对的A和成对的B。若去掉信号量的保护,则两个进程交替打印,打印的结果可能为:AB BA AA AB BB...

comm.h

#ifndef _COMM_H_
#define _COMM_H_ 

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

#define PATHNAME "."
#define PROJ_ID 0x0001

//semctl中有这个结构体
union semun{
  int val;
  struct semid_ds *buf;
  unsigned short *aray;
  struct seminfo *__buf;
};

int CreateSemSet(int nums);
int InitSem(int semid,int nums,int initVal);
int GetSemSet(int nums);
int P(int semid,int who);
int V(int semid,int who);
int DestroySemSet(int semid);

#endif 

comm.c

#include"comm.h"

static int CommSemSet(int nums,int flags){
  key_t key = ftok(PATHNAME,PROJ_ID);
  if(key < 0){
    perror("ftok");
    return -1;
  }
  int semid = semget(key,nums,flags);
  if(semid < 0){
    perror("semget");
    return -2;
  }
  return semid;
}

int CreateSemSet(int nums){
  return CommSemSet(nums,IPC_CREAT|IPC_EXCL|0666);
}

int GetSemSet(int nums){
  return CommSemSet(nums,IPC_CREAT);
}

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 sf;
  sf.sem_num = who;
  sf.sem_op = op;
  sf.sem_flg = 0;
  if(semop(semid, &sf, 1)){
    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 DestroySemSet(int semid){
  if(semctl(semid, 0, IPC_RMID) < 0){
    perror("semctl");
    return -1;
  }
  return 0;
}

运行结果:



显示系统当前的信号量资源:ipcs -s

删除semid的信号量:ipcrm -s semid



System V IPC总结:

1.创建IPC :XXXget()   ,如创建消息队列msgget,创建共享内存shmget,创建信号量semget

2.删除IPC : XXXctl() ,第三个参数为IPC_RMID ,如msgctl , shmctl , semctl



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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值