【操作系统】进程间的通信——信号量

简介: 【操作系统】进程间的通信——信号量

进程间的通信-信号量

  • 信号量就类似与马路上的红绿灯,来控制人们在各个路口朝各个方向上的行进,从而更好地有规划的使用这条道路。
  • 在程序中,信号则对进程们的执行进行控制。

什么是信号量

  • 问题:

    • 在程序中,有时会存在一种特殊代码,同一时间只允许一个进程执行该部分代码。这部分区域,被称为"临界区"
    • 然后在多进程并发执行中,当一个进程进入临界区,因某种原因被挂起时,其他进程就有可能也进入该区域。
  • 解决办法:——使用信号量
  • 什么是信号量?
  • 信号量是一种特殊的变量。
  • 我们只能对信号量执行P操作和V操作。

    • P操作:申请资源。

      • 如果信号量的值>0,则把该信号量-1。
      • 如果信号量的值=0,则挂起该进程。
    • V操作:释放资源。

      • 如果有进程因该信号量而被挂起,则恢复当前进程运行。
      • 如果没有进程因该信号量而被挂起,则把该信号量+1。
  • 注意:

    • P操作、V操作都是原子操作,即,其在执行期间,不会被中断
    • 这里指的信号量是指System V IPC的信号量,与线程所使用的信号量不同。该信号量用于进程间通信

信号量的使用

信号量的获取

  • semget
  • 函数原型:int semget(key_t key, int nsems, int semflg);
  • 功能:获取一个已存在的、或创建一个新的信号量,并返回该信号量的标识符。
  • 参数:
  • key:键值,该键值对应一个唯一的信号量。类似于共享内存的键值。

    • 不同的可通过该键值和semget获取唯一的信号量。
    • 特殊键值——IPC_PRIVAT,该信号量只允许创建者进程才可以访问,可用于父子进程间通信。
  • nsems:需要的信号量数目,一般为1。
  • semflag:访问权限。

    • 若设置为IPC_CREAT,则如果该信号量未存在,则创建该信号量,如果该信号量已经存在,也不会发生错误。
  • 返回值:
  • 成功:返回一个正整数。
  • 失败:返回-1。

信号量的操作

  • semop
  • 函数原型:int semop(int semid, struct sembuf *sops, unsigned nsops);
  • 功能:改变信号量的值,即对信号量执行P操作、V操作。
  • 参数:

    • semid:信号量标识符,即semget函数的返回值。
    • sops:是一个数组,元素类型为struct sembuf。
    • struct sembuf {
              short  sem_num;  //信号量组中的编号(即指定对哪个信号量操作)
                              //semget实际是获取一组信号量
                              //信号量组中的编号从0开始
              short  sem_op;     //操作类型:-1表示P操作;  1表示V操作
              short  sem_flg; // 通常为SEM_UNDO,使操作系统跟踪信号,
                        // 并在进程没有释放该信号量而终止时,操作系统释放信号量         
          }        
    • nsops:表示第二个参数sops所表示的数组大小,即有几个struct sembuf。
  • 返回值:

    • 成功:返回0。
    • 失败:返回-1。
  • 相关参考与补充:Linux进程间通信(五):信号量 semget()、semop()、semctl()

信号量的控制

  • semctl

    • 函数原型:int semctl(int semid, int sem_num, int cmd, ...);
    • 功能:对信号量进行控制。
    • 参数:

      • semid:信号量标识符。
      • sem_num:信号量组中的编号,如果只有一个信号量,则取0。
      • cmd:通常是下面两个值的其中一个。

        • SETVAL:用来把信号量初始化为一个已知的值。p 这个值通过union semun中的val成员设置,其作用是在信号量第一次使用前对它进行设置。
        • IPC_RMID:用于删除一个已经无需继续使用的信号量标识符。
      • 参数四类型为:union semun

        • union  semun {
                           int     val;      // SETVAL命令要设置的值
                           struct  semid_ds  *buf;
                           unsigned short    *array;
          }
        • union semun有些Linux发行版在sys/sem.h中定义,有些则没有定义,可自行定义:
        • #if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED)            #else
          union semun {
              int val;                             
              struct semid_ds *buf;    
              unsigned short int *array; 
              struct seminfo *__buf;  
          };
          #endif     
    • 返回值:略,详见-semctl(2) — Linux manual page
    • 相关参考与补充:Linux进程间通信(五):信号量 semget()、semop()、semctl()

