1.信号量描述
信号量是用来同步进程的(即控制进程的执行),是一个特殊的变量,一般取正数值。它的值代表允许访问的资源数目。获取资源时,需要对信号量的值进行原子减一,该操作被称为 P 操作。当信号量值为 0 时,代表没有资源可用,P 操作会阻塞。释放资源时,需要对信号量的值进行原子加一,该操作被称为 V操作。信号量主要用来同步进程。信号量的值如果只取 0,1,将其称为二值信号量。如果信号量的值大于 1,则称之为计数信号量。
临界资源:同一时刻,只允许被一个进程或线程访问的资源
临界区:访问临界资源的代码段
2.信号量使用
2.1操作信号量的接口介绍:
#include <sys/sem.h>
#include <sys/types.h>
#include <sys/ipc.h>
/*
semget()创建或者获取已存在的信号量
semget()成功返回信号量的 ID, 失败返回-1
key:两个进程使用相同的 key 值,就可以使用同一个信号量
nsems:内核维护的是一个信号量集,在新建信号量时,其指定信号量集中信号量的个数
semflg 可选: IPC_CREAT IPC_EXCL
*/
int semget(key_t key, int nsems, int semflg);
/*
semop()对信号量进行改变,做 P 操作或者 V 操作
semop()成功返回 0,失败返回-1
nsops是信号量个数
struct sembuf
{
unsigned short sem_num; //指定信号量集中的信号量下标
short sem_op; //其值为-1,代表 P 操作,其值为 1,代表 V 操作
short sem_flg; //SEM_UNDO
};
*/
int semop( int semid, struct sembuf *sops, unsigned nsops);
/*
semctl()控制信号量
semctl()成功返回 0,失败返回-1
cmd 选项: SETVAL //将val的值同步到信号量上
IPC_RMID
union semun//信号量下标
{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *_buf;
};
*/
int semctl( int semid, int semnum, int cmd, ...);
2.2封装信号量的接口:
sem.h 的代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<sys/sem.h>
union semu{int val};
void sem_init();//创建并初始化,或者获取已有的信号量id
void sem_p();//p操作,信号量减一
void sem_v();//v操作,信号量加一
void sem_destroy();//销毁信号量
sem.c代码如下:
#include"sem.h"
static int semid = -1;
void sem_init()
{
semid = semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600);
if(semid == -1)//信号量已经存在
{
semid = semget((key_t)1234,1,0600);
}
else//信号量不存在,信号量被创建,在这里初始化信号量
{
union semun a;
a.val = 1;
if(semctl(semid,0,SETVAL,a)==-1)
{
perror("semctl error");
}
}
}
void sem_p()
{
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = -1;//P
buf.sem_flg = SEM_UNDO;
if(semop(semid,&buf,1)==-1)
{
perror("semop P error");
}
}
void sem_v()
{
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = 1;//V
buf.sem_flg = SEM_UNDO;
if(semop(semid,&buf,1)==-1)
{
perror("semop V error");
}
}
void sem_destroy()
{
if(semctl(semid,0,IPC_RMID) == -1)
{
perror("semtcl del error");
}
}
记不住怎么封装的也没关系,一般我们使用时,都是封装好的,我们只要知道怎么用信号量PV操作控制进程就好。
2.3例题演示:
例题(1):进程 a 和进程 b 模拟访问打印机,进程 a 输出第一个字符‘a’表示开始使用打印机,输出第二个字符‘a’表示结束使用,b 进程操作与 a 进程相同。(由于打印机同一时刻只能被一个进程使用,所以输出结果不应该出现 abab),如图所示:
a.c代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<unistd.h>
#include"sem.h"
int main()
{
sem_init();
for(int i = 0; i < 5; i++)
{
sem_p();
printf("a start\t");
sleep(3);
printf("a end\n");
sem_v();
sleep(3);
}
sleep(10);
sem_destroy();
exit(0);
}
a.c最后sleep(10)是为了使a程序最后结束,这样可以在a程序里销毁信号量。
b.c代码:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<unistd.h>
#include"sem.h"
int main()
{
sem_init();
for(int i = 0; i < 5; i++)
{
sem_p();
printf("b start\t");
sleep(3);
printf("b end\n");
sem_v();
sleep(3);
}
exit(0);
}
运行结果:
可以发现,由于打印机要求只有一个,所以a使用完打印机,b或者a才能继续使用打印机,不可能a和b同时使用打印机的。
(2)例题2:有一个空间,要求A进程先向里面写数据,B进程再从里面读数据,然后A进程再写…
分析1:一个信号量情况如下:
一眼看去发现没什么问题,但是仔细思考发现并没有对A写入数据进行控制,A写入数据不受信号量控制,A写完仍可以写。我们要求是A写完之后,B读完,A才可以再次写入的。
所以不可行。
分析2:两个信号量:
一个信号量限制A写入,一个信号量限制B读取
如下图:
3.ipcs/ipcrm 介绍
ipcs 可以查看消息队列、共享内存、信号量的使用情况,使用 ipcrm 可以进行删除操作。
如果我们将a.c程序中的销毁信号量的操作去掉,那么我们创建的信号量就没有销毁。a.c代码如下。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<unistd.h>
#include"sem.h"
int main()
{
sem_init();
for(int i = 0; i < 5; i++)
{
sem_p();
printf("a start\t");
sleep(3);
printf("a end\n");
sem_v();
sleep(3);
}
exit(0);
}
运行完a和b程序后,由于程序里没有对信号量进行销毁,所以我们创建的信号量仍然存在。可以用指令ipcs查看。
以下是运行完a和b程序后用指令ipcs查看的信号量信息:
key值是16进制,换成10进制,就是我们程序中设置的key值1234。
可以用指令ipcrm + -s + semid指令销毁信号量。
ipcrm -m是对共享内存用的;
ipcrm -q是对消息队列弄的;