计算机操作系统(六) 进程控制与进程通信 (附带图谱更好对比理解)

计算机操作系统(六) 进程控制与进程通信


前言

  • 在上一篇博客中,我们深入探讨了前趋图和程序执行以及进程的描述,了解了前趋图如何展示程序操作的先后顺序,程序顺序执行与并发执行的特点,以及进程的定义、特征、基本状态和相关数据结构
  • 了解这些知识后,我们对操作系统中程序的执行方式和进程的基本概念有了更清晰的认识,为进一步学习进程的控制和通信奠定了基础。
  • 本篇博客将继续深入操作系统的世界,聚焦于进程控制与进程通信这两个关键方面
  • 通过学习这些内容,你会了解到操作系统是如何对进程进行创建、终止、阻塞、唤醒等控制操作的,以及不同进程之间是如何进行信息交互和通信的,这将有助于你更全面地理解操作系统的工作原理

我的个人主页,欢迎来阅读我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343

我的操作系统博客专栏
https://blog.csdn.net/2402_83322742/category_12916780.html?spm=1001.2014.3001.5482


一、进程控制

1.1进程的创建

在操作系统中,进程的创建是一个重要的操作。通常,一个新进程的创建可以由以下几种情况引发:

  1. 系统初始化:当操作系统启动时,会创建一些必需的系统进程,如内存管理进程、文件系统管理进程等,这些进程为系统的正常运行提供基础支持。
  2. 用户请求:用户在操作系统中启动一个新的应用程序时,操作系统会创建一个新的进程来执行该程序。例如,当我们双击打开一个文本编辑器时,操作系统会创建一个相应的进程来运行这个文本编辑器。
  3. 进程派生 :一个已经存在的进程可以通过系统调用(如在 Unix/Linux 系统中的 fork() 函数)创建一个新的子进程。子进程会复制父进程的部分或全部资源,包括内存空间、打开的文件描述符等,并开始独立运行。

以 Unix/Linux 系统中的 fork() 函数为例,fork() 函数调用一次会返回两次,在父进程中返回子进程的进程 ID,在子进程中返回 0。

  • 通过这个返回值,父进程和子进程可以区分自己的身份并执行不同的代码逻辑。
c 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
    pid_t pid;
    pid = fork();
    if (pid < 0) {
        // fork 失败
        perror("fork");
        return 1;
    } else if (pid == 0) {
        // 子进程
        printf("I am the child process, my pid is %d\n", getpid());
    } else {
        // 父进程
        printf("I am the parent process, my pid is %d, my child's pid is %d\n", getpid(), pid);
    }
    return 0;
}

1.2进程的终止

进程在完成其任务或遇到异常情况时会终止。进程终止的原因主要有以下几种:

  1. 正常结束:进程执行完其程序代码中的所有语句,达到了程序的自然结束点。例如,一个计算程序完成了所有的计算任务后,会正常终止。
  2. 错误退出:进程在执行过程中遇到错误,如除零错误、内存访问越界等,导致程序无法继续正常执行,从而引发进程终止。
  3. 外界干预 :操作系统或其他进程可以通过发送信号(如在 Unix/Linux 系统中的 kill 命令发送信号)来终止一个进程。例如,当我们在命令行中使用 kill 命令强制关闭一个无响应的程序时,就是通过发送信号让该程序对应的进程终止。

在 Unix/Linux 系统中,进程可以通过调用 exit() 函数来主动终止自己,exit() 函数会执行一些清理工作,如关闭打开的文件、释放内存等,然后将控制权交还给操作系统

1.3进程的阻塞与唤醒

进程在运行过程中,可能会因为等待某些事件的发生而无法继续执行,这时进程会进入阻塞状态。常见的导致进程阻塞的事件包括:

  1. I/O 操作:当进程需要进行输入输出操作(如读取文件数据、等待网络数据到达等)时,由于 I/O 设备的速度相对较慢,进程会进入阻塞状态,直到 I/O 操作完成。
  2. 等待信号量:进程在访问共享资源时,可能需要获取相应的信号量。如果信号量不可用,进程会进入阻塞状态,等待其他进程释放信号量。

