信号量:实现两个进程的同步,对于进程执行的同步控制
特殊的一个变量(计数器),其值大于0时,纪录临界资源的个数;当值小于0时,对此信号量执行P操作(-1),会阻塞,直到信号量的值大于0,或者有其他进程在此信号量上执行了V操作。
信号量的相关操作:
1、临界资源:临界资源是指每次仅允许一个进程访问的共享资源,即各进程采取进程互斥的方式,实现共享的资源称作临界资源。属于临界资源的硬件有打印机、磁带机等,软件有消息缓冲队列、变量、数组、缓冲区等。
2、临界区: 每个进程中访问临界资源的那段代码称为临界区
- 每次只允许一个进程进入临界区,进入后不允许其他进程进入。
- 任何临界资源,多个进程必须互斥的对它进行访问
3、原子操作:不可被中断的一个或一系列操作,即临界区的代码不会被这个临界数据的其它临界区的代码打断。这种操作一旦开始,就一直运行到结束。
4、PV操作:PV操作与信号量的处理相关,P表示通过的意思,V表示释放的意思。所谓信号灯,实际上就是用来控制进程状态的一个代表某一资源的存储单元。
- P 操作(等待操作):如果信号量sv的值大于0,就给他 -1,如果sv的值等于0,就挂起该进程。
- V 操作(发送信号):如果有其他进程因等待sv而被挂起,就让他恢复运行;如果没有进程因等待sv而挂起,就给他+1。
信号量的相关函数:Linux系统内核维护是一个信号量集
1、semget函数:创建或获取已存在的信号量
int semget(key_t key, int nsems, int flag);
key:用户标识,如果多个进程想通过同一个信号量完成数据通信,则每个进程使用相同的key值创建或者获取同一个消息队列的内核对象ID值
nsems:在创建信号量时,其指定信号量集中信号量的个数。
flag:指定一个权限操作,IPC_CREAT:新建一个信号量集对象,IPC_EXCL:与IPC_CREAT一同使用,表示如果要创建的信号量集对象已经存在,则返回错误。
如果第一次执行semget,内核中并没有信号量集,则需要创建信号量集,并且完成初始化,如果内核中已经有此信号量,则直接获取信号量标识返回即可。
2、semctl函数: 一次只能操作一个信号量
int semctl(int semid, int semnum, int cmd, ...)
semid:信号量集标识符,senget的返回值
semnum:一个数组,表示信号量的下标
cmd:要执行的操作
IPC_STAT读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中。
IPC_SET设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf参数。
IPC_RMID删除一个信号量集。
3、semop函数:对信号量的操作,p操作或者v操作
int semop(int semid,struct *buf,int len)
- 成功返回0,失败返回-1;
struct sembuf { int sem_num;//信号量的下标 int sem_op;//-1:P操作 1:V操作 int sem_flg;//设置方式,SEM_UNDO }
通过把上面三种方法进行封装,封装成我们使用的下面几种方法:
int CreateSme( int key, int init_val, int len);//创建一个信号量
- 对于获取而言,只需要key,因为key可以找到内核对象,返回id值
- 对于创建这三个变量都必须有
void SemP( int semid,int index);//P操作,一次调用只操作一个信号量
- semid是CreateSme的返回值
- index设置的下标
void SemV( int semid,int index);//V操作,一次调用只操作一个信号量
- semid是CreateSme的返回值
- index设置的下标
void DeleteSem(int semid)//销毁一个信号量
代码实现:
//sem.h
#pragma once
#include <sys/sem.h>
union semval
{
int val;
};
int CreateSem(int key, int init_val[], int len);
//一次只操作一个信号量
void SemP(int semid, int index);
void SemV(int semid, int index);
void DeleteSem(int semid);
//sem.c
#include "./sem.h"
#include <stdio.h>
//1、如果内核已经有key值对应的信号量,则直接返回
//2、如果没有,2.1先去创建此信号量集 2.2所有的信号量根据init_val的值进行初始化
int CreateSem(int key, int init_val[],int len)
{
//获取
int semid = semget((key_t)key, 0, 0664);
if(semid != -1)//成功获取
{
return semid;
}
//获取失败,创建
semid = semget((key_t)key,len, IPC_CREAT | 0664);
if(semid == -1)//创建失败
{
perror("Create Sem Error: ");
return -1;
}
//初始化
int i = 0;
for(; i < len; ++i)
{
union semval data;
data.val = init_val[i];
if(-1 == semctl(semid, i, SETVAL, data))
{
perror("init sem value fail: ");
return -1;
}
}
return semid;
}
//P操作
void SemP(int semid,int index)
{
struct sembuf buf;
buf.sem_num = index;
buf.sem_op = -1;
buf.sem_flg = SEM_UNDO;
if(-1 == semop(semid,&buf,1))
{
perror("sem p operation fail: ");
}
}
//V操作
void SemV(int semid,int index)
{
struct sembuf buf;
buf.sem_num = index;
buf.sem_op = 1;
buf.sem_flg = SEM_UNDO;
if(-1 == semop(semid, &buf, 1))
{
perror("sem v operation fail: ");
}
}
//删除整个信号量集
void DeleteSem(int semid)
{
if(-1 == semctl(semid, 0, IPC_RMID))
{
perror("delete sem fail: ");
}
}
//main.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include "sem.h"
#include <time.h>
int main(int argc, char *argv[])
{
srand((unsigned int)(time(NULL)*time(NULL)));
int val = 1;
int semid = CreateSem(1234,&val,1);
assert(semid != -1);
int count = 0;
while(1)
{
SemP(semid, 0);
printf("%s\n",argv[1]);
int n = rand() % 4;
sleep(n);
SemV(semid,0);
n = rand() % 4;
sleep(n);
count++;
if(count == 5)
{
break;
}
}
}
用命令管理:
ipcs -s :chakan
ipcrm -s semid:删除信号量