前言:
信号量的相关概念:
信号量:主要用于同步与互斥。为了防止出现因多个进程访问临界资源而引发的一系列问题,信号量可以提供这样一种访问机制,在任一时刻只能有一个执行线程访问代码的临界区域,也就是说信号量是用来协调进程对临界资源的访问。
信号量的操作:信号量是一种特殊的变量,对信号量的访问必须是原子操作,信号量的操作只有两种:P操作(-1,申请资源)和V操作(+1,释放资源)。最简单的信号量只有两种取值0和1,称这样的信号量为二元信号量。可以取值为正整数N的信号量称为多元信号量,它允许多个线程并发的访问资源。
临界资源:能被多个进程共享,但一次只能允许一个进程使用的资源称为临界资源。
临界区:涉及到临界资源的部分代码,称为临界区。
互斥:亦称间接制约关系,在一个进程的访问周期内,另一个进程就不能进行访问,必须进行等待。当占用临界资源的进程退出临界区后,另一个进程才允许去访问此临界资源。
例如,在仅有一台打印机的系统中,有两个进程A和进程B,如果进程A需要打印时, 系统已将打印机分配给进程B,则进程A必须阻塞。一旦进程B将打印机释放,系统便将进程A唤醒,并将其由阻塞状态变为就绪状态。
同步:亦称直接制约关系,它是指为完成某种任务而建立的两个或多个进程,这些进程因为需要在某些位置上协调它们的工作次序而等待、传递信息所产生的制约关系。进程间的同步就是源于它们之间的相互合作。所谓同步其实就是两个进程间的制约关系。
例如,输入进程A通过单缓冲向进程B提供数据。当该缓冲区空时,进程B不能获得所需数据而阻塞,一旦进程A将数据送入缓冲区,进程B被唤醒。反之,当缓冲区满时,进程A被阻塞,仅当进程B取走缓冲数据时,才唤醒进程A。
原子性:对于进程的访问,只有两种状态,要么访问完了,要么不访问。当一个进程在访问某种资源的时候,即便该进程切出去,另一个进程也不能进行访问。
总结:
(1)信号量本质上是一个能被两个进程或多个进程都能看到的计数器。这个计数器用来表示临界资源的多少,计数器不以传输数据为目的,以保护临界资源为目的。
(2)信号量是进程间通信的一种,本身是一种临界资源,所以必须也要保护信号量的安全性。
(3)信号量的PV操作必须是原子操作。P操作:-1,表示申请资源成功;V操作:+1,表示释放资源成功。
(4)两个进程共享一个二元信号量sem,它的过程是这样的:其中一个进程执行了P操作(sem-1),它将得到信号量,并可以进入临界区,此时sem=0。另一个进程请求临界资源(P操作)的时候,他将被阻止进入临界区,并挂起等待,当第一个进程离开临界区并执行了V操作(sem+1)释放了资源的时候,第二个进程才可以恢复执行。
1.信号量集函数:
(1)semget
函数:
函数原型:
int semget(key_t key, int nsems, int semflg);
作用:用来创建和访问一个信号量集。
参数详解:
key
:建值,信号量集的名字
nsems
:信号量集中信号量的个数
semflg
:标识函数的行为及权限。取值如下:
IPC_CREAT
:如果不存在就创建
IPC_EXCL
和IPC_CREAT
搭配使用,如果已经存在,则返回失败
权限位:设置共享内存的访问权限
返回值:成功返回一个非负整数,即信号量集的标识码,失败返回-1
(2)semctl
函数:
函数原型:
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
SETVAL
:初始化信号量的值(信号量成功创建后,需要设置初始值),这个值由第四个参数决定。第四参数是一个自定义的共同体,如下:
// 用于信号等操作的共同体。
union semun
{
int val;
struct semid_ds *buf;
unsigned short *arry;
};
销毁一个信号量的做法:
semctl(semid,0,IPC_RMID);
(3)semop
函数
函数原型:
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: 使操作系统跟踪信号,并在进程没有释放该信号量而终止时,操作系统释放信号量
};
2.函数介绍完毕,那么现在直接上demo
semdemo17
.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
//int semget(key_t key, int nsems, int semflg);
//int semctl(int semid, int semnum, int cmd, ...);
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
(Linux-specific) */
};
int main()
{
int semid;
int pid;
key_t key;
key = ftok(".",125);
//信号量集中的信号量的个数
semid = semget(key,1,IPC_CREAT|0666);//获取或创建信号量
if(semid == -1)
{
printf("sorry get the pid error!\n");
}
union semun set;
set.val = 1;
//信号量集中的第几个信号
semctl(semid,0,SETVAL,set);//初始化信号量
//SETVAL设置信号量的值,这里设置为set
pid = fork();
if(pid > 0)
{
printf("this is father!\n");
}
else if(pid == 0)
{
printf("this is child\n");
}
else
{
printf("Sorry creat fork error\n");
}
return 0;
}
这个demo
用于初认识信号量。
semdemo18
.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
//int semget(key_t key, int nsems, int semflg);
//int semctl(int semid, int semnum, int cmd, ...);
//int semop(int semid, struct sembuf *sops, unsigned nsops);
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
(Linux-specific) */
};
void pGetKey(int semid)
{
struct sembuf init;
init.sem_num = 0;
init.sem_op = -1;
init.sem_flg = 0;
semop(semid,&init,1);
printf("pGetKey ok!\n");
}
void vPutBackKey(int semid)
{
struct sembuf init;
init.sem_num = 0;
init.sem_op = 1;
init.sem_flg = 0;
semop(semid,&init,1);
printf("vPutBackKey ok!\n");
}
int main()
{
key_t key;
int semid;
int pid;
key = ftok(".",125);
semid = semget(key,1,IPC_CREAT|0666);
if(semid == -1)
{
printf("Sorry creat semid error!\n");
}
union semun set;
set.val = 0;
semctl(semid,0,SETVAL,set);
pid = fork();
if(pid > 0)
{
pGetKey(semid);
printf("this is father!\n");
vPutBackKey(semid);
semctl(semid,0,IPC_RMID);
}
else if(pid == 0)
{
vPutBackKey(semid);
printf("this is child!\n");
}
else
{
printf("creat error!\n");
}
return 0;
}
这时候锁的数量为0,所以父进程在执行拿锁的时候阻塞在那里,让子进程放锁回去后才继续执行。
结果:
CLC@Embed_Learn:~/SECOND/IPC$ ./a.out
vPutBackKey ok!
this is child!
pGetKey ok!
this is father!
vPutBackKey ok!
这就是信号量拿锁与放锁的基本操作
学习笔记,仅供参考