在Linux系统中程序是如何执行的?

1. 进程的创建

在操作系统中,进程是通过系统调用创建的。不同的操作系统使用不同的系统调用来创建进程。在 Unix 和 Linux 系统中,常见的系统调用是 fork() 和 exec(),Linux 系统中,fork() 系统调用用于创建一个新的进程。fork() 调用会创建当前进程的一个拷贝,这个拷贝称为子进程。子进程与父进程几乎完全相同,但有一个不同之处:子进程有自己独立的进程 ID。exec() 系列函数用于替换当前进程的内存空间,并执行一个新的程序。通常,exec() 与 fork() 结合使用,先使用 fork() 创建子进程,然后在子进程中使用 exec() 加载并执行新的程序。

以下是一个使用 fork() 和 exec() 的简单示例:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main() {
    pid_t pid;

    pid = fork(); // 创建子进程

    if (pid < 0) { // fork 失败
        fprintf(stderr, "Fork Failed");
        return 1;
    } else if (pid == 0) { // 子进程
        execlp("/bin/ls", "ls", NULL); // 执行 ls 命令
    } else { // 父进程
        wait(NULL); // 等待子进程完成
        printf("Child Complete\n");
    }

    return 0;
}

2.可执行程序的加载

程序文件被执行时,操作系统将可执行文件从磁盘复制到内存中的一个合适的位置,这个过程称为加载。在 Linux 系统中,可执行程序的加载过程是由内核负责的,主要通过以下几个步骤来完成:

1. 内核将程序文件标识为一个进程,并为其分配必要的资源,如内存、文件描述符和安全性ID等。

2. 程序调用 exec 系列函数:当一个进程调用 exec 系列函数(例如 execlexecvexecleexecve, 等等)时,它请求内核用一个新的可执行程序来替换当前进程的地址空间。

3. 内核首先验证可执行文件的存在性和可执行性,然后为该进程创建一个新的地址空间,并清理旧的地址空间。

4. 内核将程序的代码段、数据段和堆栈段等加载到内存中,并开始执行程序的入口点(例如,在ELF格式的可执行文件中,这是ELF头中指定的入口地址)。

当执行 ./example 命令时,shell 进程会通过系统调用 execve() 来加载 example 可执行文件。内核将程序加载到内存中,并为其创建一个新的进程环境。然后,CPU 将执行权限转移到程序的入口点,开始执行程序的机器代码。

3. 进程的调度

一个进程的生命周期包括以下几个阶段:

  1. 创建(Creation):进程通过系统调用(如 fork 或 clone)创建。在这个阶段,操作系统为新进程分配必要的资源,并初始化进程控制块(PCB)。
  2. 就绪(Ready):新创建的进程进入就绪队列,等待操作系统调度。
  3. 运行(Running):当调度器选择该进程运行时,它进入运行状态,占用 CPU 资源执行指令。
  4. 等待(Waiting/Blocked):如果进程需要等待某个事件(如 I/O 操作完成),它会进入等待状态,暂时放弃 CPU 资源。
  5. 结束(Termination):当进程完成执行或被强制终止时,它进入终止状态,操作系统回收进程占用的资源。

Linux 的进程调度机制是内核负责的重要功能之一,它决定了每个进程何时运行、运行多长时间,以确保系统的高效运行和响应。Linux支持多种调度策略:如先来先服务(FCFS),短作业优先(SJF),现代 Linux 常用的是基于完全公平调度(CFS, Completely Fair Scheduler)的调度算法。

完全公平调度(CFS)

CFS 的目标是尽可能公平地分配 CPU 时间给所有进程。它通过虚拟运行时间(vruntime)和红黑树(red-black tree)来实现这一目标。

1. 虚拟运行时间(vruntime)
  • 概念:vruntime 是每个进程的一个属性,表示进程的“虚拟”运行时间。它与实际运行时间不同,因为它考虑了进程的优先级。
  • 权重:优先级高的进程具有较小的权重,vruntime 增长较慢;优先级低的进程具有较大的权重,vruntime 增长较快。这确保了低优先级进程不会占用过多 CPU 时间。
2. 红黑树
  • 数据结构:CFS 使用红黑树来组织所有可运行的进程。红黑树是一种自平衡二叉搜索树,它保证了在最坏情况下基本操作(插入、删除、查找)的时间复杂度为 O(log N)。
  • 排序:进程按照 vruntime 存储在红黑树中,vruntime 最小的进程位于树的最左边,这个进程将被调度运行。
3. 调度过程
  • 选择进程:调度程序从红黑树中选择 vruntime 最小的进程进行调度。这确保了每次调度都是最公平的选择。
  • 更新 vruntime:当进程运行时,它的 vruntime 会增加,增加的速率取决于它的优先级。运行结束后,进程重新插入红黑树,并按照新的 vruntime 重新排序。

CFS 调度器的特点

  1. 公平性:CFS 通过 vruntime 和红黑树确保了每个进程获得尽可能公平的 CPU 时间。
  2. 高效性:使用红黑树实现进程的快速查找、插入和删除,保证了调度的高效性。
  3. 灵活性:可以处理不同优先级的进程,适应不同的工作负载和系统需求。

具体示例

假设系统中有三个进程 A、B、C,它们的优先级和 vruntime 如下:

  • 进程 A:优先级高,vruntime 较小。
  • 进程 B:优先级中等,vruntime 中等。
  • 进程 C:优先级低,vruntime 较大。

红黑树中的排序可能如下:

    B
   / \
  A   C

调度程序会选择 vruntime 最小的进程 A 来运行。随着进程 A 的运行,它的 vruntime 会增加。当它的 vruntime 超过 B 时,红黑树会重新排序,可能变成:

    A
   / \
  B   C

然后调度程序会选择新的 vruntime 最小的进程 B 来运行,以此类推。

4. 程序的执行

在完成上述步骤1,2(进程创建,程序加载)后,进入程序的执行。如下面示例:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

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

    if (pid < 0) { // fork 失败
        fprintf(stderr, "Fork Failed\n");
        return 1;
    } else if (pid == 0) { // 子进程
        printf("Child Process: PID = %d\n", getpid());
        execlp("/bin/ls", "ls", NULL); // 执行 ls 命令
    } else { // 父进程
        wait(NULL); // 等待子进程完成
        printf("Child Complete\n");
    }

    return 0;
}

详细步骤说明

  1. 调用 fork()

    • 父进程调用 fork() 创建一个新的子进程。fork() 返回两次:一次在父进程中返回子进程的 PID,一次在子进程中返回 0。
  2. 子进程调用 execlp()

    • 子进程调用 execlp("/bin/ls", "ls", NULL),使用 ls 命令替换当前进程的地址空间。这个调用不会返回,除非发生错误。
  3. 执行 ls 命令:

    • 内核加载 /bin/ls 可执行文件,将其映射到子进程的地址空间中,并将控制权交给 ls 程序。
  4. 父进程等待:

    • 父进程调用 wait(NULL),等待子进程完成。当子进程执行完 ls 命令并退出后,父进程继续执行,打印 "Child Complete"。

5. 总结

Linux 系统下,进程的创建、可执行程序的加载、程序的执行和进程的调度是操作系统核心功能的组成部分。其中每个过程都有很多Linux独有的特点和细节需要注意。通过本次实验,我更加深刻理解了这些内容的实现过程和步骤,更好地掌握了Linux系统的程序的活动和进程之间的关系,对于个人以后更好的使用Linux进行编程和提升代码编写的能力。

  • 30
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值