一、信号量
1.信号量的基本概念
信号量是一个计数器,常用于处理进程或线程的同步问题,特别是对临界资源的访问的同步。临界资源可以简单的理解为在某一个时刻只能由一个进程或线程进行操作的资源,这里的资源可以是一段代码、一个变量或某种硬件资源。信号量的值大于或等于0时表示可供并发进程使用的资源实体数;小于0时代表正在等待使用临界资源的进程数。
2.信号量的创建与使用
linux下使用系统函数semget创建或打开信号集。这个函数定义在头文件sys/sem.h中,函数原型如下:
int semget(key_t key,int nsems,int semflg);
该函数执行成功则返回一个信号集的标志符,失败返回-1。
函数的第一个参数是由ftok()得到的键值;第二个参数nsems指明要创建的信号集包含的信号个数,如果只是打开信号集,把nsems设置为0即可;第三个参数semflg为操作标志,可以取如下:
IPC_CREAT:调用semget()时,它会将此值与系统中其他信号集的key进行对比,如果存在相同的key,说明信号集已存在,此时返回该信号集的标识符,否则新建一个信号集并返回其标志符。
IPC_EXCL:(单独存在没有意义)
当semflg取IPC_CREAT|ICP_EXCL时,表示如果发现信号集已经存在,则出错,返回EEXIST。
3.信号量的操作
信号量的值与相应资源的使用情况有关,当它的值大于0时,表示当前可用资源的数量,当它的值小于0时,其绝对值表示等待使用该资源的进程的个数。信号量的值仅能由PV操作来改变。在Linux下,PV操作通过调用函数semop实现。该函数定义在头文件sys/sem.h,原型如下:
int semop(int semid,struct sembuf *sops,size_t nsops);
函数的参数
semid:为信号集的标志符;
sops:指向进行操作的结构体数组首地址;
nsops:指出将要进行操作的信号的个数。
函数的返回:
semop调用成功返回0,否则返回-1;
semop的第二个参数sops指向的结构体数组中,每个sembuf结构体对应一个特定信号的操作。
struct sembuf
{
ushort sem_num; //信号在信号集中的索引
short sem_op; //操作类型
short sem_flg; //操作标志
};
sem_op>0 信号加上sem_op的值,表示进程释放控制的资源
sem_op=0 如果没有设置IPC_NOWAIT,则调用进程进入睡眠状态,直到信号值为0;
否则进程不会睡眠,直接返回EAGAIN
sem_op<0 信号加上sem_op的值。若没有设置IPC_NOWAIT,则调用进程阻塞,直到资源可用;否则进程直接返回EAGAIN
sem_flg:信号操作标志,可能的选择有两种
IPC_NOWAIT //对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息。
SEM_UNDO //程序结束时(不论正常或不正常),保证信号值会被重设为semop()调用前的值。这样做的目的在于避免程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定。
4.信号量的控制
使用信号集时,往往需要对信号集进行一些控制操作,比如删除信号集、对内核维护的信号集的数据结构semid_ds进行设置、获取信号集中信号值等。通过semctl控制函数可以完成这些操作,该函数定义在sys/sem.h,如下所示:
int semctl(int sem_id, int sem_num, int command, ...);
第一个参数,sem_id,是由semget所获得的信号量标识符。sem_num参数是信号量数目。当我们使用信号量数组时会用到这个参数。通常,如果 这是第一个且是唯一的一个信号量,这个值为0。command参数是要执行的动作,而如果提供了额外的参数,则是union semun,根据X/OPEN规范,这个参数至少包括下列参数:
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
}
许多版本的Linux在头文件(通常为sem.h)中定义了semun联合,尽管X/Open确认说我们必须定义我们自己的联合。如果我们发现我们确实需 要定义我们自己的联合,我们可以查看semctl手册页了解定义。如果有这样的情况,建议使用手册页中提供的定义,尽管这个定义与上面的有区别。
有多个不同的command值可以用于semctl。在这里我们描述两个会经常用到的值。要了解semctl功能的详细信息,我们应该查看手册页。
这两个通常的command值为:
SETVAL:用于初始化信号量为一个已知的值。所需要的值作为联合semun的val成员来传递。在信号量第一次使用之前需要设置信号量。
IPC_RMID:当信号量不再需要时用于删除一个信号量标识。
semctl函数依据command参数会返回不同的值。对于SETVAL与IPC_RMID,如果成功则会返回0,否则会返回-1。
二、代码示例
sem_com.h
/* sem_com.h */
#ifndef SEM_COM_H
#define SEM_COM_H
#include <sys/ipc.h>
#include <sys/sem.h>
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
};
int init_sem(int, int);
int del_sem(int);
int sem_p(int);
int sem_v(int);
#endif /* SEM_COM_H */
sem_com.c
/* sem_com.c */
#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");
return -1;
}
}
int sem_p(int sem_id)
{
struct sembuf sem_b;
sem_b.sem_num = 0; /*id*/
sem_b.sem_op = -1; /* P operation*/
sem_b.sem_flg = SEM_UNDO;
if (semop(sem_id, &sem_b, 1) == -1)
{
perror("P operation");
return -1;
}
return 0;
}
int sem_v(int sem_id)
{
struct sembuf sem_b;
sem_b.sem_num = 0; /* id */
sem_b.sem_op = 1; /* V operation */
sem_b.sem_flg = SEM_UNDO;
if (semop(sem_id, &sem_b, 1) == -1)
{
perror("V operation");
return -1;
}
return 0;
}
fork.c
/* fork.c */
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define DELAY_TIME 3
int main(void)
{
pid_t result;
int sem_id;
sem_id = semget(ftok(".", 'a'), 1, 0666|IPC_CREAT); /* 创建一个信号量*/
init_sem(sem_id, 0); //初始值设为0资源被占用
/*调用fork函数,其返回值为result*/
result = fork();
/*通过result的值来判断fork函数的返回情况,首先进行出错处理*/
if(result == -1)
{
perror("Fork\n");
}
else if (result == 0) /*返回值为0代表子进程*/
{
printf("Child process will wait for some seconds...\n");
//sem_p(sem_id);
sleep(DELAY_TIME);
printf("The returned value is %d in the child process(PID = %d)\n", result, getpid());
sem_v(sem_id); //释放资源
}
else /*返回值大于0代表父进程*/
{
puts("123");
sem_p(sem_id); //等待资源,如果子进程不释放 就一直等
printf("The returned value is %d in the father process(PID = %d)\n", result, getpid());
sem_v(sem_id); //释放资源
del_sem(sem_id); //删除信号量
}
exit(0);
}
运行结果:
pc@ubuntu:~/linux_lan/pipe/10.5/test$ ./fork
123
Child process will wait for some seconds...
The returned value is 0 in the child process(PID = 4669)
The returned value is 4669 in the father process(PID = 4668)
分析:
通过运行结果可以看到,程序先执行父进程,执行到sem_p()时,由于之前init_sem()占用的资源未释放,所以父进程等待资源释放,开始执行子进程,子进程调用sem_v()释放资源后,父进程才得以执行。如果子进程在调用sem_v()之前调用sem_p(),则子进程也将被阻塞。
更好的示例: