五十一、进程间通信——System V IPC 之进程信号量

时间:2023-03-09 04:12:07
五十一、进程间通信——System V IPC 之进程信号量

51.1 进程信号量

51.1.1 信号量

  • 本质上就是共享资源的数目,用来控制对共享资源的访问
  • 用于进程间的互斥和同步
  • 每种共享资源对应一个信号量,为了便于大量共享资源的操作引入了信号量集,可对所有信号量一次性操作。对信号量集中所有操作可以要求全部成功,也可以部分成功
  • 二元信号量(信号灯)值为 0 和 1
  • 对信号量做 PV 操作2

51.1.2 信号量集属性

  五十一、进程间通信——System V IPC 之进程信号量

51.1.3 创建信号量集

  五十一、进程间通信——System V IPC 之进程信号量

  • 函数参数:
    • key:用户指定的信号量集键值
    • nsems:信号量集中信号量个数
    • semflg:IPC_CREAT,IPC_EXCL 等权限组合
  • 返回值:成功,返回信号量集 ID,出错,返回 -1

51.1.4 信号量集控制

  五十一、进程间通信——System V IPC 之进程信号量

  五十一、进程间通信——System V IPC 之进程信号量

  五十一、进程间通信——System V IPC 之进程信号量

  五十一、进程间通信——System V IPC 之进程信号量

  • 函数参数:
    • semid:信号量集 ID
    • semnum:0 表示对所有信号量操作,信号量编号从 0 开始
    • cmd:控制命令,通过 cmd 参数设定对信号量集要执行的操作
      • IPC_STAT:获取信号量集的属性    ---> buf
      • IPC_SET:设置信号量集的属性      ---> buf
      • IPC_RMID:删除信号量集               ---> buf
      • GETVAL:返回信号量的值               ---> val
      • SETVAL:设置 semnum 信号量的值 ---> val
      • GETALL:获取所有信号量的值           ---> arryr
      • SETALL:设置所有信号量的初始值  ---> array
    • arg:即 ... ,semun 联合体变量
      • val:放置获取或设置信号量集中某个信号量的值
      • buf:信号量集属性指针
      • array:放置获取或设置信号量集中所有信号量的值

51.1.5 信号量集操作

  五十一、进程间通信——System V IPC 之进程信号量

  五十一、进程间通信——System V IPC 之进程信号量

  • 函数参数:
    • semid:信号集 ID
    • sops:sembuf 结构体数组指针
      • sem_num:信号集中信号量的编号
      • sem_op:正数为 V 操作,负数为 P 操作,0 可用于对共享资源是否已用完的测试
      • sem_flg:SEM_UNDO 标识,表示在进程结束时,相应的操作将被取消。如果设置了该标志,那么在进程没有释放共享资源就退出时,内核将代为释放
    • nsops:第二个参数中结构体数组的长度
  • 返回值:成功返回 0;出错返回 -1
  • 其他说明:
    • 用于信号量集中信号量的加和减操作(PV 操作)
    • 可用于进程间的互斥或同步

51.2 信号量例子

51.2.1 PV 操作

(1)PV模块

  sem_pv.h

 #ifndef INCLUDE_SEM_PV_H_
#define INCLUDE_SEM_PV_H_ #include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <malloc.h> union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
}; /** 初始化 semnums 个信号灯/信号量值(value) */
extern int sem_I(int semnums, int value); /** 对信号量集(semid)中的信号灯(semnum)作 P() */
extern void sem_P(int semid, int semnum, int value); /** 对信号集(semid) 中的信号灯(semnum)作V(value)操作 */
extern void sem_V(int semid, int semnum, int value); /** 销毁信号量集(semid) */
extern void sem_D(int semid); #endif /* INCLUDE_SEM_PV_H_ */

  sem_pv.c

 #include "sem_pv.h"

 /** 初始化 semnums 个信号灯/信号量值(value) */
