当前位置: 首页 > news >正文

Linux应用:PCB、fork

什么是进程

进程是程序的一次执行过程,是操作系统进行资源分配和调度的基本单位。它包含了程序执行的上下文环境,如代码、数据、打开的文件描述符、信号处理函数等。每个进程在操作系统中都有唯一的标识符(PID,Process ID),操作系统通过 PID 来管理和控制进程。

进程控制块PCB(Process Control Block)

PCB 是进程存在的唯一标志,当操作系统创建一个新进程时,会为其分配一个 PCB;当进程终止时,操作系统会回收其 PCB。每个进程都有一个与之对应的 PCB,操作系统通过对 PCB 的操作来实现对进程的控制和管理。

作用

进程标识:每个 PCB 都有一个唯一的标识符,即进程 ID(PID),用于区分不同的进程。
进程控制:操作系统通过 PCB 来控制进程的执行,如进程的创建、暂停、继续、终止等操作。
进程调度:调度程序根据 PCB 中的信息(如优先级、状态等)来决定哪个进程可以获得 CPU 资源,以及何时获得 CPU 资源。
资源管理:PCB 记录了进程所占用的各种资源,如内存、文件、设备等,操作系统可以根据这些信息进行资源的分配和回收。
上下文切换:当进程被暂停执行时,其当前的执行上下文(如寄存器值、程序计数器等)会被保存到 PCB 中;当进程再次获得 CPU 资源时,操作系统会从 PCB 中恢复其执行上下文,使进程能够继续执行。
包含信息
不同操作系统的 PCB 所包含的信息可能会有所不同,但一般都包含以下几类信息:

进程标识符

内部标识符:操作系统为每个进程分配的唯一数字标识符,用于在操作系统内部对进程进行管理和识别。
外部标识符:由用户或应用程序使用的标识符,通常是一个字符串,用于方便用户或应用程序对进程进行操作和管理。
处理机状态
通用寄存器:用于保存进程在执行过程中的中间结果和临时数据。
程序计数器(PC):指示下一条要执行的指令的地址。
状态寄存器:保存进程的运行状态信息,如进位标志、溢出标志等。
栈指针:指向进程栈的栈顶地址,用于管理进程的函数调用和局部变量。

进程调度信息

进程状态:如就绪、运行、阻塞、终止等,用于描述进程当前的执行状态。
优先级:用于确定进程在调度时的优先顺序,优先级高的进程通常会优先获得 CPU 资源。
调度相关信息:如进程的等待时间、执行时间等,用于调度算法的计算和决策。

进程控制信息

程序和数据的地址:指示进程的程序代码和数据在内存中的存储位置。
进程同步和通信机制:如信号量、消息队列等,用于实现进程之间的同步和通信。
资源清单:记录进程所占用的各种资源,如内存、文件、设备等。
链接指针:用于将多个 PCB 组织成某种数据结构,如链表、队列等。

进程ID

getpid():返回当前进程的进程 ID(PID)。
getppid():返回当前进程的父进程 ID(PPID)。
getuid():返回当前进程的实际用户 ID(UID)。
geteuid():返回当前进程的有效用户 ID(EUID)。
getgid():返回当前进程的实际组 ID(GID)。
getegid():返回当前进程的有效组 ID(EGID)

#include <iostream>
#include <unistd.h>  // 包含系统调用函数的头文件

int main() {
    // 获取当前进程的进程ID
    pid_t pid = getpid();
    std::cout << "当前进程的进程ID (PID): " << pid << std::endl;

    // 获取当前进程的父进程ID
    pid_t ppid = getppid();
    std::cout << "当前进程的父进程ID (PPID): " << ppid << std::endl;

    // 获取当前进程的实际用户ID
    uid_t uid = getuid();
    std::cout << "当前进程的实际用户ID (UID): " << uid << std::endl;

    // 获取当前进程的有效用户ID
    uid_t euid = geteuid();
    std::cout << "当前进程的有效用户ID (EUID): " << euid << std::endl;

    // 获取当前进程的实际组ID
    gid_t gid = getgid();
    std::cout << "当前进程的实际组ID (GID): " << gid << std::endl;

    // 获取当前进程的有效组ID
    gid_t egid = getegid();
    std::cout << "当前进程的有效组ID (EGID): " << egid << std::endl;

    return 0;
}

在这里插入图片描述

多进程调度原理

操作系统采用多种调度算法来决定在多个进程之间如何分配 CPU 时间。常见的调度算法有先来先服务(FCFS)、短作业优先(SJF)、优先级调度、时间片轮转调度等。这些算法根据进程的特性(如预计执行时间、优先级等)和系统资源的使用情况,合理地安排进程的执行顺序,以提高系统的整体性能和资源利用率。

先来先服务(FCFS)调度算法

