【操作系统原理】—— 信号量与PV操作实现

简介: 【操作系统原理】—— 信号量与PV操作实现

实验相关知识

1、信号量与PV操作

信号量:

     信号量是一种用于进程或线程同步的经典同步机制,通常用于解决生产者-消费者问题、读者-写者问题等。

     信号量是一个计数器,表示可用资源的数量。信号量的值可以是任意非负整数。在多线程或多进程环境中,信号量用于控制对共享资源的访问。

PV 操作:

     P(Produce)操作: 也称为 Wait 操作。执行 P 操作时,信号量的值减一,表示占用了一个资源。如果信号量的值为负数,表示资源不足,执行 P 操作的线程或进程将被阻塞,直到有足够的资源。

     V(Vaporize)操作: 也称为 Signal 操作。执行 V 操作时,信号量的值加一,表示释放了一个资源。如果有其他线程或进程正在等待资源,那么其中一个将被唤醒。

使用 C 语言的伪代码表示 PV 操作:

// P 操作
P(semaphore) {
    while (semaphore <= 0) {
        // 等待资源
    }
    semaphore--;
}
// V 操作
V(semaphore) {
    semaphore++;
}

在实际应用中,信号量通常有两种类型:

     二进制信号量: 取值为0或1,通常用于互斥操作,如实现临界区的互斥访问。

     计数信号量: 可以取任意非负整数,用于表示可用资源的数量,比如用于控制资源池中资源的分配。

2、生产者–消费者问题

     生产者-消费者问题是一个经典的并发编程问题,涉及到多个线程共享有限缓冲区的情况。其中,生产者线程负责向缓冲区中生产数据,而消费者线程负责从缓冲区中消费数据。需要确保在并发执行的情况下,生产者和消费者之间的操作是正确有序的,避免数据竞争和死锁等问题。

3、信号量类型的声明:Sem__t sem类型

4、信号量函数原型:Sem_init(sem_t *sem,int pshared,unsigned int value):

     ①sem参数,为信号量地址

     ②pshared参数,值为0时,用于线程间同步,值为1时,用于进程间同步

     ③value参数,指定同时访问的线程数

5、信号量操作函数:

int sem_wait(sem_t *sem):调用一次,信号量sem进程减一。当为0时再次进行减一,进行阻塞。
int sem_post(sem__t *sem):调用一次,信号量sem进程加一。当为N时,再次进行加一,进行阻塞。

实验设备与软件环境

安装环境:分为软件环境和硬件环境

硬件环境:内存ddr3 4G及以上的x86架构主机一部

系统环境:windows 、linux或者mac os x

软件环境:运行vmware或者virtualbox

软件环境:Ubuntu操作系统

实验内容

1. 在main函数下创建两个线程,一个线程为生产者线程,一个线程为消费者线程。

#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <semaphore.h>
sem_t empty_sem,full_sem;
void init_sem(){
    sem_init(&empty_sem,0,1);
    sem_init(&full_sem,0,0);
}
void P(sem_t* sem){
    if(sem_wait(sem))
        perror("P operating error");
}
void V(sem_t* sem){
    if(sem_post(sem))
        perror("V operating error");
}
static char share_buf[50];
void * produce(void *arg){
    char buf[50]={0};
    while(1){
        printf("Input message>>\n");
        fgets(buf,sizeof(buf),stdin);
        printf("produce item is>>%s",buf);
        P(&empty_sem);
        memcpy(share_buf,buf,sizeof(buf));
        V(&full_sem);
    }
    return NULL;
}
void * consumer(void *arg){
    char buf[50]={0};
    while(1){
        P(&full_sem);
        memcpy(buf,share_buf,sizeof(share_buf));
        V(&empty_sem);
        printf("Consumer item is<<%s",buf);
    }
    return NULL;
}
int main(){
    pthread_t produce_tid;
    pthread_t consumer_tid;
    init_sem();
    pthread_create(&produce_tid,NULL,produce,NULL);
    pthread_create(&consumer_tid,NULL,consumer,NULL);
    pthread_join(produce_tid,NULL);
    pthread_join(consumer_tid,NULL);
}

2. 通过编程实现单缓冲区的生产者和消费者问题,通过观察实验结果分析信号量机制在同步与互斥中的实现原理。

     互斥原理:互斥原理是先设置一个互斥信号量mutex(可自己定义),再初始化信号量,使mutex=1,然后进行P操作(申请资源使用),这时候mutex-1,即mutex=0,对这个资源进行“加锁”,别人无法再使用这个资源,也就是相当于(C语言中for循环的条件判断,满足条件就进行,不满足就跳出),等程序使用资源完毕时,就会进行V操作(释放资源),对资源进行“解锁”,这时候mutex+1,重新使mutex=1,使资源解放出来,等待下一个程序的使用。

