信号量
信号量主要用于进程间的同步与互斥,类似于计数器,用于标记资源个数。
S > 0:S表示可用资源个数。
S == 0:表示无可用资源,无等待进程。
S < 0:|S|表示等待队列中,等待资源进程资源个数。
进程互斥:
由于进程间要求资源共享,而有些资源一次只能被一个进程访问(临界资源),从而进程间竞争使用这种资源,进程间的这种关系称为进程互斥。
程序中涉及到临界资源的代码段称为临界区。
进程同步:
有时两个进程之间必须按照一定时序才能完成特定的任务,比如:想要抽完一根烟,需要A进程点着烟,接下来B进程才能抽烟,只有按照这个次序才能完成抽完一根烟的动作,否则将不能完成这个动作。这就是进程同步。
P、V原语操作:
P、V原语操作是一段不可分割不可中断的程序,用于操作信号量,可避免在操作信号量过程中由于进程切换造成的数值不唯一错误。
同步:P、V操作不在同一个进程中。
互斥:P、V操作在同一个进程中。
信号量结构伪代码:
struct Semaphore{
int value;//资源个数
pointer_PCB queue;//等待队列
}
P操作伪代码:
p(s){
s.value--;
if(s.value < 0){
//1、将该进程状态置为等待状态
//2、将该进程PCB插入s.queue等待队列末尾
}
}
V操作伪代码:
v(s){
s.value++;
if(s.value <= 0){
//唤醒等待队列s.queue中的一个进程,
//改变其状态为就绪状态。
}
}
信号量集结构:
struct semid_ds {
struct ipc_perm sem_perm; /* 权限和所有者 */
__kernel_time_t sem_otime; /* 最后操作时间 */
__kernel_time_t sem_ctime; /* 最后改变时间(模式更改) */
struct sem *sem_base; /* 指向信号量集数组中第一个信号量的指针 */
struct sem_queue *sem_pending; /* 指向等待队列中首先处理的进程 */
struct sem_queue **sem_pending_last; /* 指向等待队列中最后处理的进程 */
struct sem_undo *undo; /* 撤销此数组上的请求 */
unsigned short sem_nsems; /* 信号量集数组中元素的个数 */
};
信号量集函数:
semget()函数:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
- 说明:该函数用于创建或者带开已有的信号量集。
- 参数:
- key:关键字,通常由ftok()产生。
- nsems:信号量集中信号量的个数。
- semflg:类似于文件操作的mode模式。
- 返回值:失败返回-1,并设置errno,成功返回信号量集标识符。
semctl()函数:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);
- 说明:此函数用于控制信号量集。
- 参数:
- semid:信号量标识符,由semget()函数产生。
- semnum:信号量集中要操作的信号量下标。
- cmd:将要采取的命令,可能的三个取值如下:
IPC_STAT:读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中。
IPC_SET:设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf参数。
IPC_RMID:将信号量集从内存中删除 - …:可变参数,依据cmd的不同而不同,通常是union semun的一个实例。
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 */
void *__pad;
};
- 返回值:成功返回0,失败返回-1,并设置errno。
semop()函数:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, unsigned nsops);
- 说明:操作一个或一组信号量,PV操作会调用此函数。
- 参数:
- semid:信号量集标识符。
- sops:指向存储信号操作结构的数组指针。信号操作结构原型如下:
struct sembuf {
unsigned short sem_num; /* semaphore index in array */
short sem_op; /* semaphore operation */
short sem_flg; /* operation flags */
};
sem_num:信号量集中信号量的下标。
sem_op:信号量一次PV操作现有值加上的值,一般只会用到三个值:
+1:也就是V操作,信号量值+1.
-1:也就是P操作,信号量值-1.
0:如果没有设置IPC_NOWAIT,PV操作会暂时睡眠,直到信号量的值为0,否则,不会睡眠,函数返回EAGAIN。
sem_flg:信号操作标志,可能有两个取值,如下:
IPC_NOWAIT:对信号的操作不能满足时,不阻塞,立即返回,并设置错误信息。
IPC_UNDO:程序结束时,保证信号量的值会被设置成semop()调用前的值,这样做的目的在于防止资源泄露。
- nsops:信号操作结构的数量,恒大于或等于1。
- 返回值:成功返回0,失败返回-1,并设置errno。
实例代码:
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 0x666
//用于semctl()的可变参数列表
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
int CreateSemSet(int nsems);
int initSem(int semid,int nsem,int initVal);
int GetSemSet(int nsems);
int P(int semid,int who);
int V(int semid,int who);
int DestroySemSet(int semid);
#endif //__COMM_H_
comm.c:
#include"comm.h"
//依据flag的不同,创建或者打开信号量集
static int CommSemSet(int nsems,int flags){
key_t _key = ftok(PATHNAME,PROJ_ID);
if(_key == -1){
perror("ftok");
return -1;
}
int semid = semget(_key,nsems,flags);
if(semid == -1){
perror("semget");
return -2;
}
return semid;
}
//创建信号量集
int CreateSemSet(int nsems){
return CommSemSet(nsems,IPC_CREAT|IPC_EXCL|0666);
}
//打开信号量集
int GetSemSet(int nsems){
return CommSemSet(nsems,IPC_CREAT);
}
//初始化信号量集
int initSem(int semid,int nsem,int initVal){
union semun _un;
_un.val = initVal;
//设置信号量数值
int ret = semctl(semid,nsem,SETVAL,_un);
if(ret == -1){
perror("semctl");
return -1;
}
return 0;
}
//根据op参数的不同,进行P或者V操作
static int CommPV(int semid,int who,int op){
struct sembuf _sbuf;
_sbuf.sem_num = who;
_sbuf.sem_op = op;
_sbuf.sem_flg = 0;
int ret = semop(semid,&_sbuf,1);
if(ret == -1){
perror("semop");
return -1;
}
return 0;
}
//P操作,申请资源
int P(int semid,int who){
return CommPV(semid,who,-1);
}
//V操作释放资源
int V(int semid,int who){
return CommPV(semid,who,1);
}
//删除信号量集
int DestroySemSet(int semid){
int ret = semctl(semid,0,IPC_RMID);
if(ret == -1){
perror("semctl");
return -1;
}
return 0;
}
sem.c:
#include"comm.h"
int main(){
int semid = CreateSemSet(1);
initSem(semid,0,1);
pid_t id = fork();
if(id < 0){
perror("fork");
return -1;
}else if(id == 0){
//child
int _semid = GetSemSet(0);
while(1){
P(_semid,0);
printf("A");
fflush(stdout);
usleep(123456);
printf("A ");
fflush(stdout);
usleep(456123);
V(_semid,0);
}
}else{
//father
while(1){
P(semid,0);
printf("B");
fflush(stdout);
usleep(345612);
printf("B ");
fflush(stdout);
usleep(561234);
V(semid,0);
}
wait(NULL);
}
DestroySemSet(semid);
return 0;
}
Makefile:
.PHONY:sem
sem:sem.c comm.c
gcc $^ -o $@
.PHONY:cl
cl:
rm -f sem
结果演示:
使用信号量的结果:

