在学习进程间通信时,信号量可能是我们会感到有点头痛的地方。事实上,我们知道了信号量的作用再去理解信号量就会容易很多。先将信号量(System V信号量)介绍如下:
1)为什么需要使用信号量?
因为我们的系统资源是有限的,但是系统是多任务的,所以存在多进程、多线程,可能同时需要对某一个资源进行访问,用来保证资源的有序访问。
否则会产生不可预计的结果。
2)信号量是什么东西?
信号量是一个计数值,这个值表示当前可用的资源数,这个值也是可用是一个负数,负数的话,表示的是等待的进程数量。还有一个指针,这个指针指向等待该信号量的进程
P操作,减一操作。
V操作,加一操作。
操作属于原子操作,是不可被打断一种操作,一定要等待这个操作的完成。要包含的代码要尽量段,而且不要有循环。
3)如何使用信号量?
(1)、产生一个key值。
SYNOPSIS
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
pathname (which must refer to an existing, accessible file) 这个路径必须是一个存在的、可访问的的文件
proj_id (which must be nonzero) to generate a key_t 必须是一个非零的数字,然后通过这个函数就会产生一个key_t类型的标示符。
返回值:成功就会返回 对应的key值,错误返回-1,并且可以把对应的错误码打印出来看一下是什么错误。
(2)、根据这个key值,去创建一个信号量。不同进程就可以根据这个信号量的ID去找到这个信号量,然后操作它。
SYNOPSIS
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
key: 就是我们第一步通过ftok产生的key值。
nsems:信号量的个数。
semflg:
创建 IPC_CREAT | 权限位 例如: IPC_CREAT | 0664
(3)、要对这个信号量,进行初始化。
SYNOPSIS
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);
semid:是创建的信号量的ID
semnum:表示是对第几个信号量来进行设置。
cmd:SETVAL 设置信号量的初始值。
第四个参数,取决于第三个参数,如果有第四个参数的话,它是一个联合体,这个联合体是这样定义的
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
(4)、调用这个信号量去保护临界资源。调用P操作,用完之后,调用V操作。
SYNOPSIS
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, unsigned nsops);
semid:这个信号量的ID
struct sembuf, containing the following members:
unsigned short sem_num; /* semaphore number操作第几个信号量 */
short sem_op; /* semaphore operation P还是V操作,如果是互斥式的信号量,P操作,这个就等于-1,如果是v操作就等于1 */
short sem_flg; /* operation flags SEM_UNDO */
成功返回0,失败返回-1;
具体代码如下:
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include <stdio.h>
union semun
{
int val;
struct semid_df*buf;
unsigned short *array;
};
int sem_p(int semid)
{
struct sembuf sbuf;
sbuf.sem_num=0; //表示对第几个信号量作操作
sbuf.sem_op=-1;//p操作,为减一操作
sbuf.sem_flg=SEM_UNDO; //默认使用这个标志位
if((semop(semid,&sbuf,1))==-1) //去设置这个信号量
{
perror("p op fail");
return -1;
}
return 0;
}
int sem_v(int semid)
{
struct sembuf sbuf;
sbuf.sem_num=0;
sbuf.sem_op=1;
sbuf.sem_flg=SEM_UNDO;
if((semop(semid,&sbuf,1))==-1)
{
perror("p op fail");
return -1;
}
return 0;
}
int main()
{
key_t key;
int semid,ret;
pid_t pid;
union semun tmp;
key =ftok("/home/gec",2);//产生一个key值,用来创建一个信号量
printf("key=%d",key);
if(key<0)
{
perror("ftok fail");
exit(1);
}
//创建这个信号量 1代表信号量的个数,返回信号量的ID
semid = semget(key,1,IPC_CREAT|0777);
if(semid<0)
{
perror("semget fail");
exit(-1);
}
tmp.val=0;
//初始化信号量的初始值为0,所以下面的操作就要先用v操作来释放这个信号量
ret=semctl(semid,0,SETVAL,tmp);
if(ret<0)
{
perror("semctl fail");
exit(-1);
}
pid = fork();
if(pid < 0)
{
perror("fork fail");
exit(-1);
}
if(pid == 0)
{
//因为信号量的初始值为0,所以不用p操作,如果这里也用p操作大家都会锁死
// sem_p(semid);
sleep(3);
printf("Child pid :%d\n",getpid());
sem_v(semid);//完了之后用V操作,回覆信号量初始值为1
}
else if(pid > 0)
{
//父进程等待信号量的恢复,恢复了之后,执行P操作,然后往下执行
sem_p(semid);
printf("Parent pid:%d\n",getpid());
//用完之后,执行V操作,恢复信号量
sem_v(semid);
//用完之后,把这个信号量删除
semctl(semid,0,IPC_RMID,tmp);
}
return 0;
}