信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
1、特点
-
信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
-
信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。
-
每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。
-
支持信号量组。
2、原型
最简单的信号量是只能取 0 和 1 的变量,这也是信号量最常见的一种形式,叫做二值信号量(Binary Semaphore)。而可以取多个正整数的信号量被称为通用信号量。
Linux 下的信号量函数都是在通用的信号量数组上进行操作,而不是在一个单一的二值信号量上进行操作。
1 #include <sys/sem.h>
2 // 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
3 int semget(key_t key, int num_sems, int sem_flags);
4 // 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
5 int semop(int semid, struct sembuf semoparray[], size_t numops);
6 // 控制信号量的相关信息
7 int semctl(int semid, int sem_num, int cmd, ...);
当semget
创建新的信号量集合时,必须指定集合中信号量的个数(即num_sems
),通常为1; 如果是引用一个现有的集合,则将num_sems
指定为 0 。
在semop
函数中,sembuf
结构的定义如下:
1 struct sembuf
2 {
3 short sem_num; // 信号量组中对应的序号,0~sem_nums-1
4 short sem_op; // 信号量值在一次操作中的改变量
5 short sem_flg; // IPC_NOWAIT, SEM_UNDO
6 }
其中 sem_op 是一次操作中的信号量的改变量:
-
若
sem_op > 0
,表示进程释放相应的资源数,将 sem_op 的值加到信号量的值上。如果有进程正在休眠等待此信号量,则换行它们。 -
若
sem_op < 0
,请求 sem_op 的绝对值的资源。- 如果相应的资源数可以满足请求,则将该信号量的值减去sem_op的绝对值,函数成功返回。
- 当相应的资源数不能满足请求时,这个操作与
sem_flg
有关。- sem_flg 指定
IPC_NOWAIT
,则semop函数出错返回EAGAIN
。 - sem_flg 没有指定
IPC_NOWAIT
,则将该信号量的semncnt值加1,然后进程挂起直到下述情况发生:- 当相应的资源数可以满足请求,此信号量的semncnt值减1,该信号量的值减去sem_op的绝对值。成功返回;
- 此信号量被删除,函数smeop出错返回EIDRM;
- 进程捕捉到信号,并从信号处理函数返回,此情况下将此信号量的semncnt值减1,函数semop出错返回EINTR
- sem_flg 指定
-
若
sem_op == 0
,进程阻塞直到信号量的相应值为0:- 当信号量已经为0,函数立即返回。
- 如果信号量的值不为0,则依据
sem_flg
决定函数动作:- sem_flg指定
IPC_NOWAIT
,则出错返回EAGAIN
。 - sem_flg没有指定
IPC_NOWAIT
,则将该信号量的semncnt值加1,然后进程挂起直到下述情况发生:- 信号量值为0,将信号量的semzcnt的值减1,函数semop成功返回;
- 此信号量被删除,函数smeop出错返回EIDRM;
- 进程捕捉到信号,并从信号处理函数返回,在此情况将此信号量的semncnt值减1,函数semop出错返回EINTR
- sem_flg指定
在semctl
函数中的命令有多种,这里就说两个常用的:
SETVAL
:用于初始化信号量为一个已知的值。所需要的值作为联合semun的val成员来传递。在信号量第一次使用之前需要设置信号量。IPC_RMID
:删除一个信号量集合。如果不删除信号量,它将继续在系统中存在,即使程序已经退出,它可能在你下次运行此程序时引发问题,而且信号量是一种有限的资源。
#include<stdio.h>
#include<stdlib.h>
#include<sys/sem.h>
#include<unistd.h>
// 联合体,用于semctl初始化
union semun
{
int val; /*for SETVAL*/
struct semid_ds *buf;
unsigned short *array;
};
// 初始化信号量
int init_sem(int sem_id, int value)
{
union semun tmp;
tmp.val = value;
if(semctl(sem_id, 0, SETVAL, tmp) == -1)
{
perror("Init Semaphore Error");
return -1;
}
return 0;
}
// P操作:
// 若信号量值为1,获取资源并将信号量值-1
// 若信号量值为0,进程挂起等待
int sem_p(int sem_id)
{
struct sembuf sbuf;
sbuf.sem_num = 0; /*序号*/
sbuf.sem_op = -1; /*P操作*/
sbuf.sem_flg = SEM_UNDO;
if(semop(sem_id, &sbuf, 1) == -1)
{
perror("P operation Error");
return -1;
}
return 0;
}
// V操作:
// 释放资源并将信号量值+1
// 如果有进程正在挂起等待,则唤醒它们
int sem_v(int sem_id)
{
struct sembuf sbuf;
sbuf.sem_num = 0; /*序号*/
sbuf.sem_op = 1; /*V操作*/
sbuf.sem_flg = SEM_UNDO;
if(semop(sem_id, &sbuf, 1) == -1)
{
perror("V operation Error");
return -1;
}
return 0;
}
// 删除信号量集
int del_sem(int sem_id)
{
union semun tmp;
if(semctl(sem_id, 0, IPC_RMID, tmp) == -1)
{
perror("Delete Semaphore Error");
return -1;
}
return 0;
}
int main()
{
int sem_id; // 信号量集ID
key_t key;
pid_t pid;
// 获取key值
if((key = ftok(".", 'z')) < 0)
{
perror("ftok error");
exit(1);
}
// 创建信号量集,其中只有一个信号量
if((sem_id = semget(key, 1, IPC_CREAT|0666)) == -1)
{
perror("semget error");
exit(1);
}
// 初始化:初值设为0资源被占用
init_sem(sem_id, 0);
if((pid = fork()) == -1)
perror("Fork Error");
else if(pid == 0) /*子进程*/
{
sleep(2);
printf("Process child: pid=%d\n", getpid());
sem_v(sem_id); /*释放资源*/
}
else /*父进程*/
{
sem_p(sem_id); /*等待资源*/
printf("Process father: pid=%d\n", getpid());
sem_v(sem_id); /*释放资源*/
del_sem(sem_id); /*删除信号量集*/
}
return 0;
}
1、信号量
特点:
1、信号量是一个计数器,用于多进程对共享数据对象的访问。
2、信号量的初始值是任一正数,说明有多少个共享资源单位可供共享应用。(常用的信号量形式:初始值为1,即某一资源某一时刻只能给单一进程占有。称为二元信号量或双态信号量)。
控制过程:
①、测试控制该资源的信号量。
②、若信号量的值为正,则进程可以使用该资源。进程将信号量的值减1,表示它使用了一个资源单位。
③、若信号量的值为0,则进程进入休眠状态,直到进程信号量大于1.进程被唤醒,再返回①。
④、当进程不在使用共享资源的时候,该信号量的值+1。若其它有进程等待此信号量,则唤醒它。
应用:
常用于进程的同步,例如多进程项目的log打印或写文件。
2、信号量集
信号量集:信号量的集合
当创建一个信号量集时,要指定该集合中信号量的数量
创建信号量集(semget)与对其赋初值(semctl)应一起,不要分开。
a、创建信号量 或 获取现在存的信号量:
int semget(key_t key,int nsems,int flag)
key:键值
nsems:表示信号量的数量
flag :同msgget
b、操作一个现有的信号量
int semctl(int semid,int semnum,int cmd,.../* union semun arg */)
semid:semget返回的信号量ID
semnum: [0] -- [nsems-1]信号量集合,即有nsems个信号量
c、操作信号量资源
int semop(int semid,struct sembuf semoparray[],size_t nops) //nops 操作几个信号量
3、实例
3.1、semget、semctl
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define NR 5
#define OP_ALL
int main(void)
{
int i,ret,semid;
//NR:信号量数组中的信号量个数
semid=semget(0x555,NR,IPC_CREAT|0644);//0x555键值,随便写的一个全局的键值
if(semid==-1)
{
perror("semget");
return 1;
}
printf("semid:%d\n",semid);
//--------------设置信号量值------------------------
#ifndef OP_ALL//逐一赋值
for(i=0;i<NR;i++)
{
ret=semctl(semid,i,SETVAL,1);
if(ret==-1) return 2;
}
#else//整体赋值
unsigned short val[NR]={1,1,1,1,1};
ret=semctl(semid,0,SETALL,val);
if(ret==-1) return 2;//error
#endif //
//---------------get semval-------------------------
#ifndef OP_ALL//逐一获取
for(i=0;i<NR;i++)
{
ret=semctl(semid,i,GETVAL);
if(ret==-1) return 3;
printf("%dth==>val:%d\n",i,ret);
}
#else//整体获取
unsigned short temp[NR];
ret=semctl(semid,0,GETALL,temp);
if(ret==-1) return 3;//error
for(i=0;i<NR;i++)
printf("%dth==>val:%d\n",i,temp[i]);
#endif //
//---------------------------------------------------
getchar();
printf("===============del sem array==========\n");
semctl(semid,0,IPC_RMID);//删除信号量
return 0;
}
3.2、父子进程同步打印字符(semop应用)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define NR 1
//申请资源
void p_lock(int semid);
//释放资源
void v_unlock(int semid);
int main(void)
{
pid_t pid;
int ret,semid;
semid=semget(0x555,NR,IPC_CREAT|0644);
if(semid==-1) return 11;
ret=semctl(semid,0,SETVAL,10);
if(ret==-1) return 22;
//------------------------------------------
pid=fork();
if(pid==-1) return 1;
else if(pid==0) //child
{
char i,*str="1234567890";
for(i=0;str[i];i++)
{
p_lock(semid);
write(STDOUT_FILENO,str+i,1);
usleep(500000);
v_unlock(semid);
}
exit(0);
}
//parent
char i,*str="abcdefghij";
for(i=0;str[i];i++)
{
p_lock(semid);
write(STDOUT_FILENO,str+i,1);
sleep(2);
v_unlock(semid);
}
wait(NULL);
putchar('\n');
return 0;
}
void p_lock(int semid)
{
struct sembuf buf={.sem_num=0,.sem_op=-10};
semop(semid,&buf,1);
}
void v_unlock(int semid)
{
struct sembuf buf={.sem_num=0,.sem_op= 10};
semop(semid,&buf,1);
}
3.3、文件同步写
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define TIMES 1000000
static int semid;
void write_file(int fd);
void unlock(void);
void lock(void);
int main(void)
{
int ret,fd,val=0;
pid_t pid;
fd=open("./txt",O_RDWR|O_TRUNC|O_CREAT,0644);
if(fd==-1) return 1;
pwrite(fd,&val,sizeof(int),0);
//----------------------------------------
semid=semget(0x555,1,IPC_CREAT|0644);//创建一个信号量
if(semid==-1) return 11;
ret=semctl(semid,0,SETVAL,1);//设置一个信号量,资源为1
if(ret==-1) return 22;
//-----------------------------------------
pid=fork();
if(pid==-1) return 2;//error
else if(pid==0) //child
{
printf("child start......\n");
write_file(fd);
printf("child write file over\n");
close(fd);
exit(0);
}
//parent
printf("parent write file\n");
write_file(fd);
wait(NULL);
pread(fd,&val,sizeof(int),0);
printf("====>val:%d\n",val);
close(fd);
return 0;
}
void write_file(int fd)
{
int i,val;
for(i=0;i<TIMES;i++)
{
//lock
lock();
pread(fd,&val,sizeof(int),0);
val+=1;
pwrite(fd,&val,sizeof(int),0);
unlock();
//unlock
}
}
void lock(void)
{
struct sembuf buf={.sem_op=-1,.sem_num=0};
semop(semid,&buf,1);
}
void unlock(void)
{
struct sembuf buf={.sem_op=1,.sem_num=0};
semop(semid,&buf,1);
}
3.4、等0操作
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define NR 1
void v_unlock(int semid);
void p_lock(int semid);
int main(void)
{
int i,ret,semid;
pid_t pid;
semid=semget(0x666,NR,IPC_CREAT|0644);
if(semid==-1) return 1;
ret=semctl(semid,0,SETVAL,10);
if(ret==-1) return 2;
pid=fork();
if(pid==-1) return 1;
else if(pid==0) //child
{
while(1)
{
for(i=0;i<10;i++)
{
p_lock(semid);
printf("===child:%d=====\n",i);
sleep(1);
}
}
}
//parent
struct sembuf zero={.sem_num=0,.sem_op=0};
while(1)
{
//wait zero
semop(semid,&zero,1);
printf("==========parent=========\n");
v_unlock(semid);
}
return 0;
}
void v_unlock(int semid)
{
struct sembuf buf={.sem_num=0,.sem_op=10};
semop(semid,&buf,1);
}
void p_lock(int semid)
{
struct sembuf buf={.sem_num=0,.sem_op=-1};
semop(semid,&buf,1);
}
3.5、栅栏程序模型
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <signal.h>
#define NR 5
static int semid;
void sigfunc(int signo)
{
if(signo!=SIGINT) return;
semctl(semid,0,IPC_RMID);
exit(0);
}
void do_work(int index);
void p_lock(int index);
int main(void)
{
int i;
pid_t pid;
signal(SIGINT,sigfunc);//is error
//------create sem-----------------
//对应的信号量用控制对应的子进程 最后一个用于父进程控制周期
semid=semget(0x555,NR+1,IPC_CREAT|0644);
if(semid==-1)
{
perror("semget");
return 1;
}
unsigned short val[NR+1]={0};
i=semctl(semid,0,SETALL,val);
if(i==-1)
{
perror("semctl");
return 2;
}
//-----------------------------------
for(i=0;i<NR;i++)
{
pid=fork();
if(pid==-1) return 1;
else if(pid==0)//child
{
do_work(i);
exit(0);
}
}
//---------------给定子进程资源---------------
struct sembuf ar[NR+1],zero={.sem_num=NR,.sem_op=0};
for(i=0;i<NR;i++)
{
ar[i].sem_op =1;
ar[i].sem_num=i;
}
ar[NR].sem_num=NR;
ar[NR].sem_op =NR;
//-----------------------------------
while(1)
{
semop(semid,&zero,1);//等0操作
printf("==========================\n");
semop(semid,ar,NR+1);
}
while(wait(NULL)!=-1)
;//empty
return 0;
}
void do_work(int index)
{
int sec;
srand(getpid());
while(1)
{
p_lock(index);
sec=rand()%6+1;
sleep(sec);
printf("%d:[%d] sleep:%d\n",index,getpid(),sec);
//通知父进程已跑完
p_lock(NR);
}
}
void p_lock(int index)
{
struct sembuf s={.sem_num=index,.sem_op=-1};
if(semop(semid,&s,1)==-1)
{
perror("semop");
exit(1);
}
}