多进程——System V信号量
概述
lLinux 操作系统中有两种信号量分别为System V和Posix 。其中Posix 信号量可以用于多线程和多进程同步,但是Posix 信号量一般只有0和1两个值。但是system V信号量可以的数值范围可以变化一般默认的范围为0~2^15 - 1,同时对于system V信号量来说其申请的不是以个来申请而是以组的概念进行申请。
使用
创建system V信号量
system V信号量使用semget函数进行申请。semget函数在成功申请到信号量之后,会返回一个信号量的标记值,用于之后对信号量的控制和访问。
该函数调用如下:
int semget(key_t key, int nsems, int semflg);
其中key为键值需要具有唯一性,如果这个值相同那么对应的信号量相同。该值一般由ftok函数产生,ftok函数负责产生一个在系统中具有唯一性的值。ftok函数如下:
key_t ftok(const char *pathname, int proj_id);
其中的pathname需要为一个文件路径,该路径必须是具体存在的,因为ftok在生成key值时需要使用到文件的inode值,因为inode值在文件系统中具有唯一性。第二个参数proj_id为偏移值,这个值的末尾8位也同时参与到key的计算中,防止通过inode值进行生成时由于文件相同出现重复。
semget的第二个参数为nsem,即所需申请的system V信号量组中信号量的数目。
第三个参数semflg负责指定这个system V信号量的的读写权限,该权限和system V的作用范围相关,如果希望信号量的适用范围尽可能广,那么可以设置为0666。
对system V信号量赋初值
赋初值需要调用如下函数
semctl(data_req_p_id, 0, SETVAL, sem_union);
需要注意的是上面赋初值的使用方式是semctl较为简单的使用方式,semctl本质上有很多调用方式。
- 其中data_rep_p_id是信号量的标记值。
- 第二个参数0指明操作信号量组中的第几个信号量,(system V信号量以组的方式进行分配)
- 第三个参数SETVAL是对应的命令,该参数指明semctl函数应该进行的操作为赋值操作
- 第四个参数sem_union是一个联合体,负责传递具体的初值
定义如下:
union semun {
int val; /* value for SETVAL */
struct semid_ds *buf; /* buffer for IPC_STAT, IPC_SET */
unsigned short int *array; /* array for GETALL, SETALL */
struct seminfo *__buf; /* buffer for IPC_INFO */
};
生成system V信号量之后,需要对system V信号量赋予初值。需要注意的时,对于system V信号量来说,其创建和赋初值是分开的,这将导致一个system V信号量在赋值时可能会出现多个进程同时赋值导致初值不确定的情况,不过这一点可以通过下面的代码进行避免:
if (0 <= (data_req_p_id = semget(data_qp_id, 1, oflag)))
{
sem_union.val = 0;
semctl(data_req_p_id, 0, SETVAL, sem_union);
}
else if (errno == EEXIST)
{
data_req_p_id = semget(data_qp_id, 1, SVSEM_MODE);
}
else
{
perror("data_req_p_id semget error\n");
}
不过上面的方式只是简单的适用于不对等的进程之间(一个进程为信号量的中心持有进程开始就被创建,其他进程在之后被陆续创建)。另外一个更好的方法时通过信号量中的一个属性值sem_otime来进行同步。这个方法的原理在于sem_otime在信号量创建时被置为0,只有在调用sem_op函数后才会变为非零值。
因此只要在上面的data_req_p_id = semget(data_qp_id, 1, SVSEM_MODE);
后面使用IPC_STAT命令调用semctl函数,等待sem_otime 变为非0时即可。这个方法可以更加有效地防止出现system V信号量初值不确定的情况发生。
sytem信号量操作
对信号量的操作函数为semop,函数申明如下所示:
int semop(int semid, struct sembuf *sops, size_t nsops);
- 第一个参数semid为信号量的标记值
- 第二个参数sembuf如下所示:
struct sembuf
{
unsigned short int sem_num; /* semaphore number */
short int sem_op; /* semaphore operation */
short int sem_flg; /* operation flag */
};
其中sem_num指的不是数目,而是system V信号量组中序列号(本质上就是几个和第几个的区别)。sem_op指的是对信号量的操作值。sem_flg指对信号量的操作的flag。这个flag总共有三种情况:
- 第一种为不指定,默认状态为阻塞状态
- 第二种IPC_NOWAIT,设置为不阻塞状态,
- 第三种为SEM_UNDO,这一种需要特殊说明,SEM_UNDO并不是指不做任何事情,该值的实际意义为将每一次的操作值都加入到一个中间变量semadj中,具体如下所示:
sem_op < 0 : semadj = semadj + sem_op的绝对值
sem_op > 0 : semadj = semadj - sem_op的绝对值
当某一个调用该信号量的进程退出时,semadj的值会加到当前信号量的值semval上。所以SEM_UNDO的真正意义为“复旧”。当定义了该属性的进程退出时,该进程对信号量的操作会变得和原始没有允许改进程时相同。因此当信号量用来作为同步信号量即一个进程负责P操作,另外一个进程负责V操作时,不能指定该属性,因为单独一个进程对该信号量的操作都是独立的P或者V,这会导致单独一个进程中的semadj中间变量的值一直+1或者-1,最后出现semadj越界的情况,导致信号量同步失败。
- 第三个参数为nsops指明sembuf的数目(即对system V信号量组还总多少个信号量进行操作)。