在具体介绍信号量之前,先来了解一下在这一块有关的概念:
- 临界资源:同一时刻,只允许一个进程访问的资源;
- 临界区:访问临界资源的代码;
- 原子操作:单指令的操作称为原子的,单条指令的执行是不会被打断的;
- p操作:减一操作,从内存中获取资源,给进程使用;
- v操作:加一操作,释放资源,将资源归还给内存;
- 阻塞与非阻塞:**关注的是进程在发出请求后的状态。
- 阻塞:发出请求后,若请求不能被立即响应,则进程被挂起,等待请求响应。
- 非阻塞:发出请求后,无论请求能否响应,进程都会继续向下执行。
- 同步、异步: **关注的是进程发出请求后,进程如何获知请求的条件已经发送
- 同步:发出请求后,如果进程不能立即响应,则不断去探测条件是否发生(类似心跳包机制)
- 异步:发出请求后,进程继续往下执行,但是在请求的条件发生后,会通过内核消息通知机制,通知进程条件已经发生。
接下来我们再来具体了解一下信号量:
信号量:类似于一个计数器,用于进程同步控制,而不是用于存储进程间通信数据。
用到的重要函数:
#include <sys/sem.h>
int semget(key_t key, int num_sems, int sem_flags);// 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
int semop(int semid, struct sembuf semoparray[], size_t numops);// 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
int semctl(int semid, int sem_num, int cmd, ...);// 控制信号量的相关信息
当semget创建新的信号量集合时,必须指定集合中信号量的个数(即num_sems),通常为1;如果是引用一个现有的集合,则将num_sems指定为0。
在semop函数中,sembuf结构中定义如下:
struct sembuf{
short sem_num; //除非使用一组信号量,否则它为0
short sem_op; //信号量在一次操作中需要改变的数据,通常是两个数,
//一个是-1,即P(等待)操作,
//一个是+1,即V(发送信号)操作。
short sem_flg; //通常为SEM_UNDO,使操作系统跟踪信号量,
//并在进程没有释放该信号量而终止时,操作系统释放信号量
};
模拟实现信号量,来进行进程控制:
#include<stdio.h>
#include<stdlib.h>
#include<sys/sem.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;
}
执行结果:
上面的例子如果不加信号量,则父进程会先执行完毕。这里加了信号量让父进程等待子进程执行完以后再执行。
附:
进程间通讯--------管道
https://blog.csdn.net/YANG_1605/article/details/96724987
进程间通讯--------共享内存
https://blog.csdn.net/YANG_1605/article/details/99321789
进程间通讯--------消息队列
https://blog.csdn.net/YANG_1605/article/details/99290536
进程间通讯--------socket