当导致进程阻塞的事件发生后,进程会从运行状态转换为阻塞状态,并被放入阻塞队列中。而当所等待的事件发生时,操作系统会将相应的进程从阻塞队列中取出,将其状态转换为就绪状态,这个过程称为唤醒

例如,当一个进程调用 read() 函数读取文件数据时,如果文件数据尚未准备好,进程会进入阻塞状态。当文件数据读取完成后,操作系统会唤醒该进程,使其可以继续执行后续的代码。

1.4进程的挂起与激活

进程的挂起是指将进程从内存中转移到外存(如硬盘)上,以释放内存资源。挂起的进程处于静止状态,不再参与 CPU 的调度。进程挂起的原因主要有:

  1. 内存资源紧张:当系统内存不足时,操作系统可能会选择一些暂时不活跃的进程进行挂起,将其数据保存到外存上,以便为其他更需要内存的进程腾出空间。
  2. 用户请求:用户可以手动请求挂起某个进程,例如在一些任务管理工具中,用户可以将一个后台运行的程序挂起,以减少其对系统资源的占用。

进程的激活是挂起的逆过程,当系统内存资源充足或用户请求激活挂起的进程时,操作系统会将进程从外存调回到内存中,并将其状态转换为就绪状态,使其可以重新参与 CPU 的调度。

二、进程通信

2.1进程通信的类型

进程通信是指不同进程之间进行信息交互的过程。在操作系统中,常见的进程通信类型有以下几种:

  1. 共享内存 :多个进程可以访问同一块内存区域,通过对共享内存的读写操作来实现数据的交换。共享内存是一种高效的通信方式,因为它不需要进行数据的复制,直接在内存中进行操作。但由于多个进程同时访问共享内存可能会导致数据竞争和不一致的问题,因此需要使用同步机制(如信号量、互斥锁等)来保证数据的一致性。
  2. 消息传递 :进程之间通过发送和接收消息来进行通信。消息可以是文本、数据块或其他形式的信息。消息传递方式相对简单,易于实现,并且可以避免共享内存中的数据竞争问题。常见的消息传递方式包括管道(Pipe)、消息队列(Message Queue)等。
  3. 信号量 :信号量主要用于实现进程之间的同步和互斥。它是一个整数值,通过对信号量的操作(如 P 操作和 V 操作)来控制进程对共享资源的访问。信号量可以用来解决生产者 - 消费者问题、读者 - 写者问题等经典的同步问题。
  4. 套接字(Socket) :套接字是一种网络通信机制,也可以用于同一台计算机上不同进程之间的通信。它提供了一种可靠的、双向的数据传输通道,适用于分布式系统中进程之间的通信。

2.2信息传递通信的实现方式

  1. 管道(Pipe) :管道是一种半双工的通信方式,即数据只能在一个方向上传输。它通常用于父子进程之间的通信。在 Unix/Linux 系统中,可以使用 pipe() 函数创建一个管道,然后通过 fork() 函数创建子进程,父子进程通过管道的文件描述符进行数据的读写操作。
  2. 消息队列(Message Queue) :消息队列是一个消息的链表,每个消息都有一个类型和一个数据部分。进程可以向消息队列中发送消息,也可以从消息队列中接收消息。在 Unix/Linux 系统中,使用 msgget() 函数创建消息队列,msgsnd() 函数发送消息,msgrcv() 函数接收消息。
  3. 共享内存的同步机制:为了保证共享内存的一致性,通常会使用信号量、互斥锁等同步机制。例如,在使用共享内存进行通信时,进程在访问共享内存之前,需要先获取相应的信号量或互斥锁,访问完成后再释放,以防止其他进程同时访问共享内存导致数据错误。

2.3实例 :Linux进程通信

以 Linux 系统中的管道通信为例,以下是一个简单的代码示例,展示了父子进程之间如何通过管道进行通信:

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define BUFFER_SIZE 100

