Linux系统中程序的执行过程

一、创建进程

1. fork()系统调用

  • 作用:在Linux系统中,fork()系统调用用于创建一个新的进程。当一个进程调用fork()时,操作系统会创建一个该进程的副本(也就是子进程),它继承了父进程的大多数属性,包括内存空间(初始时通过写时拷贝技术共享)、环境变量、打开的文件描述符、信号处理器设置等。创建子进程后,父子进程将并发执行,各自拥有独立的执行流。
  • 返回值:在父进程中,fork()返回新创建子进程的进程ID;在子进程中,fork()返回0。如果fork()调用失败(比如由于资源不足),则在父进程中返回-1,并设置相应的error。
  • 流程:
    1. 当进程调用fork()后,操作系统首先为新进程分配必要的资源,包括进程控制块、栈空间、堆空间等。
    2. 操作系统将父进程的大部分上下文(如内存、寄存器状态、打开文件描述符表等)复制到子进程中。这个复制过程会采用写时拷贝技术(父子进程共享内存),以节省内存资源,直到父子进程中的任一方尝试修改共享页面时,才会真正为该进程分配独立的物理页面。
    3. 父进程和子进程都从调用fork()的下一条指令开始并发执行。

这里有一个使用fork的代码示例

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

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

    if (pid < 0) {
        std::cerr << "fork调用失败" << std::endl;
        return 1;
    } else if (pid == 0) {
        // 子进程分支
        std::cout << "子进程id为: " << getpid() << std::endl;
        // 执行子进程的操作
        // ..
    } else {
        // 父进程分支
        std::cout << "父进程id为: " << getpid() << std::endl;
        // 等待所有子进程结束
        wait(NULL); 
    }

    return 0;
}

二、可执行程序的加载及执行

exec()系统调用

  • 作用:exec()系列函数(包括execl(), execv(), execle(),execvp()等)用于在一个已存在的进程中加载并运行一个新的程序,替换当前进程的地址空间、代码段、数据段等,从而让进程执行不同的程序,而不是简单地创建一个进程的复制品。
  • 参数:这些函数需要指定要执行的程序文件名以及传递给该程序的参数列表。
  • 特点:一旦exec()成功执行,原来的进程(包括其代码、数据、堆栈等)将被新程序完全取代,不会返回到exec()调用点。

以这段c++代码为例

#include <iostream>
using namespace std;

int findMax(int arr[], int n) {
    if (n <= 0) {
        throw invalid_argument("数组必须至少包含一个元素!");
    }
    int maxVal = arr[0];
    for(int i = 1; i < n; i++) {
        if(arr[i] > maxVal) {
            maxVal = arr[i];
        }
    }
    return maxVal;
}

