3.4 Process semaphores
(1) Introduction to the process of the semaphore
① is the number of shared resources in nature, used to control access to shared resources.
② for inter-process mutual exclusion and synchronization .
③ Each shared resource corresponding to the semaphore , for ease of handling large numbers of shared resources introduced semaphore set , one-time operation for all semaphores. Semaphore may be required to concentrate all operations are successful, it may be partially successful.
④ for Binary (also called binary) semaphore (signal) is 0 or 1.
⑤ semaphore operations do PV (P Save, V add)
(2) Set Properties semaphore
(3) create a semaphore set
head File |
#include <sys/sem.h> |
function |
int semget(key_t key, int nsems, int flag); |
parameter |
Key : the amount of user-specified set of key signals size number concentrated semaphore semaphore: Flag : IPC_CREAT, and other combinations of privileges IPC_EXCL |
Features |
Create a semaphore set |
return value |
Successful identification ID returned semaphore kernel set, an error -1 |
Control (4) sets the semaphore
head File |
#include <sys/sem.h> |
function |
int semctl(int semid, int semnum, int cmd, …/*union semun arg*/); |
parameter |
(1) semid: semaphore set ID (2) semnum: 0 indicates the amount of operation for all signals, the semaphore number starts from 0. (3) union semun { int val; // get or set the semaphore is placed concentration value of a semaphore. struct semid_ds * buf; // set attribute pointer semaphore unsigned short * array; // get or set the semaphore is placed values for all the semaphore. }; (4) cmd parameters: setting a semaphore operation to be performed by a set of parameters ①IPC_STAT: Get semaphore set of attributes ==> buf ②IPC_SET: Set a semaphore set attributes ==> buf ③IPC_RMID: Delete semaphore set ==> buf ④GETVAL: Returns the value of the semaphore ==> val ⑤SETVAL: set the value of the semaphore semnum ==> val ⑥GETALL: Get all the semaphore value ==> array ⑦SETALL: setting an initial value of all signals ==> array |
Features |
Control semaphore set |
return value |
In addition to all GETALL GET command, semctl functions return the corresponding value. Command returns a value of 0. Other successful identification ID returned semaphore kernel set, an error -1 |
Operation (5) set the semaphore
head File |
#include <sys/sem.h> |
function |
int semop(int semid, struct sembuf* sops, size_t nsops); |
parameter |
(1) semid: semaphore set ID (2) sops: sembuf structure array pointer (3) nsops: 2 length parameters of an array of structures (4)struct sembuf{ unsigned short sem_num; // the amount of signal set number semaphore short sem_op; // positive number V operation , the negative of operation P , 0 can be used to test whether the resource exhausted. short sem_flg; // SEM_UNDO flag, represents the end of the process, appropriate action will be canceled . If this flag is set , then the process does not release a shared resource on the exit, the kernel will release his behalf . }; |
Features |
Operation semaphore set |
return value |
成功返回0,出错返回-1 |
备注 |
(1)用于信号量集中信号量的加和减操作(PV操作,注意P为减操作,V为加操作) (2)可用于进程间的互斥和同步。 |
【编程实验】进程信号量实现ATM的互斥
(1)利用进程信号量的PV操作实现多进程对银行账户操作的互斥。
(2)I(1):在银行账户上绑定信号量/信号灯(初始值为1)
(3)任何进程在取款时进行P(1)操作,然后withdraw(),取完后V(1)操作。
//pv.h
#ifndef __PV_H__ #define __PV_H__ //初始化semnums个信号灯/信号量的值(value) extern int I(int semnums, int value); //对信号集(semid)中的信号灯(semindex)作P(value)操作 extern void P(int semid, int semindex,int value); //对信号量集(semid)中的信号灯(semindex)作V(value)操作 extern void V(int semid, int semindex, int value); //销毁信号量集(semid) extern void D(int semid); #endif
//pv.c
#include "pv.h" #include <sys/sem.h> #include <stdio.h> #include <stdlib.h> #include <malloc.h> #include <assert.h> //编译命令:gcc -o bin/pv.o -Iinclude -c src/pv.c /*封装对信号集的PV操作*/ //定义信号量集的Union union semun{ int val; struct semid_ds *buf; unsigned short *array; }; //创建信号量集,并初始化semnums个信号灯/信号量的值为value int I(int semnums, int value) { //创建信号量集 int semid = semget(IPC_PRIVATE, semnums, IPC_CREAT | IPC_EXCL | 0777); if(semid < 0){ return -1; //错误时 } union semun un; unsigned short* array = (unsigned short*)calloc(semnums, sizeof(unsigned short)); int i = 0; for(; i< semnums; i++){ array[i] = value; } un.array = array; //初始化信号量集中的所有的信号灯的初值 //0:表示要初始化所有的信号灯 if(semctl(semid, 0, SETALL, un) < 0){ perror("semctl error"); return -1; } free(array); return semid; //返回信号量集ID } //对信号集(semid)中的信号灯(semindex)作P(value)操作 void P(int semid, int semindex, int value) { assert(value >= 0); /* * 定义sembuf类型的结构体数组,放置若个结构体变量 * 对应要操作的信号量、要作的P或V操作 */ struct sembuf ops[] = {{semindex, -value, SEM_UNDO}};//只有1个元素,表示只操作 //一个信号量.-value表示P操作 if(semop(semid, ops, sizeof(ops)/sizeof(ops[0])) < 0){ perror("P semop error"); } } //对信号量集(semid)中的信号灯(semindex)作V(value)操作 void V(int semid, int semindex, int value) { assert( value >= 0); /* * 定义sembuf类型的结构体数组,放置若个结构体变量 * 对应要操作的信号量、要作的P或V操作 */ struct sembuf ops[] = {{semindex, value, SEM_UNDO}};//只有1个元素,表示只操作 //一个信号量。+value表示V操作 if(semop(semid, ops, sizeof(ops)/sizeof(ops[0])) < 0){ perror("semop error"); } } //销毁信号量集(semid) void D(int semid) { if(semctl(semid, 0, IPC_RMID, NULL) < 0){ perror("semctl error"); } }
//account.h
#ifndef __ACCOUNT_H__ #define __ACCOUNT_H__ typedef struct { int code; //帐号 double balance; //余额 int semid; //在共享资源上绑定一个信号量集 }Account; //取款 extern double withdraw(Account* a, double amt); //amt == amount //存款 extern double deposit(Account* a, double amt); //查看帐户余额 extern double get_balance(Account* a); #endif //__ACCOUNT_H__
//account.c
#include "account.h" #include "pv.h" #include <string.h> #include <assert.h> //取款 double withdraw(Account* a, double amt) //amt == amount { assert(a != NULL); //对信号量集semid中的0号信号灯作P操作 P(a->semid, 0, 1); //P操作 if(amt < 0 || amt > a->balance){ V(a->semid, 0, 1); return 0.0; } double balance = a->balance; //先取余额 sleep(1); //为模拟进程下可能出现的问题 balance -= amt; a->balance = balance; //更新余额。在读取余额和更新余额之间有 //故意留出“时间窗口”。 //对信号量集semid中的0号信号灯作V操作 V(a->semid, 0, 1); //V操作 return amt; } //存款 double deposit(Account* a, double amt) { assert(a != NULL); if(amt < 0){ return 0.0; } P(a->semid, 0, 1); //P操作 double balance = a->balance; //先取余额 sleep(1); //为模拟多进程下可能出现的问题 balance += amt; a->balance = balance; //更新余额。 V(a->semid, 0, 1); //V操作 return amt; } //查看帐户余额 double get_balance(Account* a) { assert(a != NULL); P(a->semid, 0, 1); //P操作 double balance = a->balance; V(a->semid, 0, 1); //V操作 return balance; }
//account_test.c
#include "account.h" #include "pv.h" #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <sys/shm.h> int main(void) { //在共享内存中创建银行帐户 int shmid; if((shmid = shmget(IPC_PRIVATE, sizeof(Account), IPC_CREAT | IPC_EXCL | 0777)) < 0){ perror("shmget error"); exit(1); } //进程共享内存映射(a为返回的映射地址) Account* a= (Account*)shmat(shmid, 0, 0); if(a == (Account*)-1){ perror("shmat error"); exit(1); } //银行帐户初始化 a->code = 100001; a->balance = 10000; printf("balance: %f\n", a->balance); //创建信号量集并初始化 a->semid = I(1, 1);//1个信号量,初始值为1 if(a->semid < 0){ perror("I(1, 1) init error"); exit(1); } //父子进程都进行取款 pid_t pid; if((pid = fork()) < 0){ perror("fork error"); exit(1); }else if(pid > 0){ //parent process //父进程进行取款操作 double amt = withdraw(a, 10000); printf("pid %d withdraw %f from code %d\n", getpid(), amt, a->code); int semid = a->semid; //解除映射 shmdt(a); wait(0); //删除共享内存区 shmctl(shmid, IPC_RMID, NULL); //销毁信号量 D(semid); }else{ //child process //子进程会继承父进程映射的共享内存地址 //子进程进行取款操作 double amt = withdraw(a, 10000); printf("pid %d withdraw %f from code %d\n", getpid(), amt, a->code); //解除映射 shmdt(a); } return 0; } /*输出结果: [root@localhost 11.IPC]# gcc -o bin/account_test -Iinclude src/pv.c src/account.c src/account_test.c [root@localhost 11.IPC]# bin/account_test balance: 10000.000000 pid 2229 withdraw 10000.000000 from code 100001 pid 2230 withdraw 0.000000 from code 100001 */
【编程实验】进程信号量实现的读者/写者的同步
(1)目的:利用进程信号量的PV操作实现进程间的同步问题
(2)在共享内存中读写数据(读者和写者问题)
(3)设置两个信号量并初始化为0:控制读的信号量I(s1, 0),控制写的信号量I(s2,1)
//read_writer.c
#include <sys/shm.h> #include <sys/sem.h> #include <stdio.h> #include <stdlib.h> #include <assert.h> //共享资源 typedef struct{ int val; int semid; //一个资源绑定一定信号量集 }Storage; //初始化信号量集 void init(Storage* s) { assert(s != NULL); //创建信号量集(包含2个信号量) if((s->semid = semget(IPC_PRIVATE, 2, IPC_CREAT | IPC_EXCL | 0777)) < 0){ perror("semget error"); exit(1); } //对信号 量中的所有信号量初始化 union semun{ int val; struct semid_ds *ds; unsigned short *array; }; union semun un; //信号量的初始设置 unsigned short array[2] = {0, 1}; //分别为控制读、写 un.array = array; if(semctl(s->semid, 0, SETALL, un) < 0){ perror("semctl error"); exit(1); } } //销毁信号量集 void destroy(Storage* s) { assert( s != NULL); if(semctl(s->semid, 0, IPC_RMID, NULL) < 0){ perror("semctl error"); exit(1); } } void write(Storage* s, int value) { struct sembuf ops_p = {1, -1, SEM_UNDO}; struct sembuf ops_v = {0, 1, SEM_UNDO}; //等待可写:P(s1)操作 if(semop(s->semid, &ops_p, 1) < 0){ perror("semop error"); } s->val = value; //写入数据 printf("%d write %d\n", getpid(), value); //通知己可读:V(s0) if(semop(s->semid, &ops_v, 1) < 0){ perror("semop error"); } } void read(Storage* s) { struct sembuf ops_p = {0, -1, SEM_UNDO}; struct sembuf ops_v = {1, 1, SEM_UNDO}; //等待可读:P(s0)操作 if(semop(s->semid, &ops_p, 1) < 0){ perror("semop error"); } int value = s->val; //读取数据 printf("%d read %d\n", getpid(), value); //通知己可写:V(s1) if(semop(s->semid, &ops_v, 1) < 0){ perror("semop error"); } } int main(void) { //将共享资源Storage创建在共享内存中 int shmid; if((shmid = shmget(IPC_PRIVATE, sizeof(Storage), IPC_CREAT | IPC_EXCL | 0777)) < 0){ perror("shmget error"); exit(1); } //共享内存映射 Storage* s = (Storage*)shmat(shmid, 0, 0); if(s == (Storage*)-1){ perror("shmat error"); exit(1); } //创建信号量集并初始化 init(s); pid_t pid = fork(); if(pid < 0){ perror("fork error"); exit(1); }else if (pid > 0){ //parent process int i = 1; for(; i<=10; i++){ write(s, i); } wait(0); destroy(s);//释放信号量集 shmdt(s); //解除映射 shmctl(shmid, IPC_RMID, NULL); //删除共享内存 }else{ //child process int i = 1; for(; i<=10; i++){ read(s); } shmdt(s);//解除共享内存映射 } return 0; } /*输出结果 [root@localhost 11.IPC]# bin/read_writer 2376 write 1 2377 read 1 2376 write 2 2377 read 2 2376 write 3 2377 read 3 2376 write 4 2377 read 4 2376 write 5 2377 read 5 2376 write 6 2377 read 6 2376 write 7 2377 read 7 2376 write 8 2377 read 8 2376 write 9 2377 read 9 2376 write 10 2377 read 10 */