进程通信之信号量
信号量机制是为了处理操作系统在对于临界资源的管理过程中,容易出现多个程序同时访问一个共享资源,然后引发死锁,误读等问题所提出的一种解决方案。
信号量是一个特殊的变量,只能对其进行等待和发送两种操作。等待操作是P操作,当信号量值为0的时候,程序等待,当值大于0的时候,使值减一,程序继续执行。发送操作是V操作,如果有进程等待,则唤起该进程,否则的话,将信号量的值加1.
#include <sys/sem.h>
//创建/获取信号量
int semget(key_t key, int num_sems, int sem_flags);
/*
参数:
key是整数值(唯一非零),就是Linux线程操作中经常用到的键值,可以通过ftok函数得到,不相关的进程可以通过它访问一个信号量,它代表程序可能要使用的某个资源,程序对所有信号量的访问都是间接的,程序先通过调用semget函数并提供一个键,再由系统生成一个相应的信号标识符(semget函数的返回值),只有semget函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。如果多个程序使用相同的key值,key将负责协调工作。
num_sems:指定需要的信号量数目
当semget创建新的信号量集合时,必须指定集合中信号量的个数(即num_sems),通常为1; 如果是引用一个现有的集合,则将num_sems指定为 0 。
sem_flags是一组标志,当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。
返回值:semget函数成功返回一个相应信号标识符(非零),失败返回-1
*/
//修改信号量p v操作
int semop(int semid, struct sembuf *sops, size_t numops);
/*
参数:
semid:信号量集的标识符
sembuf:结构体格式如下 结构体指针(参数注意&)
struct sembuf{
short sem_num;//信号在信号集中的索引,0代表第一个信号,1代表第二个信号
short sem_op;//操作类型: 信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,
//一个是+1,即V(发送信号)操作。
结构体中的sem_op 参数:
sem_op > 0
信号加上 sem_op 的值,表示进程释放控制的资源;
sem_op = 0
如果sem_flg没有设置IPC_NOWAIT,则调用进程进入睡眠状态,直到信号量的值为0;否则进程不会睡眠,直接返回 EAGAIN
sem_op < 0
信号加上 sem_op 的值。若没有设置 IPC_NOWAIT ,则调用进程阻塞,直到资源可用;否则进程直接返回EAGAIN
short sem_flg;//操作标志: 通常为SEM_UNDO,使操作系统跟踪信号,
//并在进程没有释放该信号量而终止时,操作系统释放信号量
该参数可设置为 IPC_NOWAIT 或 SEM_UNDO 两种状态,通常为SEM_UNDO
};
numops:指出将要进行操作的信号的个数
*/
//初始化和删除
int semctl(int sem_id,int sem_num,int command,[union semun sem_union]);
/*
semid :信号量集的标识符;
sem_num:即将要进行操作的信号量的编号,即信号量集合的索引值,其中第一个信号量的索引值为0。
semun:是一个联合体,一般用到val,表示要传给信号量的初始值
union semun{
int val; //一般用到val,表示要传给信号量的初始值
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
command:将要在集合上执行的命令,通常用特定的宏代替:
常用的宏有;
SETVAL:用于初始化信号量为一个已知的值。所需要的值作为联合semun的val成员来传递。在信号量第一次使用之前需要设置信号量。
eg:初始化时, semctl(semid,0,SETVAL,initsem);
IPC_RMID:从内核删除该信号量集合
eg:删除信号量 semctl(semid,0,IPC_RMID)
*/
#include<stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
//联合类型semun定义
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) */
};
// V操作:
// 释放资源并将信号量值+1
// 如果有进程正在挂起等待,则唤醒它们
void vbackkey(int id)
{
struct sembuf set;
set.sem_num = 0; //信号量编号 对第一个信号量操作
set.sem_op = 1; //V操作,加一
set.sem_flg = SEM_UNDO;
semop(id,&set,1); //参数2为结构体指针 &set
printf("backkey ok\n");
}
// P操作:
// 若信号量值为1,获取资源并将信号量值-1
// 若信号量值为0,进程挂起等待
void pgetkey(int id)
{
struct sembuf set;
set.sem_num = 0;
set.sem_op = -1; //P操作,减一
set.sem_flg = SEM_UNDO;
semop(id,&set,1);
printf("getkey ok\n");
}
int main(int argc,char **argv)
{
key_t key;
key = ftok(".",2);
int semid;
// 创建一个新的信号量或者是取得一个已有信号量的键
semid = semget(key,1,IPC_CREAT|0666);
//初始化
union semun initsem;
initsem.val = 0; //将信号量值设为0!目的为了父子进程先后顺序
semctl(semid,0,SETVAL,initsem);//semtcl进行初始化
int pid = fork();
if(pid > 0)//父进程
{
//因为将信号量初始化为0,所以父进程进行P操作,父进程会被挂起等待
//待子进程进行V操作后,方可继续执行
pgetkey(semid);
printf("this is father\n");
vbackkey(semid);
//删除信号量
semctl(semid,0,IPC_RMID);
}
else if(pid == 0)//子进程
{
//父进程被挂起,子进程就会先执行printf
printf("this is child\n");
vbackkey(semid);
//子进程进行V操作,val加1,父进程就可以继续执行p
}
else
{
printf("fork error\n");
}
}
线程同步
linux 信号量相关函数都声明头文件 semaphore.h 头文件中,所以使用信号量之前需要先包含头文件
#include <semaphore.h>
信号量的创建就像声明一般的变量一样简单,例如:sem_t sem,之后对该信号量进行初始化和使用。
sem_init
该函数用于创建信号量,其原型如下:
int sem_init(sem_t *sem, int pshared, unsigned int value);
该函数初始化由 sem 指向的信号对象,并给它一个初始的整数值 value。
pshared 控制信号量的类型,值为 0 代表该信号量用于多线程间的同步,值如果大于 0 表示可以共享,用于多个相关进程间的同步
参数 pshared > 0 时指定了 sem 处于共享内存区域,所以可以在进程间共享该变量
sem_wait
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
sem_wait 是一个阻塞的函数,测试所指定信号量的值,它的操作是原子的。若 sem value > 0,则该信号量值减去 1 并立即返回。若sem value = 0,则阻塞直到 sem value > 0,此时立即减去 1,然后返回。
sem_trywait 函数是非阻塞的函数,它会尝试获取获取 sem value 值,如果 sem value = 0,不是阻塞住,而是直接返回一个错误 EAGAIN。
sem_post
把指定的信号量 sem 的值加 1,唤醒正在等待该信号量的任意线程。
int sem_post(sem_t *sem);
sem_getvalue
int sem_getvalue(sem_t *sem, int *sval);
获取信号量 sem 的当前值,把该值保存在 sval,若有 1 个或者多个线程正在调用 sem_wait 阻塞在该信号量上,该函数返回阻塞在该信号量上进程或线程个数。
sem_destroy
该函数用于对用完的信号量的清理。它的原型如下:
int sem_destroy(sem_t *sem);
成功则返回 0,失败返回 -1