示例

  • 示例1:不使用信号量,并发执行多个程序,观察对临界区的访问。
#include <stdlib.h>
#include <stdio.h>

int main(void) {
    int i;
    pid_t pd = fork();
    for (i=0; i<5; i++) {
        
        /* 模拟临界区----begin */
        printf("Process(%d) In\n", getpid());        
        sleep(1);
        printf("Process(%d) Out\n", getpid());
         /* 模拟临界区----end */ 
        sleep(1);
    }
    return 0;
}

image-20220824164003543

可见并不是我们想要的效果,我们想要的是一个进去了,另外一个就不可以进去了,出去一个,另外一个才可以进去。

  • 示例2:使用信号量,并发指定多个进程,观察对临界区的访问。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdlib.h>
#include <stdio.h>

#if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED)                           
#else
    union semun {
        int val;                             
        struct semid_ds *buf;    
        unsigned short int *array; 
        struct seminfo *__buf;  
    };
#endif     

//信号的初始化
static sem_initial(int semid){
    int ret;
    union semun semun;
    semun.val = 1;
    ret = semctl(semid,0,SETVAL,semun);
    if(ret == -1){
        fprintf(stderr, "semctl failed!\n");
    }
    return  ret;
}

//将p v操作封装成函数
//p操作
static int sem_p(int semid){
    int ret;
    struct sembuf sembuf;
    sembuf.sem_op = -1;//操作类型,设置为-1即p操作。
    sembuf.sem_num = 0;//指定信号量在信号量组中的编号
    sembuf.sem_flg = SEM_UNDO;//让操作系统跟踪信号,如果进程忘记释放,则操作系统进行负责释放。
    ret = semop(semid,&sembuf,1);//根据设置对信号量进行操作
    if (ret == -1) {
        fprintf(stderr, "sem_p failed!\n");
    }
    return ret;
}
//v操作
static int sem_v(int semid){
    int ret;
    struct sembuf sembuf;
    sembuf.sem_op = 1;//操作类型,设置为1即v操作。
    sembuf.sem_num = 0;//指定信号量在信号量组中的编号
    sembuf.sem_flg = SEM_UNDO;//让操作系统跟踪信号,如果进程忘记释放,则操作系统进行负责释放。
    ret = semop(semid,&sembuf,1);//根据设置对信号量进行操作
    if (ret == -1) {
        fprintf(stderr, "sem_v failed!\n");
    }
    return ret;
}

int main(int argc, char* argv[]) {
    int semid;

    //获取信号
    semid =semget((key_t)1234,1,0666 | IPC_CREAT);
    if(semid == -1){
        printf("semget failed!\n");
        exit(1);
    }
    //信号量的初始化
    if (argc > 1) {
        int ret = sem_initial(semid);
        if (ret == -1) {
            exit(1);
        }
    }
    for(;;){
        if(sem_p(semid) == -1){//p操作,申请,若无可用资源,则挂起等待。
            exit(1);
        }

        /* 模拟临界区----begin */
        printf("Process(%d) In\n", getpid());        
        sleep(3);
        printf("Process(%d) Out\n", getpid());
        /* 模拟临界区----end */ 
        if(sem_v(semid) == -1){//v操作,释放。
            exit(1);
        }
    }

    return 0;
}

image-20220824170933245