3. 定义两个进程,一个进程为生产者,一个进程为消费者,能不能用进程来模拟单缓冲区的生产者和消费者问题?为什么?

     可以

     1.生产者进程:产生一个数据,当要送入缓冲区的时候,要检查缓冲区是否已满,若未满,则可将数据送入缓冲区,并通知消费者进程,否则等待。

     2.对于消费者进程:当它去取数据的时候,要看缓冲区中是否有数据可以去取,若有则取走一个数据,并通知生产者进程,否则等待。

     3.缓冲区是一个临界资源,因此,诸进程对缓冲区的操作程序是一个共享临界区,所以这里还有一个互斥问题。

4. 实际案例

题目:

     有一只铁笼子,每次只能放入一只动物,猎手向笼子放入老虎,农民向笼子放入猪,动物园等待取笼中的老虎,饭店等待取笼中的猪,试用PV操作写出能同步执行的程序。

思路:

(1)分析:

     只有一只铁笼子,而且每次只能放一种动物(这一点就避免了老虎和猪在一起的情况)。所以设置铁笼子的空间资源为cage,它同时也是笼子的互斥使用,初值为1;

     猎手放老虎前先看看有无空间(即笼子里面是否为空),若有则占用笼子,放入老虎。接着向动物园发信号(sem_post(&tiger))(V操作)

     动物园先看有无老虎,若有则抢笼子,取走老虎后将笼子释放(sem_post(&cage))(V操作)

     农民放猪前先看看有无空间,若有则占用笼子,放入猪。接着向饭店发信号(sem_post(&pig))(V操作)

     饭店先看有无猪,若有则抢笼子,取走猪后将笼子释放(sem_post(&cage))(V操作)

(2)信号量:

     空间资源cage,初值为1;

     TIGHT表示笼子中老虎个数,PIG表示笼子中猪个数,初值均为0

代码:

#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<semaphore.h>
sem_t cage,pig,tiger;
pthread_mutex_t mutex;
int PIG=0,TIGHT=0; 
void * hunter(void * arg) {
  while (1) {
    sleep(2);
    sem_wait(&cage);
    sem_post(&tiger);
    pthread_mutex_lock(&mutex);
    TIGHT++;
    printf(" 猎人 %ld 放入 1 只老虎到笼子里面,有 %d 只老虎在笼子里面\n", pthread_self(),TIGHT);
    pthread_mutex_unlock(&mutex);
  }
  return NULL;
}
void * zoo(void * arg) {
  while (1) {
    sleep(2);
    sem_wait(&tiger);
    pthread_mutex_lock(&mutex);
    TIGHT--;
    printf("动物园%ld 取出 1 只老虎到笼子里面,有 %d 只老虎在笼子里面\n", pthread_self(),TIGHT);
    sem_post(&cage);
    pthread_mutex_unlock(&mutex);
  }
  return NULL;
}
void * farmer(void * arg) {
  while (1) {
    sleep(2);
    sem_wait(&cage);
    sem_post(&pig);
    pthread_mutex_lock(&mutex);
    PIG++; 
    printf("农民%ld 放入 1 只猪到笼子里面,有 %d 只猪在笼子里面\n", pthread_self(),PIG);
    pthread_mutex_unlock(&mutex);
  }
  return NULL;
}
void * cook(void * arg) {
  while (1) {
    sleep(2);
    sem_wait(&pig);
    pthread_mutex_lock(&mutex);
    PIG--;
    printf("厨师%ld 取出 1 只猪到笼子里面,有 %d 只猪在笼子里面\n", pthread_self(),PIG);
    sem_post(&cage);
    pthread_mutex_unlock(&mutex);
  }
  return NULL;
}
int main() {
  //初始化互斥量
  sem_init(&cage,0,1);
  sem_init(&tiger,0,0);
  sem_init(&pig,0,0);
  pthread_mutex_init(&mutex, NULL);
  //创建四个线程
  pthread_t tid1, tid2, tid3,tid4;
  pthread_create(&tid1, NULL,hunter, NULL);
  pthread_create(&tid2, NULL,zoo, NULL);
  pthread_create(&tid3, NULL,farmer, NULL);
  pthread_create(&tid4, NULL,cook, NULL);
  //等待线程执行结束
  pthread_join(tid1, NULL);
  pthread_join(tid2, NULL);
  pthread_join(tid3, NULL);
  pthread_join(tid4, NULL);
  pthread_exit(NULL);
  //注销互斥锁
  pthread_mutex_destroy(&mutex);
  return 0;
}


