本质
计数器,记录可以访问共享资源的次数。
作用
保护共享资源(硬件设备,文件,共享内存等等)
- 互斥(一个时刻,只能有一个进程来访问共享资源)
- 同步(互斥的基础上,加上了访问的顺序规定)
信号量大于0,则可以访问;信号量等于0,则不可以访问。
访问时会将信号量减1,访问完成会将信号量加1。(加1减1可由用户控制),信号量减小到0之后,进程就不能去访问共享资源了。
信号量用法
- 定义一个唯一的key(ftok)
- 构造一个信号量(semget)
- 初始化信号量(semctl SETBA)
- 对信号量进行 p / v 操作(就是加减操作,semop)
- 删除信号量(semctl RMID)
semget函数
- 功能:获取 / 创建信号量的 id
- 函数原型:
int semget(key_t key,int nsems,int semflg);
-
参数:
- key:信号量键值
- nsems:信号量数量
- semflg:
IPC_CREAT:信号量不存在则创建
mode:信号量的权限,rwx
-
返回值:
成功:信号量 id
失败:-1
semctl函数
- 功能:获取 or 设置信号量的相关属性
- 函数原型:
int semctl(int semid,int semnum,int cmd,union semun arg);
-
参数:
- semid:信号量 id
- semnum:信号量编号
- cmd:
IPC_STAT:获取信号量的属性信息
IPC_SET:设置信号量的属性
IPC_RMID:删除信号量
IPC_SETVAL:设置信号量的值(特有)
-
arg:
union semun
{
int val;
struct semid_ds *buf;
};
-
返回值:
- 成功:由 cmd 类型决定,上面列出的四种成功都是返回0。
- 失败:-1
semop函数
- 功能:对信号量进行加减操作
- 函数原型:
int semop(int semid,struct sembuf *sops,size_t nsops);
-
参数:
-
- semid:信号量id
-
- sops:信号量操作结构体数组
struct sembuf
{
short sem_num;//信号量编号
short sem_op;//信号量 p / v 操作
short sem_flg;//信号量行为,一般设置为SEM_UNDO,自动释放信号量
};
-
- nsops:信号量数量
- 返回值:
-
- 成功:0
-
- 失败:-1
测试代码:
test.c
#include<sys/ipc.h>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<sys/sem.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<errno.h>
union semun
{
int val;
struct semid_ds *buf;
};
//初始化信号量
int init_sem(int sem_id,int init_value)
{
union semun sem_union;
sem_union.val = init_value;
if(semctl(sem_id,0,SETVAL,sem_union) == -1)
{
printf("Initialize semaphore.\n");
return -1;
}
return 0;
}
//删除信号量
int del_sem(int sem_id)
{
union semun sem_union;
if(semctl(sem_id,0,IPC_RMID,sem_union) == -1)
{
perror("Delete semahore.\n");
return -1;
}
}
// p 操作
int sem_p(int sem_id)
{
struct sembuf sops;
sops.sem_num = 0;//单个信号量的编号应该为0
sops.sem_op = -1;//表示 p 操作,减操作
sops.sem_flg = SEM_UNDO;//到最后系统会自动释放系统中残留的信号量
if(semop(sem_id,&sops,1) == -1)
{
perror("P operation.\n");
return -1;
}
return 0;
}
// v 操作
int sem_v(int sem_id)
{
struct sembuf sops;
sops.sem_num = 0;//单个信号量的编号为 0
sops.sem_op = 1;//表示 v 操作
sops.sem_flg = SEM_UNDO;//系统自动释放残留的信号量
if(semop(sem_id,&sops,1) == -1)
{
perror("V operation.\n");
return -1;
}
return 0;
}
#define DELAY_TIME 3
int main(void)
{
pid_t result;
int sem_id;
//创建一个信号量
sem_id = semget((key_t)6666, 1, 0666 | IPC_CREAT);
//信号量的id,和信号量初始化的值为0
init_sem(sem_id, 0);
//调用 fork() 函数
result = fork();
if(-1 == result)
{
perror("Fail fork.\n");
}
else if(0 == result)//子进程
{
printf("Child process will wait for some seconds ...\n");
sleep(DELAY_TIME);
printf("The child process is running ...\n");
//给信号量的值加1
sem_v(sem_id);
}
else//父进程
{
//若信号量的值已经为0,那么这个减1的操作将阻塞于此
//从而保证子进程运行在父进程的前面
sem_p(sem_id);
printf("The father process is running ...\n");
sem_v(sem_id);
del_sem(sem_id);
}
exit(0);
}
编译与运行:
jl@jl-virtual-machine:~/test/11_4$ gcc test.c
jl@jl-virtual-machine:~/test/11_4$
jl@jl-virtual-machine:~/test/11_4$
jl@jl-virtual-machine:~/test/11_4$
jl@jl-virtual-machine:~/test/11_4$ ./a.out
Child process will wait for some seconds ...
The child process is running ...
The father process is running ...
jl@jl-virtual-machine:~/test/11_4$
jl@jl-virtual-machine:~/test/11_4$
jl@jl-virtual-machine:~/test/11_4$
可以看到,父进程总是在子进程后面运行。