int sem_I(int semnums, int value)
{
/** 创建信号量集 */
int semid;
/** 创建信号量集 */
semid = semget(IPC_PRIVATE, semnums, IPC_CREAT | IPC_EXCL | );
if(semid < ){
return -;
} union semun un;
unsigned short *array = (unsigned short *)calloc(semnums, sizeof(unsigned short));
int i;
for(i = ; i < semnums; i++){
array[i] = value;
}
un.array = array; /**
* 初始化信号量集中所有信号灯的初值
* 0: 表示要初始化所有的信号灯
*/
if(semctl(semid, , SETALL, un) < ){
perror("semctl error");
return -;
}
free(array);
return semid;
} /** 对信号量集(semid)中的信号灯(semnum)作 P() */
void sem_P(int semid, int semnum, int value)
{
assert(value >= ); /** 定义 sembuf 类型的结构体数组,放置若干个结构体变量,对应要操作的信号量、P或V操作 */
struct sembuf ops[] = {{semnum, -value, SEM_UNDO}};
if(semop(semid, ops, sizeof(ops)/sizeof(struct sembuf)) < ){
perror("semop error");
}
} /** 对信号集(semid) 中的信号灯(semnum)作V(value)操作 */
void sem_V(int semid, int semnum, int value)
{
assert(value >= ); /** 定义 sembuf 类型的结构体数组,放置若干个结构体变量,对应要操作的信号量、P或V操作 */
struct sembuf ops[] = {{semnum, value, SEM_UNDO}};
if(semop(semid, ops, sizeof(ops)/sizeof(struct sembuf)) < ){
perror("semop error");
}
} /** 销毁信号量集(semid) */
void sem_D(int semid)
{
if(semctl(semid, , IPC_RMID, NULL) < ){
perror("semctl error");
}
}

  编译:

  gcc -o obj/sem_pv.o -Iinclude -c src/sem_pv.c

(2)互斥操作

  五十一、进程间通信——System V IPC 之进程信号量

  

  atm_account.h

 #ifndef INCLUDE_ATM_ACCOUNT_H_
#define INCLUDE_ATM_ACCOUNT_H_ #include <malloc.h>
#include <assert.h>
#include <string.h> typedef struct {
int code;
double balance;
int semid; ///< 在共享资源上绑定一个信号量集
}atm_account; /** 取款 */
extern double atm_account_withdraw(atm_account *a, double amt); /** 存款 */
extern double atm_account_deposit(atm_account *a, double amt); /** 查看账户余额度 */
extern double amt_account_balanceGet(atm_account *a); #endif /* INCLUDE_ATM_ACCOUNT_H_ */

  atm_account.c

 #include "sem_pv.h"
#include "atm_account.h" /** 取款 */
double atm_account_withdraw(atm_account *a, double amt)
{
assert(a != NULL); /** 对信号量集 semid 中的0号信号量/信号灯作 P(1) 操作 */
sem_P(a->semid, , );
if(amt < || amt > a->balance){
/** 对信号量集 semid 中的0号信号量/信号灯作 V(1) 操作 */
sem_V(a->semid, , );
return 0.0;
} double balance = a->balance;
sleep();
balance -= amt;
a->balance = balance;
/** 对信号量集 semid 中的0号信号量/信号灯作 V(1) 操作 */
sem_V(a->semid, , );
return amt;
} /** 存款 */
double atm_account_deposit(atm_account *a, double amt)
{
assert(a != NULL); /** 对信号量集 semid 中的0号信号量/信号灯作 P(1) 操作 */
sem_P(a->semid, , );
if(amt < ){
/** 对信号量集 semid 中的0号信号量/信号灯作 V(1) 操作 */
sem_V(a->semid, , );
return 0.0;
}
double balance = a->balance;
sleep();
balance += amt;
a->balance = balance;
/** 对信号量集 semid 中的0号信号量/信号灯作 V(1) 操作 */
sem_V(a->semid, , ); return amt;
} /** 查看账户余额度 */
double amt_account_balanceGet(atm_account *a)
{
assert(a != NULL);
/** 对信号量集 semid 中的0号信号量/信号灯作 P(1) 操作 */
sem_P(a->semid, , );
double balance = a->balance;
/** 对信号量集 semid 中的0号信号量/信号灯作 V(1) 操作 */
sem_V(a->semid, , );
return balance;
}

  测试代码:atm_account_test.c

 #include "atm_account.h"
