Chapter 11 Inter-Process Communication (4) _ process semaphore

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
 */

地址:https://www.cnblogs.com/5iedu/p/6593802.html

发布了25 篇原创文章 · 获赞 4 · 访问量 2万+

Guess you like

Origin blog.csdn.net/zhou8400/article/details/97623953