在多任务的操作系统中会运行多个进程,这些进程会有一些关联,如多个进程会为了完成一个任务会相互协作,这就是进程间的同步,有时为了争夺有限的系统资源(硬件或软件资源)会进入竞争状态,这就是进程的互斥关系
进程间的同步与互斥的根源在于临界资源的问题,临界资源是同一时刻只允许有有限个(通常是一个)进程可以访问,修改的资源,同通常包括硬件资源(处理器,内存,存储器,以及一些外围设备)和软件资源(共享代码段,共享结构或者变量),访问临界资源的代码叫做临界区,临界区本省又是临界资源。
信号量------信号量是用来解决进程间同步和互斥问题的一种进程之间的通信机制,包括一个称为信号量的变量,在该信号量下等待资源的进程等待队列,和对信号量进行的两个原子操作(PV操作).信号量对应了一种资源,是一个非负的整数,他的值表示当前可以使用的资源数目,若他为0表示没有资源可以使用,下面对P,V操作的解释:
P操作:如果当前有可以使用的资源(信号量>0),则占用一个资源(信号量减一,进入临界区代码);如果没有可用的资源(信号量的值为0),则这个进程会被阻塞,直到系统将资源分配这个进程(进入等待队列,一直等到资源轮到这个进程)
V操作:如果在该信号量下的等待队列中有进程在等待资源,就唤醒一个阻塞进程,如果没有进程等待资源,就释放一个资源(信号量加一)
使用信号量访问临界区的伪代码如下所示:
最简单的信号量只能去0,1两种值,这种信号量成为二维信号量,二维信号量比较容易扩展到多维信号量
信号量的使用步骤如下:
- 创建信号量或者使用系统已经存在的信号量,此时需要调用semget()函数,不同进程使用同一个信号量键值获得同一个信号量
- 初始化信号量,使用semctl()的SETVAL操作,当使用二维信号量时,通常将信号量初始化为1
- 进行信号量的PV操作,此时调用semopt()函数,这一步是实现进程之间同步与互斥的核心工作部分
- 如果不需要信号量,需要从系统中删除他,可以使用semctl()函数的IPC_RMID操作,但值得注意的是对已将删除的信号量程序不要有操作
semget()函数语法如下:
semctl()函数语法如下:
semopt()函数语法如下:
下面是一个信号量控制父子进程的程序:
#include "sem_com.h"
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)
{
perror("initialize semaphore") ;
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 semaphore") ;
reutrn -1 ;
}
}
int sem_p(int sem_id)//P操作
{
struct sembuf sem_b ;
sem_b.sem_num = 0 ;
sem_b.sem_op = -1 ;
sem_b.sem_flg = SEM_UNDO ;
if (semop(sem_id, &sem_b, 1) == -1)
{
perror("semop") ;
return -1 ;
}
return 0 ;
}
int sem_v(int sem_id)//V操作
{
struct sembuf sem_b ;
sem_b.sem_num = 0 ;
sem_b.sem_op = 1 ;
sem_b.sem_flg = SEM_UNDO ;
if (semop(sem_id, &sem_b, 1) == -1)
{
perror("semop") ;
return -1 ;
}
return 0 ;
}
上面的代码把关于信号量的创建,初始化,p,v操作给封装起来了,便于使用
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define DELAY_TIME 3
int main()
{
pid_t result ;
int sem_id ;
sem_id = semget(ftok(".", 'a'), 1, 0666|IPC_CREAT) ;
init_sem(sem_id, 0) ;//将信号量的初始值为0
result = fork() ;
if (result == -1)
{
perror("fork\n") ;
}
else if (result == 0)
{
printf("child process will wait for some seconds") ;
sleep(DELAY_TIME) ;
printf("the returned value is %d in the child process(PID = %d)\n",
result, getpid()) ;
sem_v(sem_id) ;//v操作
}
else
{
sem_p(sem_id) ;//p操作
printf("the returned value is %d in the father process(pid = %d)\n",
result, getpid()) ;
sem_v(sem_id) ;//v操作
del_sem(sem_id) ;//删除信号量
}
exit(0) ;
}
运行结果如下:
我刚开始就是弄不懂咋回事,为什么是子进程先退出,我问我以前的一个同事,发现这个信号量的是这样初始化的
init_sem(sem_id, 0) ;
他将信号量的初始值初始化为0,这样父进程调用sem_p(sem_id)就会进入等待队列中,子进程先执行,然后释放资源,此时父进程才会执行。
如果把信号量的操作删除则运行结果如下:
此时父进程先退出,因为子进程需要sleep几秒钟。