面向工资编程 2018-02-06
信号量通信机制主要用来实现进程间同步,避免并发访问共享资源。信号量可以标识系统可用资源的个数。最简单的信号量为二元信号量
下图为 Linux 信号量通信机制的概念图。在实际应用中,两个进程通信可能会使用多个信号量,因此,Linux 在管理时以信号量集合的概念来管理。
通常所说的创建一个信号量实际上是创建了一个信号量集合,在这个信号量集合中,可能有多个信号量。整个信号量集合由以下部分组成。
1.信号量集合数据结构:在此数据结构中定义了整个信号量集合的基本属性,如访问权限。 2.信号量:信号量集合使用指针指向一个由数组构成的信号量单元,在此信号量单元中存储了各信号量的值。
信号量集合的数据结构定义如下:
from /usr/include/linux/sem.h struct semid_ds { struct ipc_perm sem_perm; /* permissions .. see ipc.h 权限 */ __kernel_time_t sem_otime; /* last semop time 最近semop时间 */ __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 undo队列 */ unsigned short sem_nsems; /* no. of semaphores in array 信号量数 */ };
信号量的数据结构定义如下:
from /usr/src/kernels/xxx/include/linux/sem.h xxx 为 uname -r 命令所得 struct sem { int semval; /* current value 信号量的值 */ int sempid; /* pid of last operation 最近一个操作的进程号PID */ };
1.创建信号量集合
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> /* * 第一个参数为 key 值,一般由 ftok() 函数产生 * 第二个参数为创建的信号量个数,以数组的方式存储 * 第三个参数用来标识信号量集合的权限 */ int semget(key_t key, int nsems, int semflg);
2.控制信号量集合、信号量
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> /* * 第一个参数为要操作的信号量标识符 * 第二个参数,如果要操作的是信号量则它是信号量的下标;如果操作集合,此参数无意义 * 第三个参数为要执行的操作 * 第四个参数则需根据第三个参数进行设置,其类型为 senum 的共用体 */ int semctl(int semid, int semnum, int cmd, ...); 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) */ };
3.信号量的操作
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> /* * 第一个参数为要操作的信号量的标识符 * 第二个参数为 sembuf 结构体 * 第三个参数为 sops 个数 */ int semop(int semid, struct sembuf *sops, unsigned nsops); sembuf 结构体如下: struct sembuf { unsigned short sem_num; /* semaphore index in array 信号量下标 */ short sem_op; /* semaphore operation 信号量操作 */ short sem_flg; /* operation flags 操作标识 */ }; sem_flg 为操作标识。可选为以下各值: IPC_NOWAIT:在对信号量集合的操作不能执行的情况下,调用立即返回。 SEM_UNDO:当进程退出后,该进程对 sem 进行的操作将被撤销。
下面用一个程序来演示 SEM_UNDO 的效果:
#include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> #include <unistd.h> #include <stdlib.h> 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 */ }; static void sem_init(int id) { union semun sem; sem.val = 10; /* 初始化信号量的值 */ semctl(id, 0, SETVAL, sem); } static void sem_v(int id) { struct sembuf buf = { .sem_num = 0, .sem_op = -1, /* 可改为 SEM_UNDO 查看结果 */ // .sem_flg = 0, .sem_flg = SEM_UNDO, }; /* 操作信号量 */ semop(id, &buf, 1); } static int get_val(int id) { /* 获取信号量的值 */ return semctl(id, 0, GETVAL); } int main() { int sem_id, pid; /* 创建信号量集合 */ sem_id = semget((key_t)1004, 1, IPC_CREAT | 0600); sem_init(sem_id); if((pid = fork()) == -1){ perror("fork Err"); exit(0); } else if(!pid){ sem_v(sem_id); printf("child : %d \n", get_val(sem_id)); } else{ sleep(1); printf("parent : %d \n", get_val(sem_id)); } return 0; }
如果未采用 SEM_UNDO 标识,子进程输出 9 ,父进程输出 9;
如果采用了 SEM_UNDO 标识,子进程输出 9,父进程输出 10。