版权声明:Dream_dog专属 https://blog.csdn.net/Dog_dream/article/details/84317706
信号量概述
在多任务操作系统环境下,多个进程/线程会同时进行。多任务可能为了完成同一个目标会相互协作,这样就形成了任务之间的同步关系。同样,不同任务之间为了争夺有限的系统资源(硬件或软件资源)会进入竞争状态。 任务间的互斥与同步关系存在的根源在于临界资源。临界资源:是指在同一时刻只允许有限个(通常只有一个)操作的资源。访问临界资源的代码成为临界区。 信号量是用来解决进程/线程之间同步与互斥问题的一种通信机制,包括一个称为信号量的变量和在该信号量下等待资源进程等待队列,以及对信号量进行的两个原子操作(PV操作)。信号量常常被用做一个锁机制,在某个进程正对特定资源进行操作时,信号量可以防止其他进程去访问它PV原子操作(信号量工作原理)
1. P(操作):如果有可用资源(信号量值大于0),则占用一个资源(信号量值减1,进入临界区代码);如果没有可用资源(信号量值等于0),则被阻塞,直到系统将资源分配给该任务(进入等待队列,一直等待到有资源是被唤醒)。2. V(操作):如果在该信号量的等待队列中有任务在等待资源,则唤醒一个阻塞任务。如果没有等待它,则释放一个资源(信号量值加1)。
信号量数据结构
内核为每一个信号量集合设置了一个semid_ds机构体
union semun{//信号量操作的联合结构
int val;//整形变量
struct semid_ds *buf;//semid_ds结构体指针
unsigned short *arry;//数组类型
struct seminfo *_buf;//信号量内部结构
信号量函数
1 semget()函数用于创建一个新的信号量集合,或者访问已存在的集合。 头文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
函数原型:
int semget(key_t key, int nsems, int semflg);
key:所创建或打开信号量集的键值。
nsems:创建的信号量集中的信号量的个数,该参数只在创建信号量集时有效。
semflg:调用函数的操作类型,也可用于设置信号量集的访问权限,两者通过or表示
IPC_CREAT:如果内核不存在这样的信号量集合,则把他们创建出来。
IPC_EXCL :当与IPC_CREAT在一起使用时,如果信号量集合早已存在,则操作失败。单独使用semget()或者返回新创建的信号量集合的信号标识符;
返回值:
如果成功,则返回信号量集的IPC标识符。
如果失败,则返回-1,errno被设定成以下的某个值
EACCES:没有访问该信号量集的权限
EEXIST:信号量集已经存在,无法创建
EINVAL:参数nsems的值小于0或者大于该信号量集的限制;或者是该key关联的信号量集已存在,并且nsems大于该信号量集的信号量数
ENOENT:信号量集不存在,同时没有使用IPC_CREAT
ENOMEM :没有足够的内存创建新的信号量集
ENOSPC:超出系统限制
2 semop()信号量操作函数 向已建好了的信号量发送命令 头文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
函数原型:
int semop(int semid, struct sembuf *sops, size_t nsops);
semid:信号集的识别码,可以通过semget获取。
sops: 将要执行的操作
nsops:信号操作结构的数量,恒大于或等于1。
struct sembuf
struct sembuf{
unsigned short sem_num;
short sem_op;
short sem_flg;
};
sem_num:操作信号在信号集中的编号,第一个信号的编号是0。
sem_op:sem_op值:
(1)若sem_op为正,这对应于进程释放占用的资源数。sem_op值加到信号量的值上。(V操作)
(2)若sem_op为负,这表示要获取该信号量控制的资源数。信号量值减去sem_op的绝对(P操作)
(3)若sem_op为0,这表示调用进程希望等待到该信号量值变成0
◆如果信号量值小于sem_op的绝对值(资源不能满足要求),则:
(1)若指定了IPC_NOWAIT,则semop()出错返回EAGAIN。
(2)若未指定IPC_NOWAIT,则信号量的semncnt值加1(因为调用进程将进 入休眠状态),后调用进程被挂起直至:
①此信号量变成大于或等于sem_op的绝对值;
②从系统中删除了此信号量,返回EIDRM;
③进程捕捉到一个信 号,并从信号处理程序返回,返回EINTR。(与消息队列的阻塞处理方式 很相似)
sem_flg:信号操作标志,可能的选择有两种
IPC_NOWAIT //对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息。
EM_UNDO //程序结束时(不论正常或不正常),保证信号值会被重设为semop()调用前值。
这样做的目的在于避免程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定。
3 semctl()函数对一个信号量执行各种控制操作 头文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
原型:
int semctl(int semid, int semnum, int cmd, /*可选参数*/);
semid:信号集的识别码,可以通过semget获取。
semnu:指定信号集中的哪个信号(操作对象)
cmd:指定以下10种命令中的一种,在semid指定的信号量集合上执行此命令。
IPC_STAT 读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中。
IPC_SET 设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf参数。
IPC_RMID 将信号量集从内存中删除。
GETALL 用于读取信号量集中的所有信号量的值。
GETNCNT 返回正在等待资源的进程数目。
GETPID 返回最后一个执行semop操作的进程的PID。
GETVAL 返回信号量集中的一个单个的信号量的值。
GETZCNT 返回这在等待完全空闲的资源的进程数目。
SETALL 设置信号量集中的所有的信号量的值。
SETVAL 设置信号量集中的一个单独的信号量的值。
可选参数:取决于cmd若设置则为union semun类型
实例
head.h#ifndef _MYSEM_H_
#define _MYSEM_H_
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include<sys/wait.h>
#include<errno.h>
#include<sys/shm.h>
#include<unistd.h>
#define PATH "."
#define BUFMAX 1024
union semun
{
int val;
struct semid_ds *buf;
unsigned short *arry;
};
extern int sem_init(int semid,int id,int vale); //信号量集合初始化
extern int sems_create(int nums); //创建nums个信号量的集合
extern int sems_destroy(int semid); //释放信号量集
extern int sem_p(int semid,int id);//分配
extern int sem_v(int semid,int id);//释放
#endif
sem.c
#include"./head.h"
int sem_init(int semid,int id,int vale) //信号量集合初始化
{
union semun _semun;
_semun.val=vale;
if(semctl(semid,id,SETVAL,_semun)<0)//初始化信号量为ID的
{
perror("sem_init is error\n");
return -1;
}
return 0;
}
int sems_create(int nums) //创建nums个信号量的集合
{
key_t key= ftok(PATH,'a');//生成key值
if(key<0)
{
perror("create key is error\n");
}
int semid=semget(key,nums,IPC_CREAT|0666);//创建信号量
if(semid<0)
{
perror("semget is error\n");
return -1;
}
return semid;
}
int _semop(int semid,int id,int vale)
{
struct sembuf _sembuf;
_sembuf.sem_num=id;
_sembuf.sem_op=vale;
_sembuf.sem_flg=SEM_UNDO;
if(semop(semid,&_sembuf,1)<0)
{
perror("semop is error\n");
return -1;
}
return 0;
}
int sems_destroy(int semid) //释放信号量集
{
if(semctl(semid,0,IPC_RMID,NULL)<0)
{
perror("sems_destroy is error\n");
return -1;
}
return 0;
}
int sem_p(int semid,int id)//分配
{
return _semop(semid,id,-1);
}
int sem_v(int semid,int id)//释放
{
return _semop(semid,id,1);
}
main.c
#include"./head.h"
int main()
{
int semid=sems_create(10);//创建10的大小的信号量集
sem_init(semid,0,0);
sem_init(semid,1,1);
pid_t pid;
pid=fork();
if(pid<0)
{
perror("fork is error\n");
exit(-1);
}
else if(pid==0)//子进程
{
//pritf("Child process will wait for some secnods...\n");
while(1)
{
sem_p(semid,1);//对0号信号量进行分配操作
puts("hello ");
sleep(2);
puts("word ");
sleep(2);
puts("#########");
sem_v(semid,0);//对0号信号量进行释放操作
}
}
else//父进程
{
while(1)
{
sem_p(semid,0);
puts("fhello ");
sleep(2);
puts("fword ");
sleep(2);
puts("#########");
sem_v(semid,0);
}
wait(NULL);
}
sems_destroy(semid);
return 0;
}
信号量就是对资源的锁定和释放