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 <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/msg.h>
#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 <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/msg.h>
#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. 读者-作者问题

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

你可能感兴趣的:(unix,操作系统,C语言,PV原句,进程同步互斥)