信号量的有关概念:
信号量:主要用于同步与互斥。为了防止出现因多个进程访问临界资源而引发的一系列问题,信号量可以提供这样一种访问机制,在任一时刻只能有一个执行线程访问代码的临界区域,也就是说信号量是用来协调进程对临界资源的访问。
信号量的操作:信号量是一种特殊的变量,对信号量的访问必须是原子操作,信号量的操作只有两种: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