4 信号量
- 信号量是专门用来解决进程同步与互斥问题的一种通信机制,它与信号无关;
- 不同于管道、FIFO以及消息队列,一般不用来传输数据;
- 信号量包括:表示资源数量的非负整型变量、修改信号量的原子操作P和V、该信号量下等待资源的进程队列。
使用信号量进行通信时,通常需要如下步骤:
- 创建信号量/信号量集或者获取系统中已有的信号量/信号量集;
- 初始化信号量:早期信号量通常初始化为1,但有些进程一次需要多个同类的临界资源或多个不同类且唯一的临界资源,因此可能需要初始化信号量集;
- 信号量的P、V操作:根据进程请求修改信号量的数量,P操作使信号量-1,V操作使信号量+1;
- 从系统中删除不需要的信号量。
Linux内核提供了三个系统调用:
- semget
- semctl
- semop
4.1 semget函数
#include <sys/sem.h>int semget(key_t key, int nsems, int semflg);
功能:创建一个新的信号量集或获取一个已经存在的信号量集。
参数说明:
- key:传入参数,信号量的键值,通常为一个整数;
- nsems:创建的信号量数目;
- semflg:标志位,同open和msgget函数的标志位功能相似,用来设置权限
– 权限位可与IPC_CREAT及IPC_EXCL发生位或;
– IPC_PRIVATE:表示该信号量为当前进程的私有信号量。
返回值说明:
- 成功:返回信号量的标识符;
- 不成功:返回-1并设置errno,常见errno值如下:
– EACCES:表示进程无访问权限;
– ENOENT:表示传入的键值不存在;
– EINVAL:表示nsems小于0或信号量已达上限;
– EEXIST:当semflg设置为IPC_CREAT和IPC_EXCL时,该信号量已存在。
4.2 semctl函数
#include <sys/sem.h>int semctl(int semid, int semnum, int cmd, ...);
功能:对信号量或信号量集进行控制。
参数说明:
- semid:信号量标识符,通常为semget函数的返回值;
- semnum:信号量在信号量集中的编号,该参数在使用信号量集时才会使用,通常设置为0,表示取第一个信号;
- cmd:对信号量进行操作,常用的设置为SETVAL和IPC_RMID:
– SETVAL:表示semctl函数的功能为初始化信号量的值,信号量的值通过可选参数传入,信号量在使用前应先对其值进行设置;
– IPC_RMID:表示semctl函数的功能为删除指定信号量。信号量的删除应由其所有者或创建者进行,没有被删除的信号量将会一直存在于系统中。 - 最后一个参数是可选参数,依赖于参数cmd,使用该参数时,用户必须在程序自定义一个如下所示的共用体:
union semun{int val; //cmd = SETVAL, 用于指定信号量值struct semid_ds *buf; //cmd = IPC_STAT或IPC_SET, 该项才生效unsigned short *array; //cmd = GETALL或SETALL, 该项才生效struct seminfo *_buf; //cmd = IPC_INFO, 该项才生效
};
//semid_ds是一个由内核维护的记录信号量属性信息的结构体
struct semid_ds{struct ipc_perm sem_perm; //所有者和标识权限time_t sem_otime; //最后操作时间time_t sem_ctime; //最后更改时间unsigned short sem_nsems; //信号集中的信号数量
};
返回值说明:
- 成功:根据参数cmd的取值返回相应信息,通常为一个非负整数;
- 不成功:返回-1并设置errno。
4.3 semop函数
#include <sys/sem.h>int semop(int semid, struct sembuf *sops, unsigned nsops);
功能:改变信号量的值。
参数说明:
- semid:信号量标识符,通常为semget函数的返回值;
- sops:一个struct sembuf类型的数组指针,该数组中的每个元素设置了要对信号量集的哪个信号做哪种操作;
struct sembuf{short sem_num; //信号量在信号量集中的编号short sem_op; //信号量操作short sem_flag; //标志位
};
– sem_op = -1:表示P操作;
– sem_op = +1:表示V操作;
– 通常设置sem_flag = SEM_UNDO:若进程退出前没有删除信号量,则信号量将会由系统自动释放。
- nsops:表示参数sops所指数组中元素的个数。
返回值说明:
- 成功:返回0;
- 不成功:返回-1并设置errno。
【案例1】使用信号量实现父子进程同步,防止父子进程抢夺CPU。
#include <stdio.h>
#include <stdlib.h>
#include <sys/sem.h>
//自定义共用体
union semu{int val;struct semid_ds* buf;unsigned short* array;struct seminfo* _buf;
};
static int semId;
//设置信号量值
static int setSemValue() {union semu tempSemUnion;tempSemUnion.val = 1;if (semctl(semId, 0, SETVAL, tempSemUnion) == -1){return 0;}//of ifreturn 1;
}//of setSemValue
//p操作,获取信号量
static int semaphoreP() {struct sembuf tempSemBuf;tempSemBuf.sem_num = 0;tempSemBuf.sem_op = -1;tempSemBuf.sem_flg = SEM_UNDO;if (semop(semId, &tempSemBuf, 1) == -1){perror("sem_p err");return 0;}//of ifreturn 1;
}//of semaphoreP
//V操作,释放信号量
static int semaphoreV() {struct sembuf tempSemBuf;tempSemBuf.sem_num = 0;tempSemBuf.sem_op = 1;tempSemBuf.sem_flg = SEM_UNDO;if (semop(semId, &tempSemBuf, 1) == -1){perror("sem_v err");return 0;}//of ifreturn 1;
}//of semaphoreV
//删除信号量
static void delSemValue() {union semu tempSemUnion;if (semctl(semId, 0, IPC_RMID, tempSemUnion) == -1){perror("del err");}//of if
}//of delSemValue
int main() {int i;pid_t temPid;char tempChar = 'C';semId= semget((key_t)1000, 1, 0664 | IPC_CREAT);//创建信号量if (semId== -1){perror("sem_c err");exit(-1);}//of ifif (!setSemValue()){ //设置信号量值perror("init err");exit(-1);}//of iftemPid = fork(); //创建子进程if (temPid== -1){ //若创建失败delSemValue(); //删除信号量exit(-1);} else if (temPid == 0){ //设置子进程打印的字符tempChar = 'Z';} else { //设置父进程打印的字符tempChar = 'C';}//of ifsrand((unsigned int)getpid()); //设置随机数种子for (i = 0; i < 8; i++) { //循环打印字符semaphoreP(); //获取信号量printf("%c", tempChar);fflush(stdout); //将字符打印到屏幕sleep(rand() % 4); //沉睡printf("%c", tempChar);fflush(stdout); //再次打印到屏幕sleep(1);semaphoreV(); //释放信号量}//of for iif (temPid > 0){wait(NULL); //回收子进程delSemValue(); //删除信号量}//of ifprintf("\nprocess %d finished.\n", getpid());return 0;
}//of main