信号量:是一种计数器,代表空闲的可用资源的数目。信号量本身也是临界资源,对临界资源进行保护。
由于是用于同步与互斥的,下面来看看同步与互斥的概念。
同步:直接制约关系,指多个进程(或线程)为了合作完成任务,必须按照规定的次序来运行。
互斥:间接制约关系,指系统中的某些共享资源,一次只能允许一个线程访问,当一个线程正在访问该临界资源时,其他的线程必须等待。
临界资源:系统中的某些资源一次只允许一个进程使用,称这些资源为临界资源或互斥资源。
临界区:在进程中涉及到互斥资源的程序段为临界区。
进程同步的前提条件:保证数据安全。
信号量与P、V原语:
P:表示申请资源
V:表示释放资源
.信号量:
1. 互斥:P、V在同一个进程中。
2. 同步:P、V不在同一个进程中。
信号量值的含义:
- S>0:S表示可用资源的个数。
- S=0:表示无可用资源,无等待进程。
- S<0:S的绝对值表示等待队列中进程个数。
信号量结构体伪代码:
struct semaphore
{
int value;
pointer_PCB queue;
}
信号量为一个整数,我们设这个信号量为:sem。很显然,我们规定在sem大于等于零的时候代表可供并发进程使用的资源实体数,sem小于零的时候,表示正在等待使用临界区的进程的个数。根据这个原则,在给信号量附初值的时候,我们显然就要设初值大于零。
p操作和v操作是不可中断的程序段,称为原语。P,V原语中P是荷兰语的Passeren,相当于英文的pass,V是荷兰语的Verhoog,相当于英文中的incremnet。
P原语操作的动作是:
(1) sem减1;
(2) 若sem减1后仍大于或等于零,则进程继续执行;
(3)若sem减1后小于零,则该进程被阻塞后进入与该信号相对应的队列中,然后转进程调度。
P(s)
{
s.value=s.value--;//申请了资源
if(s.value<0)
{
该进程状态置为等待状态
将该进程的PCB插入相应的等待队列s.queue末尾
}
}
V原语操作的动作是:
(1) sem加1;
(2) 若相加结果大于零,则进程继续执行;
(3)若相加结果小于或等于零,则从该信号的等待队列中唤醒一等待进程,然后再返回原进程继续执行或转进程调度。
V原语:
V(s)
{
s.value=s.value++;//释放了资源
if(s.value<=0)
{
唤醒相应等待队列s.queue中等待的一个进程
改变其状态为就绪态
并将其插入就绪队列
}
}
信号量集:
POSIX IPC标准对信号量的的要求并不高:信号量(sem_init)、命名信号量(sem_open);
System V IPC要求信号量必须是一个集合,即:信号量集;
信号量集和信号量一样,都是为了控制多个进程对共享资源的同步访问而引入的同步对象;System V IPC中规定:不能只单独定义一个信号量,而是只能定义一个信号量的集合,即:信号量集,其中包含一组信号量,同一信号量集中的多个信号量使用同一个唯一的ID来引用,这样做的目的是为了对多个共享资源进行同步控制的需要。
信号量集结构:
struct semid_ds{
struct ipc_perm sem_perm;
time_t sem_otime;//最后一次semop()操作的时间 */
time_t sem_ctime;//最后一次改动此数据结构的时间 */
unsigned short sem_nsems;//信号量集(数组)中的信号量的个数 */
};
信号量集函数:
1.semget函数:
- 功能:用来创建和访问一个信号量集
函数原型:
int semget(key_t key,int nsems,int semflg);
参数:
参数 | 作用 |
---|---|
key | 信号集的名字 |
nsems | 信号集中信号的个数 |
semflg | 由9个权限标志构成,他们的用法和创建和mode 模式标志一样 |
4. 返回值:成功返回一个非负整数,即该信号集标识码;失败返回-1.
2.semctl函数:
- 功能:用于控制信号量集
- 原型:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid,int semum,int cmd,...)
3. 参数:
参数 | 作用 |
---|---|
semid | 由semget函数返回的信号集标识码 |
semum | 信号集中信号量的序号 |
cmd | 将要采取的动作 |
如果有第四个参数,它通常是一个union semum结构,定义如下:
union semun {
int val;
struct semid_ds *buf;
unsigned short *arry;
};
4. 返回值:成功返回0,失败返回-1.
5. cmd的可取值:
命令 | 说明 |
---|---|
SETVAL | 设置信号量集中的信号量的计数值 |
GETVAL | 获取信号量集中的信号量的计数值 |
IPC_STAT | 把semid_ds结构中的数据设置为信号集的当前关联值 |
IPC_SET | 在进程有足够权限的前提下,把信号集的当前关联值设置为semid_ds数据结构中给出的值 |
IPC_RMID | 删除信号集 |
3.semop函数:
- 功能:改变信号量的值
- 原型:
int semop(int semid,struct sembuf*sops,unsigned nsops);
- 参数:
参数 | 作用 |
---|---|
semid | 由semget函数返回的信号集标识码 |
sops | 是个指向一个结构数值的指针 |
nsops | 信号量的个数 |
struct sembuf{
short sem_num; // 信号量的编号
short sem_op; // 信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,
// 一个是+1,即V(发送信号)操作。
short sem_flg; // 通常为SEM_UNDO,使操作系统跟踪信号,
// 并在进程没有释放该信号量而终止时,操作系统释放信号量
};
返回值:成功返回0,失败返回-1.
代码展示:
comm.h:
1 #pragma once
2 #include<stdio.h>
3 #include <sys/types.h>
4 #include <sys/ipc.h>
5 #include <sys/sem.h>
6
7 #define PATHNAME "."
8 #define PROJ_ID 0x6666
9
10 union semun {
11 int val;
12 struct semid_ds *buf;
13 unsigned short *array;
14 struct seminfo *__buf;
15 };
16
17 int commSemset(int nsems,int flags);
18 int createSemset(int nsems);//创建
19 int getSemset(int nsems);//获取
20 int commPV(int semid,int who,int op);
21 int initSemset(int semid,int semnum,int initval);//初始化
22 int P(int semid,int who);
23 int V(int semid,int who);
24 int destroySemset(int semid,int semum);
~
comm.c:
#include"comm.h"
2 int commSemset(int nsems,int flags)
3 {
4 key_t key=ftok(PATHNAME,PROJ_ID);
5 if(key<0){
6 perror("ftok");
7 return -1;
8 }
9 int semid=semget(key,nsems,flags);
10 if(semid<0){
11 perror("semget");
12 return -1;
13 }
14 return semid;
15 }
16 int createSemset(int nsems)
17 {
18 return commSemset(nsems,IPC_CREAT|IPC_EXCL|0666);
19 }
20 int getSemset(int nsems)
21 {
22 return commSemset(nsems,IPC_CREAT);
23 }
24 int initSemset(int semid,int semnum,int initval)
25 {
26 union semun un;
27 un.val=initval;
28 int ret=semctl(semid,semnum,SETVAL,un);
29 if(ret<0){
30 perror("semctl");
31 return -1;
32 }
33 return 0;//sucess
34 }
35 int commPV(int semid,int who,int op)
36 {
37 struct sembuf buf;
38 buf.sem_num=who;
39 buf.sem_flg=0;
40 buf.sem_op=op;
41 if(semop(semid,&buf,1)<0){
42 perror("semop");
43 return -1;
44 }
45 return 0;
46 }
47 int P(int semid,int who)
48 {
49 return commPV(semid,who,-1);
50 }
51 int V(int semid,int who)
52 {
53 return commPV(semid,who,1);
54 }
55 int destroySemset(int semid,int semnum)
56 {
57 int ret=semctl(semid,semnum,IPC_RMID);
58 if(ret<0){
59 perror("semctl");
60 return -1;
61 }
62 }
63
sem.c:
#include"comm.h"
2
3 int main()
4 {
5 int semid=createSemset(1);
6 initSemset(semid,0,1);
7 pid_t pid=fork();
8 if(pid==0){//child
9 int _semid=getSemset(1);//该函数的参数不能大于createSemSet的参数值
10 while(1){
11 P(_semid,0);
12 printf("A");
13 fflush(stdout);
14 sleep(1);
15 printf("A");
16 fflush(stdout);
17 sleep(1);
18 V(_semid,0);
19 }
20 }
21 else{
22 while(1){
23 P(semid,0);
24 printf("B");
25 fflush(stdout);
26 sleep(1);
27 printf("B");
28 fflush(stdout);
29 sleep(1);
30 V(semid,0);
31 }
32 wait(NULL);
33 }
34 destroySemset(semid,0);
35 return 0;
36 }
我们在加上 P V 操作,用二元信号量保护临界资源(显示器),就不会出现交叉现象了。
若是去掉P、V操作,则出现交叉的情况:
ipcs -s :显示信号量集
ipcrm -s+semid / ipcrm -S+key:删除信号量集