可以看到,一个出来,另一个才可以进去。
相关文章
|
2天前
|
算法 调度 UED
深入理解操作系统:进程调度与优先级队列
【10月更文挑战第31天】在计算机科学的广阔天地中,操作系统扮演着枢纽的角色,它不仅管理着硬件资源,还为应用程序提供了运行的环境。本文将深入浅出地探讨操作系统的核心概念之一——进程调度,以及如何通过优先级队列来优化资源分配。我们将从基础理论出发,逐步过渡到实际应用,最终以代码示例巩固知识点,旨在为读者揭开操作系统高效管理的神秘面纱。
|
1天前
|
Linux 调度 C语言
深入理解操作系统:进程和线程的管理
【10月更文挑战第32天】本文旨在通过浅显易懂的语言和实际代码示例,带领读者探索操作系统中进程与线程的奥秘。我们将从基础知识出发,逐步深入到它们在操作系统中的实现和管理机制,最终通过实践加深对这一核心概念的理解。无论你是编程新手还是希望复习相关知识的资深开发者,这篇文章都将为你提供有价值的见解。
|
3天前
|
算法 调度 UED
深入理解操作系统的进程调度机制
本文旨在探讨操作系统中至关重要的组成部分之一——进程调度机制。通过详细解析进程调度的概念、目的、类型以及实现方式,本文为读者提供了一个全面了解操作系统如何高效管理进程资源的视角。此外,文章还简要介绍了几种常见的进程调度算法,并分析了它们的优缺点,旨在帮助读者更好地理解操作系统内部的复杂性及其对系统性能的影响。
|
4天前
|
消息中间件 存储 供应链
进程间通信方式-----消息队列通信
【10月更文挑战第29天】消息队列通信是一种强大而灵活的进程间通信机制,它通过异步通信、解耦和缓冲等特性,为分布式系统和多进程应用提供了高效的通信方式。在实际应用中,需要根据具体的需求和场景,合理地选择和使用消息队列,以充分发挥其优势,同时注意其可能带来的复杂性和性能开销等问题。
|
4天前
深入理解操作系统:进程与线程的管理
【10月更文挑战第30天】操作系统是计算机系统的核心,它负责管理计算机硬件资源,为应用程序提供基础服务。本文将深入探讨操作系统中进程和线程的概念、区别以及它们在资源管理中的作用。通过本文的学习,读者将能够更好地理解操作系统的工作原理,并掌握进程和线程的管理技巧。
13 2
|
3天前
|
消息中间件 算法 Linux
深入理解操作系统之进程管理
【10月更文挑战第30天】在数字时代的浪潮中,操作系统作为计算机系统的核心,扮演着至关重要的角色。本文将深入浅出地探讨操作系统中的进程管理机制,从进程的概念入手,逐步解析进程的创建、调度、同步与通信等关键过程,并通过实际代码示例,揭示这些理论在Linux系统中的应用。文章旨在为读者提供一扇窥探操作系统深层工作机制的窗口,同时激发对计算科学深层次理解的兴趣和思考。
|
2天前
|
算法 Linux 调度
深入理解操作系统之进程调度
【10月更文挑战第31天】在操作系统的心脏跳动中,进程调度扮演着关键角色。本文将深入浅出地探讨进程调度的机制和策略,通过比喻和实例让读者轻松理解这一复杂主题。我们将一起探索不同类型的调度算法,并了解它们如何影响系统性能和用户体验。无论你是初学者还是资深开发者,这篇文章都将为你打开一扇理解操作系统深层工作机制的大门。
8 0
|
4月前
|
运维 关系型数据库 MySQL
掌握taskset:优化你的Linux进程,提升系统性能
在多核处理器成为现代计算标准的今天,运维人员和性能调优人员面临着如何有效利用这些处理能力的挑战。优化进程运行的位置不仅可以提高性能,还能更好地管理和分配系统资源。 其中,taskset命令是一个强大的工具,它允许管理员将进程绑定到特定的CPU核心,减少上下文切换的开销,从而提升整体效率。
掌握taskset:优化你的Linux进程,提升系统性能
|
4月前
|
弹性计算 Linux 区块链
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
147 4
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
|
3月前
|
算法 Linux 调度
探索进程调度:Linux内核中的完全公平调度器
【8月更文挑战第2天】在操作系统的心脏——内核中,进程调度算法扮演着至关重要的角色。本文将深入探讨Linux内核中的完全公平调度器(Completely Fair Scheduler, CFS),一个旨在提供公平时间分配给所有进程的调度器。我们将通过代码示例,理解CFS如何管理运行队列、选择下一个运行进程以及如何对实时负载进行响应。文章将揭示CFS的设计哲学,并展示其如何在现代多任务计算环境中实现高效的资源分配。