未使用信号量的结果:

将P、V操作封装成静态库、动态库:
静态库:
所需文件:
[DELL@MiWiFi-R1CL-srv lib]$ ll
总用量 16
-rw-rw-r--. 1 DELL DELL 1392 5月 2 18:42 comm.c
-rw-rw-r--. 1 DELL DELL 505 5月 2 18:42 comm.h
-rw-rw-r--. 1 DELL DELL 666 5月 2 18:45 lib.c
-rw-rw-r--. 1 DELL DELL 310 5月 2 20:56 Makefile
lib.c:
#include"comm.h"
int main(){
int semid = CreateSemSet(1);
initSem(semid,0,1);
pid_t id = fork();
if(id < 0){
perror("fork");
return -1;
}else if(id == 0){
//child
int _semid = GetSemSet(0);
while(1){
P(_semid,0);
printf("A");
fflush(stdout);
usleep(123456);
printf("A ");
fflush(stdout);
usleep(456123);
V(_semid,0);
}
}else{
//father
while(1){
P(semid,0);
printf("B");
fflush(stdout);
usleep(345612);
printf("B ");
fflush(stdout);
usleep(561234);
V(semid,0);
}
wait(NULL);
}
DestroySemSet(semid);
return 0;
}
Makefile:
.PHONY:stalib
stalib:
gcc -c comm.c -o comm1.o
ar -rc libmystapv.a comm1.o
gcc lib.c -o stalib -L. -lmystapv
rm comm1.o libmystapv.a
结果:

动态库:
所需文件:若没有贴出的源文件同上。
[DELL@MiWiFi-R1CL-srv lib]$ ll
总用量 16
-rw-rw-r--. 1 DELL DELL 1392 5月 2 18:42 comm.c
-rw-rw-r--. 1 DELL DELL 505 5月 2 18:42 comm.h
-rw-rw-r--. 1 DELL DELL 666 5月 2 18:45 lib.c
-rw-rw-r--. 1 DELL DELL 310 5月 2 20:56 Makefile
Makefile.c:
.PHONY:dynlib
dynlib:
gcc -fPIC -c comm.c -o comm2.o
gcc -shared -o libmydynpv.so comm2.o
gcc lib.c -o dynlib -L. -lmydynpv
结果:

本文深入讲解了信号量机制,包括其基本概念、P/V原语操作、信号量集的使用及其实现代码示例,帮助读者理解进程间的同步与互斥。
399

被折叠的 条评论
为什么被折叠?