int main() {
    int pipefd[2];
    pid_t cpid;
    char buf[BUFFER_SIZE];

    // 创建管道
    if (pipe(pipefd) == -1) {
        perror("pipe");
        return EXIT_FAILURE;
    }

    // 创建子进程
    cpid = fork();
    if (cpid == -1) {
        perror("fork");
        return EXIT_FAILURE;
    }

    if (cpid == 0) {  // 子进程
        close(pipefd[1]);  // 关闭写端

        // 从管道读数据
        ssize_t nbytes = read(pipefd[0], buf, BUFFER_SIZE);
        if (nbytes == -1) {
            perror("read");
            return EXIT_FAILURE;
        }

        printf("Child received: %s\n", buf);

        close(pipefd[0]);  // 关闭读端
        return EXIT_SUCCESS;
    } else {  // 父进程
        close(pipefd[0]);  // 关闭读端

        char *message = "Hello from parent!";
        // 向管道写数据
        if (write(pipefd[1], message, strlen(message) + 1) == -1) {
            perror("write");
            return EXIT_FAILURE;
        }

        close(pipefd[1]);  // 关闭写端
        wait(NULL);  // 等待子进程结束
        return EXIT_SUCCESS;
    }
}

总结(核心概念速记):

核心概念速记
操作系统之进程控制与通信 = 进程全生命周期控制 + 多样通信方式实现 + 机制应用与系统关联

  • 进程控制要点
    • 创建 :源于系统初始化、用户请求、进程派生(如 fork()),新进程获部分或全部父进程资源。
    • 终止 :包括正常结束、错误退出、外界干预(如 kill 命令、exit() 函数),需清理资源。
    • 阻塞与唤醒:因 I/O 操作、等信号量致阻塞,入阻塞队列;事件发生则唤醒,转就绪态。
    • 挂起与激活:内存紧张或用户请求致挂起(移至外存),内存足或用户需则激活(调回内存)。
  • 进程通信方式
    • 共享内存:多进程读写同内存区,高效但需同步机制(信号量、互斥锁)防数据竞争。
    • 消息传递:通过消息(如管道、消息队列)收发通信,简单易实现,避数据竞争。
    • 信号量:整数值,P、V 操作控制共享资源访问,解生产者 - 消费者等同步问题。
    • 套接字:可靠双向通道,用于网络及同机进程通信,适分布式系统。
  • 机制应用与系统关联
    • 进程控制确保系统资源合理分配与回收,维护进程有序运行。
    • 进程通信实现进程间信息交互协作,支撑复杂系统功能实现。

进程控制状态转换对比

