第11章 进程间通信(4)_进程信号量

3.4 进程信号量

(1)进程信号量简介

  ①本质上就是共享资源的数目,用来控制对共享资源的访问。

  ②用于进程间的互斥和同步

  ③每种共享资源对应一个信号量,为了便于大量共享资源的操作引入信号量集,可对所有信号量一次性操作。对信号量集中所有操作可以要求全部成功,也可以部分成功。

  ④二元(也叫双态)信号量(信号灯)值为0或1。

  ⑤对信号量做PV操作(P减,V加)

(2)信号量集属性

 

(3)创建信号量集

头文件

#include <sys/sem.h>

函数

int semget(key_t key, int nsems, int flag);

参数

key: 用户指定的信号量集键值

size:信号量集中信号量的个数

flag:IPC_CREAT、IPC_EXCL等权限组合

功能

创建信号量集

返回值

成功返回内核中信号量集的标识ID,出错返回-1

(4)信号量集的控制

头文件

#include <sys/sem.h>

函数

int semctl(int semid, int semnum, int cmd, …/*union semun arg*/);

参数

(1)semid: 信号量集ID

(2)semnum:0表示对所有信号量操作,信号量编号从0开始。

(3)union semun{

    int val; //放置获取或设置信号量集中某个信号量的值。

    struct semid_ds* buf;//信号量集属性指针

    unsigned short* array; //放置获取或设置信号量集中所有信号量的值。

      };

(4)cmd参数:通过参数设定对信号量集要执行的操作

  ①IPC_STAT:获取信号量集的属性       ==> buf

  ②IPC_SET: 设置信号量集的属性        ==> buf

  ③IPC_RMID:删除信号量集                ==> buf

  ④GETVAL:  返回信号量的值               ==> val

  ⑤SETVAL:  设置semnum信号量的值 ==> val

  ⑥GETALL:  获取所有信号量的值         ==> array

  ⑦SETALL:  设置所有信号量的初始值   ==> array

功能

控制信号量集

返回值

除GETALL以外所有GET命令,semctl函数都返回相应的值。其他命令的返回值为0.成功返回内核中信号量集的标识ID,出错返回-1

(5)信号量集的操作

头文件

#include <sys/sem.h>

函数

int semop(int semid, struct sembuf* sops, size_t nsops);

参数

(1)semid: 信号量集ID

(2)sops:sembuf结构体数组指针

(3)nsops:第2个参数中结构体数组的长度

(4)struct sembuf{

  unsigned short sem_num; //信号量集中的信号量的编号

  short sem_op; //正数为V操作负数为P操作,0可用于测试资源是否用完。

  short sem_flg;//SEM_UNDO标志,表示在进程结束时,相应的操作将被取消。如果设置了该标志那么在进程没有释放共享资源就退出时,内核将代为释放

};

功能

信号量集的操作

返回值

成功返回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
", 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
", 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
", 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
", 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
", 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
 */
原文地址:https://www.cnblogs.com/5iedu/p/6593802.html