#include "sem_pv.h"
#include <unistd.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h> int main(void)
{
/** 在共享内存中创建银行账户 */
int shmid;
if((shmid = shmget(IPC_PRIVATE, sizeof(atm_account), IPC_CREAT | IPC_EXCL | )) < ){
perror("shmget error");
return ;
} /** 进行共享内存映射(a 为映射的地址) */
atm_account *a = (atm_account *)shmat(shmid, , );
if(a == (atm_account *)-){
perror("shmat error");
return ;
}
a->code = ;
a->balance = ; /** 创建信号量集并初始化(1 个信号量/信号灯,初值为 1) */
a->semid = sem_I(, );
if(a->semid < ){
perror("sem_I(1, 1) error");
return ;
}
printf("balance: %f\n", a->balance); pid_t pid;
if((pid = fork()) < ){
perror("fork error");
return ;
}
else if(pid > ){
/** 父进程执行取款操作 */
double amt = atm_account_withdraw(a, );
printf("pid %d withdraw %f form code %d\n", getpid(), amt, a->code);
wait(); /** 对共享内存的操作要在解除映射之前 */
printf("balance: %f\n", a->balance); sem_D(a->semid); ///< 销毁信号量集
shmdt(a); ///< 解除共享内存的映射
shmctl(shmid, IPC_RMID, NULL);///< 释放共享内存
}
else {
/** 子进程进行取款操作 */
double amt = atm_account_withdraw(a, );
printf("pid %d withdraw %f form code %d\n", getpid(), amt, a->code); shmdt(a); ///< 解除共享内存的映射
} return ;
}

  编译运行如下:

  五十一、进程间通信——System V IPC 之进程信号量

51.2.2 PV操作--读者写者案例

  目的:利用进程信号量的 PV操作实现进程间的同步问题

  共享内存中读写数据(读者和写者问题)

  五十一、进程间通信——System V IPC 之进程信号量

  

 #include <sys/shm.h>
#include <sys/sem.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.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, , IPC_CREAT | IPC_EXCL | )) < ){
perror("semget error");
exit();
} /** 对信号量集中的所有信号量初始化 */
union semun{
int val;
struct semid_ds *ds;
unsigned short *array;
};
union semun un;
/** 2 个信号量的初值设置为 0 */
unsigned short array[] = {, };
un.array = array;
if(semctl(s->semid, , SETALL, un) < ){
perror("semctl error");
exit();
}
} void destroy(Storage *s)
{
assert(s != NULL);
if(semctl(s->semid, , IPC_RMID, NULL) < ){
perror("semctl error");
exit();
}
} void writer(Storage *s, int val)
{
/** 写入数据到 Storage */
s->val = val;
printf("%d write %d\n", getpid(), val); /** 设置信号量 0 号作 V(1) 操作 */
struct sembuf ops_v[] = {{, , SEM_UNDO}};
/** 设置信号量 1 号作 P(1) 操作 */
struct sembuf ops_p[] = {{, -, SEM_UNDO}}; /** V(s1) */
if(semop(s->semid, ops_v, ) < ){
perror("semop error");
} /** P(s2) */
if(semop(s->semid, ops_p, ) < ){
perror("semop error");
}
} void reader(Storage *s)
{
assert(s != NULL); /** 设置信号量 0 号作 P(1) 操作 */
struct sembuf ops_p[] = {{, -, SEM_UNDO}};
/** 设置信号量 1 号作 V(1) 操作 */
struct sembuf ops_v[] = {{, , SEM_UNDO}};
/** P(s1) */
if(semop(s->semid, ops_p, ) < ){
perror("semop error");
}
/** 从 Storage 中读取数据 */
printf("%d read %d\n", getpid(), s->val);
/** V(s2) */
if(semop(s->semid, ops_v, ) < ){
perror("semop error");
}
} int main(void)
{
/** 将共享资源 Storage 创建在共享内存中 */
int shmid;
if((shmid = shmget(IPC_PRIVATE, sizeof(Storage), IPC_CREAT | IPC_EXCL | )) < ){
perror("shmget error");
exit();
} /** 父进程进行共享内存映射 */
Storage *s = (Storage *)shmat(shmid, , );
if(s == (Storage *)-){
perror("shmat error");
exit();
} /** 创建信号量并初始化 */
init(s); pid_t pid;
pid = fork();
if(pid < ){
perror("fork error");
exit();
}
else if(pid > ){
int i = ;
for(;i <= ; i++){
writer(s, i);
}
wait();
destroy(s);
shmdt(s);
shmctl(shmid, IPC_RMID, NULL);
}
else{
int i = ;
for(;i <= ; i++){
reader(s);
}
shmdt(s);
}
}