相关文章
|
12天前
|
存储 算法 安全
深入理解操作系统内存管理:原理与实践
【5月更文挑战第25天】 随着计算机技术的不断发展,操作系统在硬件资源管理中扮演着越来越重要的角色。其中,内存管理作为操作系统的核心功能之一,对于提高系统性能和稳定性具有重要意义。本文将深入探讨操作系统内存管理的基本原理,包括内存分配、内存回收、内存保护等方面,并通过实例分析如何在实际开发中优化内存管理策略,以提高系统运行效率。
|
7天前
|
程序员 内存技术
深入理解操作系统内存管理:原理与实践
【5月更文挑战第30天】 在现代计算机系统中,操作系统的内存管理是确保系统高效、稳定运行的关键。本文将深入探讨操作系统中内存管理的理论基础和实际应用,包括物理内存与虚拟内存的映射机制、分页与分段技术、以及内存分配策略等。通过对内存管理机制的分析与案例实践,旨在为读者提供一个清晰的内存管理概念框架和操作指南,帮助理解操作系统如何优化内存资源使用,提高系统性能。
|
7天前
|
存储 Linux 开发者
深入理解操作系统内存管理:原理与实践
【5月更文挑战第30天】 本文旨在探讨操作系统中的内存管理机制,其核心是内存的有效分配、使用与回收。我们将从内存管理的基本原理出发,逐步深入到具体的实现技术,如分页、分段和虚拟内存等。文章将详细阐述每种技术的工作原理、优势及其可能面临的问题,并通过实际案例来展示这些技术在现代操作系统中的应用。通过阅读本文,读者将对操作系统的内存管理有一个全面而深入的了解,并能够对常见的内存问题进行诊断和优化。
|
7天前
|
缓存 算法 Java
深入理解操作系统内存管理:原理与实践
【5月更文挑战第30天】 操作系统的心脏——内存管理,是确保系统高效稳定运行的关键。本文将深入剖析操作系统内存管理的基本原理,包括物理内存与虚拟内存的映射机制、分页与分段技术、以及内存分配策略等。同时,结合现代操作系统实例,探讨内存管理在多任务环境中的创新应用,如Linux内核的内存管理优化。文章旨在为读者提供一个全面、深入的视角,以理解并掌握操作系统中这一至关重要的组成部分。
|
7天前
|
存储 缓存 算法
深入理解操作系统的内存管理:原理与实践
【5月更文挑战第30天】 在现代计算机系统中,操作系统扮演着至关重要的角色,尤其是内存管理作为其核心功能之一。本文将详细探讨操作系统内存管理的基本原理、关键技术以及实际应用场景。我们将从内存层次结构出发,解析地址转换和分页机制,并探讨虚拟内存技术如何使得系统运行更加高效稳定。同时,通过分析具体案例,本文还将展示内存管理在提升系统性能和安全性方面的重要作用。
|
8天前
|
算法 安全 调度
深入理解操作系统内存管理:原理与实践
【5月更文挑战第29天】 在现代计算机系统中,操作系统扮演着至关重要的角色,其中内存管理是其核心职能之一。本文旨在剖析操作系统中内存管理的基本原理和关键技术,以及它们如何在不同类型的操作系统中得以实现。我们将从内存的分配与回收机制入手,探讨分页、分段以及虚拟内存等概念,并分析它们如何共同作用以支持多任务处理和保护系统安全。通过实例演示和性能分析,本文为读者呈现一个全面而深入的操作系统内存管理视角。
|
9天前
|
机器学习/深度学习 传感器 自动驾驶
基于深度学习的图像识别技术在自动驾驶系统中的应用深入理解操作系统内存管理:原理与实践
【5月更文挑战第28天】 随着人工智能技术的飞速发展,图像识别作为其重要分支之一,在多个领域展现出了广泛的应用潜力。尤其是在自动驾驶系统中,基于深度学习的图像识别技术已成为实现车辆环境感知和决策的关键。本文将深入探讨深度学习算法在自动驾驶图像识别中的作用,分析其面临的挑战以及未来的发展趋势,并以此为基础,展望该技术对自动驾驶安全性和效率的影响。
|
9天前
|
存储 缓存 内存技术
深入理解操作系统内存管理:原理与实践
【5月更文挑战第28天】 在现代计算机系统中,操作系统的内存管理是确保系统高效、稳定运行的关键组成部分。本文将深入探讨操作系统内存管理的基本原理和实践技巧,包括内存分配、虚拟内存技术、分页机制以及内存优化策略等内容。通过对这些概念和技术的详细解析,读者将能够更好地理解操作系统如何管理和优化内存资源,从而提高计算机系统的性能和稳定性。
|
10天前
|
并行计算 安全 大数据
深入理解操作系统内存管理:原理与实践
【5月更文挑战第27天】 在现代计算机系统中,操作系统的内存管理是确保系统高效、稳定运行的关键组成部分。本文旨在深入剖析操作系统内存管理的基本原理,并结合具体案例探讨内存管理的实践应用。文章首先介绍内存管理的基本概念,包括地址空间、虚拟内存和物理内存。随后,详细阐述不同的内存分配策略,如首次适应、最佳适应和伙伴系统,并分析各自的优势与局限性。文章还将探讨分页和分段机制的原理及其在现代操作系统中的应用,并通过实际代码示例展示如何在Linux内核中实现内存管理的关键技术。最后,讨论当前内存管理面临的挑战和未来可能的发展趋势。
|
10天前
|
C++
【操作系统】信号量机制(整型信号量、记录型信号量),用信号量实现进程互斥、同步、前驱关系
【操作系统】信号量机制(整型信号量、记录型信号量),用信号量实现进程互斥、同步、前驱关系
24 6