int main() {
    int arr[] = {3, 5, 7, 2, 8, 1, 6}; // 示例数组
    int n = sizeof(arr)/sizeof(arr[0]); // 计算数组长度
    
    try {
        int maxVal = findMax(arr, n);
        cout << "数组中的最大值为: " << maxVal << endl;
    } catch (const invalid_argument& e) {
        cerr << e.what() << endl;
    }
    return 0;
}
1. 编译阶段
  • 预处理:我们使用g++编译器执行预处理操作,处理源代码中的预处理指令(如#include、#define等)。在这个例子中,#include<iostream>会被替换为相应的头文件内容。
  • 编译:预处理后的源代码被编译成汇编代码,然后进一步编译成目标代码。对于每个.cpp文件,这会产生一个对应的.o目标文件。
  • 链接:链接器将所有目标文件以及所需的库文件(如标准C++库)链接在一起,形成一个完整的可执行文件。
g++ maxVal.cpp -o maxVal
2. 加载阶段
  • 加载程序:当你在终端输入./maxVal来运行程序时,Linux shell通过调用execve()系统调用来启动一个新的进程,并指定maxVal作为该进程的程序映像。
  • 内核初始化:Linux内核为新进程分配资源,包括虚拟内存空间、PID(进程ID)、打开文件描述符等。然后,内核读取可执行文件的头部信息(ELF格式在Linux中最常见),根据这些信息设置堆栈、堆、代码段和数据段的位置。
  • 动态链接:如果可执行文件是动态链接的(例如,使用共享库),内核会加载这些共享库到进程的地址空间中,并进行重定位,解决动态链接符号。对于C++程序,这通常涉及glibc等库。
  • 程序入口点:内核将控制权传递给程序的入口点(通常是 _start符号,由链接器设定,之后会跳转到C/C++程序的main函数)。此时,C++运行时环境(如全局变量的初始化、构造函数的调用)也会被执行。
  • 执行:main函数开始执行,按照代码的逻辑顺序进行。在这个例子中,程序会创建一个数组,计算并打印出最大值,然后正常退出。
  • 资源清理:当main函数返回或通过其他方式结束时,C++的析构函数会被调用以清理资源,然后内核回收进程占用的所有资源,关闭进程。

三、进程调度

时间片轮转调度
  • 原理:每个进程被分配一个固定的时间片,在这个时间片内运行,然后被挂起,让下一个进程运行。如果一个进程在时间片结束之前没有完成,它将被放回队列的尾部等待下一次调度。
  • 优点:确保了公平性和资源的均衡分配,每个任务都有机会获得一定的执行时间,避免了某个长时间运行的任务占用CPU的问题。
  • 缺点:可能会引入较高的上下文切换开销,对于长时间运行的任务可能不够高效。
优先级调度
  • 原理:每个进程被分配一个优先级值,根据优先级决定进程的调度顺序。具有较高优先级的进程将优先被调度,而具有较低优先级的进程将被延迟调度。
  • 优点:能够实现实时性需求和优先级控制,快速响应高优先级任务。
  • 缺点:如果高优先级任务一直占用CPU,可能导致低优先级任务得不到执行机会(饥饿现象)。
进程调度器

Linux使用了完全公平调度器(CFS)作为其默认的进程调度算法。CFS旨在为所有进程提供公平的CPU时间份额,特别强调交互式进程的响应性。

调度队列

CFS使用红黑树来组织可运行进程,根据进程的虚拟运行时间(vruntime)来排序。vruntime是一个反映进程已使用CPU时间和其nice值影响的综合指标,它使得nice值较高的进程(优先级较低)看起来“运行得更久”,从而在调度决策中处于不利地位。

时间片与nice值

时间片:每个进程被分配一个时间片来执行,时间片结束后,进程将被重新放回就绪队列。CFS动态调整时间片长度,以实现公平性,即所有进程在长时间尺度上获得大致相等的CPU时间。

nice值:范围从-20(最高优先级)到19(最低优先级)。nice值影响进程分配到的时间片长度,nice值越低的进程将获得更多CPU时间。

实时调度

除了CFS,Linux还支持实时调度器,服务于具有严格时间要求的实时进程。实时进程分为两个优先级类别(SCHED_FIFO和SCHED_RR),它们可以抢占普通(非实时)进程。SCHED_FIFO遵循先来先服务原则,而SCHED_RR则为每个进程分配固定时间片,时间片结束后即使进程还在运行也会被切换。

调度触发

时钟中断:是最常见的调度触发点,每当发生时钟中断(如每10毫秒),内核检查当前进程是否需要被替换,或者是否有更高优先级的进程等待执行。
系统调用/中断返回:在系统调用完成或硬件中断处理结束返回用户空间时,内核有机会进行调度检查。
自愿性上下文切换:进程主动放弃CPU(如调用sched_yield()),也会触发调度。

进程状态转换

在调度过程中,进程会在就绪态、运行态和阻塞态、等待状态等状态间转换。阻塞状态的进程(如等待I/O操作完成)不会被考虑在调度决策中,只有就绪态的进程才可能被选中运行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值