目录
本文是进程间通信系列的第3篇。前2篇请参考:《进程间通信(1) - IPS概述》,《进程间通信(2) - 信号(signal)》
1. 前言
本文章中所有例子,基于RHEL6.5。
2.信号量
信号量是一种用于提供不同进程间或一个进程间的不同线程间进行同步手段的原语,System V信号量在内核中维护。
二值信号量:其值只有0、1 两种选择,0表示资源被锁,1表示资源可用;
计数信号量:其值在0 和某个限定值之间,不限定资源数只在0 1 之间;
计数信号量集:多个信号量的集合组成信号量集。
3.信号量集结构
内核维护的信号量集结构信息如下:
定义在头文件<sys/sem.h>
struct semid_ds {
struct ipc_perm sem_perm; /* permissions .. see ipc.h */
__kernel_time_t sem_otime; /* last semop time */
__kernel_time_t sem_ctime; /* last change time */
struct sem* sem_base; /* ptr to first semaphore in array */
struct sem_queue* sem_pending; /* pending operations to be processed */
struct sem_queue** sem_pending_last; /* last pending operation */
struct sem_undo* undo; /* undo requests on this array */
unsigned short sem_nsems; /* no. of semaphores in array,代表该信号量标示符的信号量个数 */
};
其中ipc_perm 结构是内核给每个进程间通信对象维护的一个信息结构,其成员包含所有者用户id,所有者组id、创建者及其组id,以及访问模式等;
semid_ds结构体中的sem结构是内核用于维护某个给定信号量的一组值的内部结构,其结构定义:
struct sem {
int semval; //代表当前信号量的值
int sempid; //最后一个成功操作该信号量的进程id,该结构体在内核以双向链表进行维护
struct list_head sem_pending; //pending single-sop operations
};
求信号量极值
# sysctl -a | grep sem
输出格式分别是:
SEMMSL(每个信号量集最大信号量数目)
SEMMNS(整个系统可以创建的信号量最大数目)
SEMOPM(每次semop对信号量操作的最大值)
SEMMNI(系统中可以创建的信号量集中的最大数目)
4.创建信号量semget
int semget(key_t key, int nsems, int semflg); //成功返回信号量标示符,失败返回-1
key:是通过调用ftok函数得到的键值;
nsems:代表创建信号量的个数,如果只是访问而不创建则可以指定该参数为0,我们一旦创建了该信号量,就不能更改其信号量个数,只要你不删除该信号量,你就是重新调用该函数创建该键值的信号量,该函数只是返回以前创建的值,不会重新创建;
semflg:指定该信号量的读写权限,当创建信号量时不许加IPC_CREAT ,若指定IPC_CREAT | IPC_EXCL则创建是存在该信号量,创建失败;
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/sem.h>
int main() {
int semid;
key_t semkey;
if ((semkey = ftok("/etc/profile", 1)) < 0) {
perror("ftok");
exit(1);
}
//创建包含信号量的信号集,权限666,信号量集中的信号量数目是1个
if ((semid = semget(semkey, 1, IPC_CREAT | 0666)) < 0) {
perror("semget");
exit(1);
}
printf("semid=%d\n", semid);
return 0;
}
运行结果:
5.改变信号量值semop
信号量操作是PV操作,“互斥”与“同步”
int semop(int semid, struct sembuf *sops, unsigned nsops); //成功返回0,失败返回-1;
第一个参数semid 为信号量标示符;nsops为第二个参数的操作数组的个数,第二个参数sops为一个结构体数组指针,结构体定义在sys/sem.h中,结构体如下:
struct sembuf {
unsigned short sem_num; //操作信号的下标,其值可以为0 到nops
short sem_op; //信号量操作数
short sem_flg; //信号量操作标志,其值可以为0、IPC_NOWAIT 、 SEM_UNDO
//0代表在对信号量的操作不能执行的情况下,该操作阻塞到可以执行为止;
//IPC_NOWAIT 在对信号量的操作不能执行的情况下,该操作立即返回;
//SEM_UNDO当操作的进程推出后,该进程对sem进行的操作将被取消;
};
sem_op取值 >0 则信号量加上它的值,等价于进程释放信号量控制的资源
sem_op取值 =0若没有设置IPC_NOWAIT, 那么调用进程 将进入睡眠状态,直到信号量的值为0,否则进程直接返回
sem_op取值 <0则信号量加上它的值,等价于进程申请信号量控制的资源,若进程设置IPC_NOWAIT则进程再没有可用资源情况下,进程阻塞,否则直接返回。
6.控制信号量semctrl
int semctl(int semid, int semnum, int cmd, ...); // 成功返回非负值,失败返回-1
semid:信号集的标识符;
semnum:标识一个特定信号,该参数仅用于 SETVAL、GETVAL、GETPID命令;
cmd控制类型;
...说明函数参数是可选的,通过该共用体变量semun选择操作参数,各字段如下:
union semun {
int val; /* SETVAL控制,用于设置信号量的值 */
struct semid_ds __user *buf; /* 用于IPC_STAT & IPC_SET ,指向semid_ds结构指针,用于获取或者设置信号量控制结构 */
unsigned short __user *array; /*用于GETALL & SETALL,指向短整形数组指针,用于获取或者设置信号量集的值 */
struct seminfo __user *__buf; /* IPC_INFO控制命令,用于返回系统内核定义的信号量极值,为一结构指针,结构类型seminfo */
};
其中,seminfo的定义如下:
struct seminfo {
int semmap;
int semmni;
int semmns;
int semmnu;
int semmsl;
int semopm;
int semume;
int semusz;
int semvmx;
int semaem;
};
semctl的cmd参数
--IPC_STAT读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中。
--IPC_SET设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf参数。
--IPC_RMID将信号量集从系统中删除
--GETALL用于读取信号量集中的所有信号量的值,存于semnu的array中
--SETALL设置所指定的信号量集的每个成员semval的值
--GETPID返回最后一个执行semop操作的进程的PID。
--LSETVAL把的val数据成员设置为当前资源数
--GETVAL把semval中的当前值作为函数的返回,即现有的资源数,返回值为非负数
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <string.h>
typedef union semun//semctl需要的
{
int val;//保存信号量值
struct semid_ds* buf;//信号量控制结构指针
ushort* array;//无符号短整形变量
} SEMCTL_UNION;
int main() {
int n, semid;//信号量标示符变量
key_t semkey;//键值变量
SEMCTL_UNION semctl_arg;//联合类型变量
struct sembuf buf;//semop调用所需的结构变量
if ((semkey = ftok("/etc/profile", 1)) < 0)//创建键值
{
perror("ftok");
exit(1);
}
if ((semid = semget(semkey, 1, 0)) < 0)//创建信号量
{
perror("semget");
exit(2);
}
semctl_arg.val = 2;//初始化
if (semctl(semid, 0, SETVAL, semctl_arg) < 0)//设置信号量初始值
{
perror("semctl");
exit(3);
}
memset(&buf, 0x00, sizeof(struct sembuf));//清空
buf.sem_num = 0;//信号量序号从0开始,第一个
buf.sem_op = -1;//P操作,所以-1
buf.sem_flg = IPC_NOWAIT;//非阻塞
for (n = 0;; n++)//循环调用P操作,直到信号量变为0
{
if (semop(semid, &buf, 1) == -1)//P操作
{
perror("semop");
break;
}
printf("semop[%d]:current semphore value=%d\n", n, semctl(semid, 0, GETVAL, semctl_arg));
}
return 0;
}
运行结果: