一.信号量
信号量和P,V原语由Dijkstra提出
信号量
互斥:p,v在同一个进程中
同步:p.v在不同进程中
信号量值含义
s>0:s表示可用资源的个数
s=0:表示无可用资源,无等待进程
s<0:|s|表示等待队列中进程个数
struct semaphore
{
int value; //计数值
pointer_PCB queue; //队列
}
P原语----申请资源
P(s)
{
s.value--;
if(s.value <0)
{
该进程状态置为等待状态
将该进程的PCB插入相应的等待队列s.queue末尾
}
}
V原语--- 归还资源
V(s)
{
s.value++;
if(s.value <= 0)
{
唤醒相应等待队列s.queue中等待的一个进程
改变其状态为就绪态
并将其插入就绪队列
}
}
二,信号量集结构
对于系统的每个信号量集,内核维护一个如下的信息结构,定义在<sys/sem.h>头文件中,ubuntu中的路径:/usr/include/linux/sem.h
下图中变量sem_nsems的值为2,另外该集合的两个成员分别用下标[0]和[1]标记
三.信号量集函数
以下是几个信号量集操作函数:
#include <sys/types.h>#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
int semctl(int semid, int semnum, int cmd, ...);
int semop(int semid, struct sembuf *sops, unsigned nsops);
(1)
功能:用来创建和访问一个信号量集
原型 int semget(key_t key, int nsems, int semflg);
参数
key: 信号量集的名字
nsems:信号量集中信号量的个数
semflg: 由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该信号量集的标识码;失败返回-1
(2)
功能:用于控制信号量集
原型 int semctl(int semid, int semnum, int cmd, ...);
参数
semid:由semget返回的信号量集标识码
semnum:信号量集中信号量的序号,从0开始编号
cmd:将要采取的动作(有三个可取值)
最后一个参数是 union semun,具体成员根据cmd 的不同而不同
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) */
};
返回值:成功返回0;失败返回-1
cmd 取值如下:
SETVAL 设置信号量集中的信号量的计数值
GETVAL 获取信号量集中的信号量的计数值
IPC_STAT 把semid_ds结构中的数据设置为信号量集的当前关联值
IPC_SET 在进程有足够权限的前提下,把信号量集的当前关联值设置为semid_ds数据结构中给出的值
IPC_RMID 删除信号量集
(3)
功能:用来创建和访问一个信号量集(PV操作)
原型 int semop(int semid, struct sembuf *sops, unsigned nsops);
参数
semid: 是该信号量集的标识码,也就是semget函数的返回值
sops: 是个指向一个结构体的指针
nsops: 信号量的个数
返回值:成功返回0;失败返回-1
struct sembuf
{
unsigned short sem_num; // semaphore number 信号量编号,操作哪个信号量
short sem_op; // semaphore operation 操作方式 , PV操作
short sem_flg; // operation flags ,一般为不等待和撤销两种标志
};
sem_num:是信号量的编号。
sem_op:是信号量一次PV操作时加减的数值,一般只会用到两个值,一个是“-1”,也就是P操作,等待信号量变得可用;另一个是“+1”,也就是我们的V操作,发出信号量已经变得可用。当然+-n 和0 都是允许的。需要注意的是只有+n 才确保将semval +n 后马上返回,而-n 和 0 很可能是会阻塞的,+-n 需要进程对信号量集有写的权限,而0 只需要读的权限。
sem_flag:的两个取值是IPC_NOWAIT或SEM_UNDO,设为前者如果当某个信号量的资源为0时进行P操作,此时不会阻塞等待,而是直接返回资源不可用的错误;设为后者,当退出进程时对信号量资源的操作撤销;不关心时设置为0即可。
当要对一个信号量集中的多个信号量进行操作时,sops 是结构体数组的指针,此时nsops 不为1。此时对多个信号量的操作是作为一个单元原子操作,要么全部执行,要么全部不执行。
四.信号量示例
#include <sys/types.h> #include <unistd.h> #include <sys/ipc.h> #include <sys/sem.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) union semun { int val; /* value for SETVAL */ struct semid_ds *buf; /* buffer for IPC_STAT, IPC_SET */ unsigned short *array; /* array for GETALL, SETALL */ /* Linux specific part: */ struct seminfo *__buf; /* buffer for IPC_INFO */ }; //生成信号量 int sem_create(key_t key) { int semid = semget(key, 1, 0666 | IPC_CREAT | IPC_EXCL); if (semid == -1) ERR_EXIT("semget"); return semid; } //打开信号量 int sem_open(key_t key) { int semid = semget(key, 0, 0); if (semid == -1) ERR_EXIT("semget"); return semid; } //P操作 int sem_p(int semid) { struct sembuf sb = {0, -1, 0/*IPC_NOWAIT*//*SEM_UNDO*/}; int ret = semop(semid, &sb, 1); if (ret == -1) ERR_EXIT("semop"); return ret; } // V操作 int sem_v(int semid) { struct sembuf sb = {0, 1, 0 /*SEM_UNDO*/}; int ret = semop(semid, &sb, 1); if (ret == -1) ERR_EXIT("semop"); return ret; } // 删除信号量集 int sem_d(int semid) { int ret = semctl(semid, 0, IPC_RMID, 0); if (ret == -1) ERR_EXIT("semctl"); return ret; } //设置信号量集中的信号量的计数值 int sem_setval(int semid, int val) { union semun su; su.val = val; int ret = semctl(semid, 0, SETVAL, su); if (ret == -1) ERR_EXIT("semctl"); printf("value updated...\n"); return ret; } //获取信号量集中的信号量的计数值 int sem_getval(int semid) { int ret = semctl(semid, 0, GETVAL, 0); if (ret == -1) ERR_EXIT("semctl"); printf("current val is %d\n", ret); return ret; } int sem_getmode(int semid) { union semun su; struct semid_ds sem; su.buf = &sem; int ret = semctl(semid, 0, IPC_STAT, su); // su为一个输出参数 if (ret == -1) ERR_EXIT("semctl"); printf("current permissions is %o\n", su.buf->sem_perm.mode); // 有两个嵌套的结构体 return ret; } int sem_setmode(int semid, char *mode) { union semun su; struct semid_ds sem; su.buf = &sem; int ret = semctl(semid, 0, IPC_STAT, su); if (ret == -1) ERR_EXIT("semctl"); printf("current permissions is %o\n", su.buf->sem_perm.mode); sscanf(mode, "%o", (unsigned int *)&su.buf->sem_perm.mode); //格式化输入 //在进程有足够权限的前提下,把信号量集的当前关联值设置为semid_ds数据结构中给出的值 ret = semctl(semid, 0, IPC_SET, su); if (ret == -1) ERR_EXIT("semctl"); printf("permissions updated...\n"); return ret; } void usage(void) { fprintf(stderr, "usage:\n"); fprintf(stderr, "semtool -c\n"); fprintf(stderr, "semtool -d\n"); fprintf(stderr, "semtool -p\n"); fprintf(stderr, "semtool -v\n"); fprintf(stderr, "semtool -s <val>\n"); fprintf(stderr, "semtool -g\n"); fprintf(stderr, "semtool -f\n"); fprintf(stderr, "semtool -m <mode>\n"); } int main(int argc, char *argv[]) { int opt; opt = getopt(argc, argv, "cdpvs:gfm:"); // 解析参数,-s,-m根参数 if (opt == '?') exit(EXIT_FAILURE); if (opt == -1) { usage(); exit(EXIT_FAILURE); } key_t key = ftok(".", 's'); // 生成关键字,低序8位非零 int semid; switch (opt) { case 'c': //创建 sem_create(key); break; case 'p': // p操作 semid = sem_open(key); sem_p(semid); sem_getval(semid); break; case 'v': // V操作 semid = sem_open(key); sem_v(semid); sem_getval(semid); break; case 'd': //删除操作 semid = sem_open(key); sem_d(semid); break; case 's': // 设置信号量 ,需要参数 semid = sem_open(key); sem_setval(semid, atoi(optarg)); break; case 'g': // 获取信号量计数值 semid = sem_open(key); sem_getval(semid); break; case 'f': // 获取权限 semid = sem_open(key); sem_getmode(semid); break; case 'm': // 设置权限,需要参数 semid = sem_open(key); sem_setmode(semid, argv[2]); break;<span style="font-size:18px;"><strong>当要对一个信号量集中的多个信号量进行操作时,sops 是结构体数组的指针,此时nsops 不为1。此时对多个信号量的操作是作为一个单元原子操作,要么全部执行,要么全部不执行。</strong></span> } return 0; }