Unix编程(二)C语言利用PV原句解决5个经典的进程同步问题

问题定义


利用信号量PV操作来实现进程间的同步互斥


知识概要


  • 信号量机制:利用PV操作对信号量进行处理
  • 信号量(semaphore)的数据结构为一个值和一个指针,指针指向该信号量的下一个进程。
  • 信号量的值与相关资源的使用情况有关:
    • 当它的值大于0时,表示当前可用资源的数量。
    • 当它的值小于0时,其绝对值表示等待使用该资源的进程的个数。
    • 信号量的值能且仅能 通过PV操作进行修改。
  • P操作:执行一次P操作意味着请求分配一个单位的资源,因此S的值减1,当S<0时,表示已经没有可用资源,请求者必须等待别的进程释放该类资源,它才能继续运行。
  • V操作:执行一次V操作意味着释放一个资源,因此S的值加1,当S<0,表示有某些进程正在等待资源,因此要唤醒一个等待状态的进程,使之继续运行。
  • 原语:不可中断的进程

PV原语操作的代码实现(以消费者-生产者问题为基本)。


  • ipc.h头文件(函数的生命和结构体的定义)
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define KEY_MUTEX 100
#define KEY_FULL 102
#define KEY_EMPTY 101
#define KEY_SHM 200
#define BUFFER_SIZE 10

/*
Semaphore
它负责协调各个线程,以保证它们能够正确、合理的使用公共资源
*/
typedef int semaphore;

/*
1.union中可以定义多个成员,union的大小最大的成员的大小决定
2.union成员共享同一块大小的内存,一次只能使用其中的一个成员
3.对某一个成员赋值,会覆盖其他成员的值(因为它们共享同一块内存)
但前提是成员所占的字节不同时只会覆盖相应字节上的值
比如对char成员赋值就不会把整个int成员覆盖掉
*/

union semun 
{
    int val;
    struct semid_ds *buf;
    /*
       struct semid_ds 
       内核为每个信号量集维护一个信号量结构体
       {
            struct ipc_sem *sem_perm;//权限
            ushort sem_nsems; //在信号集中的序号
            time_t sem_otime; //上次被semop的时间
            time_t sem_ctime; //上次被改变的时间
       }

       struct ipc_perm 
       {
            key_t key;
       }  
       struct sem
       {
           ushort semval;
           short sempid;
           ushort semncnt;
           ushort semzcnt; 
       }
    */
    unsigned short int *array;
};

//共享内存的结构体
struct shared_use_st 
{
    int buffer[BUFFER_SIZE];//存放标志位的数组
    int low;//当前消费者可以取东西的位置
    int high;//当前生产商可以生产商品的位置
    int cur;//记录当前生产的是第几个商品
};

extern int sem_p ( semaphore sem_id );//p函数
extern int sem_v ( semaphore sem_id );//v函数

-ipc.c (函数的具体定义)

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "ipc.h"



static int sem_set ( semaphore sem_id , int val )//修改
{
    union semun sem_union;
    sem_union.val = val;
/*
   int semctl ( int semid , int semnum , int cmd,... );
   semid: 信号集的标识符,即是信号表的索引
   semnum: 信号集的索引,用来存取信号集内的某个信号
   cmd:需要执行的命令   
   SETVAL根据semnum返回信号的值

*/
    if ( semctl(sem_id , 0 , SETVAL , sem_union ) == -1 )
            return 0;
    return 1;
}

static void sem_del ( semaphore sem_id )
{
    union semun sem_union;
    //IPC_RMID 立即删除信号集,唤醒所有被阻塞的线程
    if ( semctl ( sem_id , 0 , IPC_RMID , sem_union ) == -1 )
        fprintf ( stderr , "Failed to delete semaphore\n" );
}

//p函数,
int sem_p ( semaphore sem_id )
{
    struct sembuf sem_b;
    /*
       struct sembuf 
       {
            short sem_num;//信号量的数目
            short sem_op;//信号量的变化量值
            short sem_flg;
            //设置为SEM_UNDO,这会使得操作系统跟踪当前进程对信号量
            //所做的改变,而且如果进程终止而没有释放这个信号量,
            //如果新海鸥梁为这个进程所占有,这个标记可以使得操作
            //系统自动释放这个信号量。
       }
    */
    sem_b.sem_num = 0;
    sem_b.sem_op = -1; //p函数所以是-1
    sem_b.sem_flg = SEM_UNDO;
    /*
    int semop ( int sem_id , struct sembuf* sem_ops , size_t num_sem_ops )
    对信号量集标识符为semid中的一个或多个信号量进行P操作或V操作
    sem_id是由semget所返回的信号量标识符。
    sem_ops,是一个指向结构体数组的指针,其中每一个结构至少包含下列成员
    num_sem_ops就是上面的数组的大小
    */
    if ( semop(sem_id, &sem_b , 1 ) == -1 )
    {
        fprintf ( stderr , "semaphore_p failed\n");
        return 0;
    }
    return 1;
}

//V函数
int sem_v ( semaphore sem_id )
{
    struct sembuf sem_b;
    sem_b.sem_num = 0;
    sem_b.sem_op = 1;//V函数所以是1
    sem_b.sem_flg = SEM_UNDO;
    if ( semop( sem_id , &sem_b , 1 ) == -1 )
    {
        fprintf ( stderr , "semaphore_v , failed\n");
        return 0;
    }
    return 1;
}

static int shm_create ( int key )
{
    /*
    共享内存函数由shmget,shmat,shmat,shmdt,shmct四个函数组成
    int shmget ( key_t key , size_t size , int shmflg )
key:

0 : 会建立新共享内存对象
    1 : 大于0的三十二位整数,通常要求此值来源与flok返回的IPC键值
size:
    0 : 只获取共享内存时指定为0
    1 : 新建共享内存的大小
shmflg:
    0: 取共享内存标识符
    IPC_CREAT: 当shmflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存,返回此共享内存的标识符
    IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存就报错
    成功返回共享内存的标识符
    错误则返回-1
    */
    shmget ( (key_t)key , sizeof ( struct shared_use_st) ,0666|IPC_CREAT );//建立共享的内存对象
}

5个经典的进程同步问题


  1. 制造商消费者问题
  2. 哲学家就餐问题
  3. 熟睡的理发师问题
  4. 三个烟鬼的问题
  5. 读者-作者问题

本篇文章已经更新完毕,希望大家把我误解的地方指正~~~,如果有更好的理解可以在评论区大家交流一下,敬谢

你可能感兴趣的:(Linux深入学习)