- 临界资源:同一时刻只能被一个进程访问的资源
- 临界区:访问临界资源的代码区域
- 原子操作:不能被打断的操作,一旦开始执行,只能到结束,中间不能被打断
信号量
他与其他的IPC机构不一样,它是一个计数器,用于多进程对共享数据对象的访问。因为信号量是对于所有进程都可以访问到的,所以信号量在内核(只有一个内核嘛)中实现。在内核中一个内核对象中有一个信号量集,每个信号量集会有多个信号量,你可以将这个信号量集看成一个数组,其中数组中每个元素就是一个信号量。(数组的元素个数是程序员指定的)我们使用信号量,来解决进程间共享资源引发的同步问题,是进程之间同步控制的一种机制。
对于信号量有两个操作(因为信号量访问具有原子性,所以两个操作都是原子操作)
- v操作(+1):将信号量的个数加一,进程可以访问资源。
- p操作(-1):将信号量的个数减一,资源被一个进程占据了,或许当前进程需要等待。
<1>若信号量的值为正,则进程可以使用该资源。进程将信号量的值减一,表示它使用了一个资源单位
<2>若此信号量的值为0,则进程进入休眠状态(等待),直至信号量值大于0,进程被唤醒,可以使用资源。
信号量的使用
Linux提供了一组信号量API,我们来看看
1:要获得一个信号量ID,要调用的第一个函数是semget(创建信号量(集))
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
int semget(key_t key,int nsems,int flag); //成功返回信号量ID,出错返回-1
key :信号量键值,可以理解为信号量的唯一性标记。用户态有一个key值,如果获取信号量ID时,使用的key值相同,则访问的信号量就是同一个。(相当于我么key就是文件名,而id就是我们的文件描述符,用id来操作信号量)
nsems:是该信号量集中的信号量数(即数组大小)。如果创建新集合(一般在服务器进程中),则必须指定nsems。如果引用一个现存的集合(一个客户进程),则将nsems指定为0.
sem_flags:有两个值:IPC_CREAT和IPC_EXCL
IPC_CREAT表示若信号量已存在,返回该信号量标识符。
IPC_EXCL表示若信号量已存在,返回错误。
2:信号量的创建与其赋初始值是分开的,赋初始值我们用semctl函数
#include<sys/sem.h>
int semctl(int semid,int semnum,int cmd,..../*union semun arg*/);
semnum:指定信号量集中的一个成员(即数组中的元素),semnum值在0--nsems-1之间(下标)
cmd:常用的有两个 IPC_RMID,GETVAL(其他的cmd值可以查书,这里就不一一列举了)
IPC_RMID:从系统中删除信号量集(IPC开头的cmd操作的是整个信号量集)
GETVAL:返回成员semnum的semval的值(也即计数器的值(信号量)。GET开头的cmd操作的是semnum成员里的数值,而非整个信号量集)
对于第四个参数是可选的,如果使用该参数,其类型是semun,它是一个联合体
union semun{
int val;
struct semid_ds *buff;
unsigned short *array;
}
一般用到的是val,表示要传给信号量的初始值。
3:p/v操作 ,修改信号量的值
#include<sys/sem.h>
int semop(int semid,struct sembuf semoparry[],size_t nops);//成功返回0,出错返回-1
参数semoparry是一个指针,他指向一个信号量操作数组,信号量操作由sembuf结构表示
nops规定该数组中操作的数量(元素数)
struct sembuf{
unsigned short sem_num; //要进行操作的信号量集的元素下标 0-nsems-1
short sem_op; //信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作另一个是+1,即V(发送信号)操作。
short sem_flg; ////通常为SEM_UNDO,使操作系统跟踪信号,并在进程没有释放该信号量而终止时,操作系统释放信号量(会将信号量自动复原)
}
实例:
sem.h
#ifndef __SEM_H
#define __SEM_H
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/sem.h>
#include<fcntl.h>
#include<assert.h>
#include<sys/ipc.h>
typedef union semun
{
int val; //我们只用到了val,所以我们就重新定义一下这个结构
}semun;
int sem_init(key_t key); //初始化操作
void sem_p(int id,int num); //p操作
void sem_v(int id,int num); //v操作
void sem_del(int id); //删除信号量
#endif
#include"sem.h"
int sem_init(key_t key) //获取信号量集id函数
{
int id=semget(key,0,0664);//获取下,看看key值所对应的信号量即是否存
if(id==-1) //key值对应的信号量集不存在,要创建
{
id=semget(key,2,0664|IPC_CREAT); //2是元素个数
semun un;
un.val=0; //信号量数值,这个很关键
semctl(id,1,GETVAL,un); //根据un值设置1号下标的信号量中的各个属性
}
return id;
}
void sem_p(int id,int num) //num是要操作的元素下标
{
struct sembuf buf;
buf.sem_num=num;
buf.sem_op=-1;
buf.sem_flg=SEM_UNDO;
semop(id,&buf,1); //1是要操作的元素数
}
void sem_v(int id,int num) //num是要操作的元素下标
{
struct sembuf buf;
buf.sem_num=num;
buf.sem_op=1;
buf.sem_flg=SEM_UNDO;
semop(id,&buf,1);
}
void sem_del(int id)
{
semctl(id,1,IPC_RMID);
}
main1.c
#include"sem.h"
int main()
{
int semid=sem_init((key_t)2333);
int fd=open("a.txt",O_WRONLY|O_CREAT,0664);
assert(fd!=1);
sleep(3);
write(fd,"Hello",5);
sem_v(semid,1);
close(fd);
}
main2.c
#include"sem.h"
int main()
{
int semid=sem_init((key_t)2333);
sem_p(semid,1);
int fd=open("a.txt",O_RDONLY|O_CREAT,0664);
assert(fd!=-1);
char buff[128]={0};
int n=read(fd,buff,127);
printf("%s\n",buff);
close(fd);
sem_del(semid);
exit(0);
}