信号量的相关概念:
信号量
信号量本质上是一个计数器(不设置全局变量是因为进程间是相互独立的,而这不一定能看到,看到也不能保证++引用计数为原子操作),用于多进程对共享数据对象的读取,它和管道有所不同,它不以传送数据为主要目的,它主要是用来保护共享资源(信号量也属于临界资源),使得资源在一个时刻只有一个进程独享。
在了解信号量之前,我们先来看几个概念:
临界资源:两个进程看到的同一个公共的资源,但是同时只能被一个进程所使用的的资源叫做临界资源(互斥资源)
临界区:在晋城中涉及到互斥资源的程序段叫临界区
信号量主要用于同步和互斥,下面我们来看看什么是同步和互斥。
互斥:各个进程都要访问共享资源,但共享资源是互斥的,同时只能有一个进程使用。因此,各个进程之间竞争使用这些资源,将这种关系称为互斥。
同步:多个进程需要相互配合共同完成一项任务。
信号量的工作机制
简单说一下信号量的工作机制,可以直接理解成计数器,信号量会有初值(>0),每当有进程申请使用信号量,通过一个P操作来对信号量进行-1操作,当计数器减到0的时候就说明没有资源了,其他进程要想访问就必须等待,当该进程执行完这段工作(我们称之为临界区)之后,就会执行V操作来对信号量进行+1操作。
P:如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行
V:如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就+1。
在信号量进行PV操作时都为原子操作(因为它需要保护临界资源)
信号量的相关函数:
信号量结构体
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中等待的一个进程
改变其状态为就绪态
并将其插入就绪队列
}
}
信号量集结构
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
同消息队列类似,这里也可以使用ipcs -s查看IPC资源,使用ipcrm -s删除IPC资源:
信号量实例:
应用以上函数,编写一段代码:
创建Makefile:
test_sem:comm.c test_sem.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f test_sem
comm.h
#ifndef _COMM_H_
#define _COMM_H_
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
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*/
};
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 destorySemSet(int semid);
#endif
comm.c
#include "comm.h"
static int commSemSet(int nums, int flags)
{
key_t key = ftok("/tmp", 0x6666);
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 initSemSet(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) < 0)
{
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 destorySemSet(int semid)
{
if(semctl(semid, 0, IPC_RMID) < 0)
{
perror("semctl");
return -1;
}
return 0;
}
test_sem.c
#include "comm.h"
int main()
{
int semid = createSemSet(1);
initSemSet(semid, 0, 1);
pid_t pid = fork();
if(pid == 0)
{
//child
int _semid = getSemSet(0);
while(1)
{
P(_semid, 0);
printf("A");
fflush(stdout);
usleep(123456);
printf("A ");
fflush(stdout);
usleep(123456);
V(_semid, 0);
}
}
else
{
//parent
while(1)
{
P(semid, 0);
printf("B");
fflush(stdout);
usleep(123456);
printf("B ");
fflush(stdout);
usleep(123456);
V(semid, 0);
}
wait(NULL);
}
destorySemSet(semid);
return 0;
}
未使用PV操作时结果:
加入PV操作时的结果:
我们可以看到所有的AB都是成对出现的,不会出现交叉的情况。这是因为P、V操作实现过程中具有原子性,能够实现对临界区的管理,它的执行是不会受其他进程的影响。