控制操作 状态转换 触发条件 作用
创建 无 -> 就绪 系统初始化、用户启动程序、进程派生 产生新进程,分配资源准备运行
终止 运行/就绪/阻塞 -> 无 正常结束、错误、外界信号(killexit 结束进程,回收资源
阻塞 运行 -> 阻塞 I/O 操作未完成、信号量不可用 使进程暂停,释放 CPU 等资源
唤醒 阻塞 -> 就绪 等待事件发生(I/O 完成、信号量获) 恢复进程可运行状态
挂起 就绪/阻塞 -> 就绪挂起/阻塞挂起 内存不足、用户手动请求 释放内存资源,暂停进程活动
激活 就绪挂起/阻塞挂起 -> 就绪 内存充足、用户请求激活 恢复进程到可调度状态

进程通信方式特点关系

复制代码
通信方式 → 特点 → 适用场景 → 同步需求  
   ↓          ↓           ↓            ↓  
   共享内存 → 高效,需同步 → 同机进程大量数据交互 → 强(信号量、互斥锁)  
   消息传递 → 简单,避竞争 → 进程间少量数据交换 → 弱(消息队列有序)  
   信号量 → 同步互斥控制 → 共享资源访问控制 → 强(P、V 操作)  
   套接字 → 可靠双向,网络适用 → 分布式系统进程通信 → 依具体场景  

知识图谱

复制代码
计算机操作系统(六) 进程控制与进程通信  
├─ 进程控制  
│  ├─ 进程创建(系统、用户、进程派生)  
│  ├─ 进程终止(正常、错误、外界干预)  
│  ├─ 进程阻塞与唤醒(I/O、信号量相关)  
│  └─ 进程挂起与激活(内存、用户因素)  
├─ 进程通信  
│  ├─ 通信类型(共享内存、消息传递、信号量、套接字)  
│  ├─ 实现方式(管道、消息队列、同步机制)  
│  └─ Linux 实例(管道通信代码示例)  
├─ 关键术语  
│  ├─ `fork()`、`kill()`、`exit()`、`read()`、`write()`  
│  ├─ 共享内存、消息传递、信号量、套接字  
│  ├─ 管道、消息队列、互斥锁  
└─ 应用理解  
   ├─ 进程控制对系统资源管理影响  
   ├─ 进程通信对系统功能协作作用  
   └─ 不同场景下控制与通信方式选择  

重点提炼

  1. 进程控制关键
    • 理解进程创建多种方式及资源继承关系,把握系统启动与程序运行基础。
    • 清楚进程终止原因和方式,重视资源清理,保障系统稳定。
    • 掌握阻塞、唤醒、挂起、激活条件和机制,理解进程状态转换对资源调度的意义。
  2. 进程通信核心
    • 区分不同通信方式特点和适用场景,根据需求合理选择,如共享内存适合大量数据,消息传递适合少量数据。
    • 认识同步机制在共享内存和信号量中的重要性,防止数据不一致和资源竞争。
    • 通过 Linux 实例学习进程通信实现细节,提升编程实践能力,理解操作系统底层机制。
  3. 系统关联应用
    • 进程控制和通信共同支撑操作系统高效运行,是实现多任务、资源共享等功能的基础。
    • 在实际系统设计和开发中,综合运用进程控制和通信机制,优化系统性能和稳定性。
  4. 技术拓展思考
    • 随着多核处理器和分布式系统发展,进程控制和通信面临新挑战和优化方向,如多核间进程调度、分布式通信效率。
    • 研究新的同步和通信算法,提升系统并发性能和数据一致性,适应复杂应用场景需求。

进程通信同步机制对比表

同步机制 原理 优点 缺点 适用场景
信号量 整数值,P、V 操作改变值控制资源访问 灵活控制资源互斥与同步,可实现复杂同步逻辑 使用不当易死锁,需精确设计操作顺序 多进程共享有限资源场景,如生产者 - 消费者问题
互斥锁 锁定资源,一次仅一进程访问 简单直观,有效防止数据竞争 进程长时间持有锁影响并发性能,可能死锁 对共享资源短时间独占访问场景
条件变量 结合互斥锁,线程等待特定条件满足 避免线程忙等待,提高资源利用率 需与互斥锁配合使用,编程复杂 线程需等待特定事件发生场景,如生产者 - 消费者缓冲区非空/非满

操作系统进程控制与通信演进脉络

复制代码
技术演进 ------ 简单进程控制(创建、终止) → 状态转换与资源管理(阻塞、唤醒、挂起) → 多样通信方式(共享内存、消息传递) → 分布式通信拓展(套接字)  
   ↓         ↓               ↓               ↓  
应用升级 ------ 单任务程序执行 → 多任务并发管理 → 同机进程协作 → 分布式系统跨机通信  

以上就是对本次关于操作系统博客内容的总结,后续我们将深入探讨操作系统更多知识。

我的个人主页,欢迎来阅读我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343

我的操作系统博客专栏
https://blog.csdn.net/2402_83322742/category_12916780.html?spm=1001.2014.3001.5482

|--------------------|
| 非常感谢您的阅读,喜欢的话记得三连哦 |

相关推荐
fridayCodeFly39 分钟前
:ref 和 this.$refs 的区别及 $ 的作用
linux·运维·服务器
LJTYBQ1 小时前
轻松认识 SQL 关键字,打开数据库操作大门
数据库·笔记·sql
Blossom.1181 小时前
基于Python的机器学习入门指南
开发语言·人工智能·经验分享·python·其他·机器学习·个人开发
山外有山a1 小时前
从 Neo4j 数据库中提取数据并绘制图谱
数据库·neo4j
郝YH是人间理想2 小时前
Python面向对象
开发语言·python·面向对象
Full Stack Developme3 小时前
SQL 版本历史
数据库·sql
大刀爱敲代码3 小时前
基础算法01——二分查找(Binary Search)
java·算法
Hum8le3 小时前
小科普《DNS服务器》
运维·服务器
大土豆的bug记录4 小时前
鸿蒙进行视频上传,使用 request.uploadFile方法
开发语言·前端·华为·arkts·鸿蒙·arkui
故事与他6455 小时前
Thinkphp(TP)框架漏洞攻略
android·服务器·网络·中间件·tomcat