原理:按照进程进入就绪队列的先后顺序来分配 CPU。先进入队列的进程先获得 CPU 资源,一直执行到完成或因某种原因阻塞才释放 CPU。
优点:实现简单,公平性好,每个进程都按照其到达的先后顺序获得服务,不会出现进程被饿死的情况。
缺点:如果有一个长作业先进入队列,那么后面的短作业即使已经就绪,也必须等待长作业执行完成才能得到处理,导致短作业的平均等待时间可能很长,系统的整体效率不高。

短作业优先(SJF)调度算法

原理:从就绪队列中选择预计执行时间最短的进程来分配 CPU。这种算法假设每个进程的执行时间是已知的,它总是优先选择执行时间短的作业,以减少平均周转时间。
优点:可以有效地降低作业的平均等待时间,提高系统的吞吐量。因为短作业能够更快地完成,释放资源,让其他作业有更多机会使用 CPU。
缺点:需要事先知道每个作业的执行时间,这在实际中往往是难以准确预测的。而且该算法可能导致长作业长时间等待,甚至出现饥饿现象,即长作业可能一直得不到执行的机会。

优先级调度算法

原理:为每个进程分配一个优先级,根据优先级的高低来决定进程的执行顺序。优先级高的进程优先获得 CPU 资源,当有更高优先级的进程进入就绪队列时,当前正在执行的低优先级进程可能会被抢占,除非系统采用非抢占式优先级调度,即只有当高优先级进程主动放弃 CPU 或阻塞时,低优先级进程才有机会执行。
优点:可以根据进程的重要性或紧急程度来分配 CPU 资源,确保关键任务能够优先得到处理。
缺点:如果不采取适当的措施,低优先级的进程可能会长期处于等待状态,出现饥饿现象。此外,如何合理地确定进程的优先级也是一个复杂的问题,需要综合考虑多种因素。

时间片轮转调度算法

原理:将 CPU 的时间划分成一个个固定长度的时间片,每个进程轮流在一个时间片内执行。当时间片用完后,无论进程是否执行完成,都将被暂停,然后将 CPU 分配给下一个就绪进程。如此循环,直到所有进程都执行完毕或满足某种结束条件。
优点:可以保证每个进程都能在一定时间内获得 CPU 资源,实现了进程之间的公平调度,用户会感觉系统在同时处理多个任务,提高了系统的响应速度和交互性。
缺点:由于进程的切换需要保存和恢复现场等操作,会带来一定的系统开销。如果时间片设置得太短,会导致进程切换过于频繁,系统开销增大;如果时间片设置得太长,又会退化为类似 FCFS 算法,无法及时响应其他进程的请求。

fork创建子进程

fork函数是 UNIX/Linux 系统中用于创建子进程的系统调用。调用fork函数后,操作系统会为新的子进程分配资源,包括内存空间、文件描述符等,并复制父进程的上下文环境。子进程几乎是父进程的一个副本,它们从fork函数调用点开始并发执行不同的代码分支,通过fork函数的返回值来区分父子进程,父进程返回子进程的 PID,子进程返回 0。

#include <iostream>
#include <unistd.h>  // 包含fork()函数的头文件

int main() {
    // 调用fork()函数创建子进程
    pid_t pid = fork();

    // 检查fork()的返回值
    if (pid < 0) {
        // fork()失败,输出错误信息
        std::cerr << "Fork failed!" << std::endl;
        return 1;
    } else if (pid == 0) {
        // 子进程,fork()返回值为0
        std::cout << "This is the child process. Its PID is: " << getpid() << std::endl;
        std::cout << "The parent PID of the child process is: " << getppid() << std::endl;
    } else {
        // 父进程,fork()返回子进程的PID
        std::cout << "This is the parent process. Its PID is: " << getpid() << std::endl;
        std::cout << "The PID of the child process is: " << pid << std::endl;
    }

    return 0;
}

包含头文件:
#include :用于输入输出操作。
#include <unistd.h>:包含了fork()函数以及getpid()和getppid()函数的声明。getpid()用于获取当前进程的 PID,getppid()用于获取当前进程的父进程的 PID。
调用fork()函数:
pid_t pid = fork();:调用fork()函数创建子进程。fork()函数会返回两次,在父进程中返回子进程的 PID,在子进程中返回 0,如果创建失败则返回 - 1。
检查fork()的返回值:
if (pid < 0):表示fork()调用失败,输出错误信息并返回 1。
else if (pid == 0):表示当前代码在子进程中执行,输出子进程的 PID 和父进程的 PID。
else:表示当前代码在父进程中执行,输出父进程的 PID 和子进程的 PID。
在这里插入图片描述

什么是子进程

子进程是由另一个进程(父进程)通过fork或其他类似机制创建的新进程。子进程继承了父进程的许多属性,如用户 ID、组 ID、环境变量、文件描述符等,但它有自己独立的 PID 和内存空间,与父进程并发执行,在一定程度上可以看作是一个独立的程序在运行。

子进程和父进程的关系

父子关系:父进程创建子进程,它们之间存在明确的父子层次结构。​
资源继承:子进程继承父进程的部分资源,如打开的文件描述符,这意味着父进程打开的文件在子进程中仍然保持打开状态。​
并发执行:父子进程并发运行,它们的执行顺序由操作系统的调度算法决定。​
生命周期关联:一般情况下,父进程可以通过wait系列函数等待子进程结束,并获取子进程的退出状态,子进程的结束也可能会影响父进程的后续行为。

父子进程对文件的操作

由于子进程继承了父进程的文件描述符,父子进程对同一文件描述符所指向的文件进行操作时,具有一些特殊的行为。例如,父子进程共享文件的偏移量,一个进程对文件的写入操作会影响另一个进程对文件的读取位置。但如果在fork之后,某个进程又单独打开了同一个文件,那么它会得到一个新的文件描述符,与父进程的文件描述符相互独立,对文件的操作也相互独立。

#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <cstring>
#include <sys/types.h>
#include <sys/wait.h>

#define BUFFER_SIZE 100
#define FILE_NAME "test_file.txt"

int main() {
    pid_t pid = fork();

    if (pid < 0) {
        std::cerr << "Fork failed!" << std::endl;
        return 1;
    } else if (pid == 0) {
        // 子进程
        sleep(1); // 等待父进程完成写入操作

        FILE *fp = fopen(FILE_NAME, "r");
        if (fp == nullptr) {
            std::cerr << "Child process: Failed to open file!" << std::endl;
            return 1;
        }

        char buffer[BUFFER_SIZE];
        memset(buffer, 0, BUFFER_SIZE);

        size_t bytesRead = fread(buffer, sizeof(char), BUFFER_SIZE - 1, fp);
        if (bytesRead > 0) {
            std::cout << "Child process: Read from file: " << buffer << std::endl;
        }

        fclose(fp);
    } else {
        // 父进程
        FILE *fp = fopen(FILE_NAME, "w");
        if (fp == nullptr) {
            std::cerr << "Parent process: Failed to open file!" << std::endl;
            return 1;
        }

        const char *message = "Hello, this is written by the parent process!";
        fwrite(message, sizeof(char), strlen(message), fp);
        fclose(fp);

        std::cout << "Parent process: Wrote to file." << std::endl;

        // 等待子进程结束
        int status;
        waitpid(pid, &status, 0);
    }

    return 0;
}

头文件包含:
:用于标准输入输出操作。
<unistd.h>:包含 fork() 和 sleep() 等函数。
<fcntl.h>:提供文件控制相关的函数和常量。
:用于字符串操作,如 strlen 和 memset。
<sys/types.h> 和 <sys/wait.h>:提供进程相关的类型和 waitpid 函数。
常量定义:
BUFFER_SIZE:定义读取文件时使用的缓冲区大小。
FILE_NAME:指定要操作的文件名称。
fork() 函数调用:
通过 fork() 创建子进程,依据返回值判断当前是父进程还是子进程。
子进程操作:
调用 sleep(1) 让子进程等待 1 秒,确保父进程完成文件写入操作。
以只读模式打开文件,若打开失败则输出错误信息。
使用 fread 函数从文件读取数据到缓冲区,并输出读取到的内容。
关闭文件。
父进程操作:
以写入模式打开文件,若打开失败则输出错误信息。
使用 fwrite 函数将字符串写入文件。
关闭文件并输出写入成功的信息。
调用 waitpid 等待子进程结束。
在这里插入图片描述

相关文章:

  • Linux C++ 编程死锁详解
  • JAVA-Thread类实现多线程
  • 想怎么测就怎么测?CAD“弧线智能打断”,让测量不再受限!
  • 【机器学习】迁移学习(Transfer Learning)
  • 多模态自适应融合技术:轻量级AutoFusion与GAN-Fusion详解
  • 如何设计微服务及其设计原则?
  • Redis--zset类型
  • 使用conda将python环境打包,移植到另一个linux服务器项目中
  • IO多路转接 ——— select、poll、epoll
  • C# NX二次开发:获取模型中所有的草图并获取草图中的对象
  • PostgreSQL 多数据库集簇配置及多数据库复制方法【流程+代码实例】
  • 【操作系统安全】任务2:用户与用户组
  • 手写智能指针shared_ptr
  • 【软考网工-实践篇】DHCP 动态主机配置协议
  • springboot436-基于SpringBoot的汽车票网上预订系统(源码+数据库+纯前后端分离+部署讲解等)
  • Windsurf初体验
  • CUDA编程之OpenCV与CUDA结合使用
  • 人工智能】数据挖掘与应用题库(401-500)
  • c++介绍智能指针 十二(1)
  • python画图文字显示不全
  • wordpress自建图床/seo网页的基础知识
  • 做手机旅游网站/网站模板大全
  • vps做vpn svn和网站/广告联盟接单赚钱平台
  • 自己做婚恋网站/国内优秀网页设计赏析
  • 做网站有什么类型/小程序开发平台有哪些
  • ps快速做网站/网络运营课程培训班