Linux下一个应用程序开始执行到main被调用之间经历了什么?

2 个回答

  • 白秀秀
    请优先考虑优先考虑你的人。
    在Linux系统上,一个Java应用程序(特别是Spring Boot应用程序)从启动到调用`main`函数之间会经历以下详细步骤: ### 1. 启动Java虚拟机(JVM) 当用户在终端运行一个Java应用程序时,例如执行 `java -jar myapp.jar`,以下步骤开始: #### 1.1 Shell解析命令 - 用户输入的命令会由shell解析,并找到`java`可执行文件。 #### 1.2 执行Java命令 - Shell调用`execve`系统调用来执行`java`可执行文件,并传递JAR文件和其他参数。 - `execve` 是 Unix 和 Linux 系统中的一个系统调用,用于执行一个新程序并替换当前进程。它接受三个参数:程序路径、参数数组和环境变量数组。调用 `execve` 后,当前进程的内存空间被新程序替换,并从新程序的入口点开始执行。如果调用成功,`execve` 不会返回;如果失败,则返回 -1 并设置 `errno` 错误码。典型应用场景包括 Shell 命令执行、守护进程启动和多进程编程中的子进程管理。 ### 2. Java虚拟机初始化 JVM开始执行,并进行以下初始化步骤: #### 2.1 加载和启动类加载器 - JVM加载Bootstrap类加载器,用于加载核心Java类库(如`java.lang`包)。 - 通过扩展类加载器和应用类加载器加载扩展库和应用程序类。 #### 2.2 内存分配 - JVM初始化内存区域,如堆、方法区、栈、程序计数器和本地方法栈。 #### 2.3 加载核心类 - JVM加载核心类,如`java.lang.Object`、`java.lang.String`等。 ### 3. 加载和执行Main类 JVM加载包含`main`方法的主类,并执行该方法。 #### 3.1 加载主类 - 应用类加载器找到并加载包含`main`方法的主类。例如,Spring Boot应用的主类可能是`com.example.MySpringBootApplication`。 #### 3.2 查找`main`方法 - JVM查找主类中的`public static void main(String[] args)`方法。 #### 3.3 执行`main`方法 - JVM调用`main`方法,开始执行Java应用程序的逻辑。 ### 4. Spring Boot启动流程 在Spring Boot应用程序中,`main`方法通常包含SpringApplication.run(...)来启动Spring框架。 #### 4.1 SpringApplication 初始化 - Spring Boot应用的`main`方法调用`SpringApplication.run()`,这个方法负责引导整个Spring应用。 ```java public static void main(String[] args) { SpringApplication.run(MySpringBootApplication.class, args); } ``` #### 4.2 创建Spring上下文 - SpringApplication创建一个Spring应用上下文(通常是AnnotationConfigServletWebServerApplicationContext或AnnotationConfigReactiveWebServerApplicationContext)。 #### 4.3 准备环境 - Spring Boot应用准备运行环境,包括系统环境变量、应用配置文件等。 #### 4.4 执行Spring Boot生命周期 - Spring Boot启动过程中,执行一系列的初始化步骤,如设置Banner、加载配置文件、准备应用上下文、刷新上下文等。 #### 4.5 扫描和注册Bean - Spring框架扫描项目中的组件,并注册所有标记为Spring Bean的类(如`@Component`、`@Service`、`@Repository`等)。 #### 4.6 启动内嵌服务器 - 如果是Web应用,Spring Boot会启动内嵌的服务器(如Tomcat、Jetty),并部署应用程序。 #### 4.7 完成启动 - Spring Boot完成所有初始化工作后,应用进入运行状态,等待处理HTTP请求或执行其他任务。 ### 5. 小结 综上所述,从启动Spring Boot应用到调用`main`函数之间的详细步骤如下: 1. **启动JVM**:通过`execve`系统调用启动JVM,初始化类加载器和内存区域。 2. **加载主类**:JVM加载包含`main`方法的主类,并找到`main`方法。 3. **执行`main`方法**:JVM调用`main`方法,开始执行Spring Boot应用程序。 4. **Spring Boot初始化**:SpringApplication初始化并创建应用上下文,准备环境,执行生命周期方法,扫描和注册Bean,启动内嵌服务器。 5. **应用进入运行状态**:Spring Boot完成启动并进入运行状态,等待处理请求或执行任务。 每个步骤都涉及多个底层操作,确保Spring Boot应用程序能够正确加载并运行。
    编辑于 2024-06-03 14:42 •IP属地 北京
    举报
    评论
    收藏

  • 王重阳
    不要和差点意思的人周旋。
    在Linux系统中,一个应用程序从开始执行到`main`函数被调用之间经历了多个复杂的步骤。以下是一个详细的流程概述: ### 1. 加载器(Loader) 当用户在终端或脚本中启动一个程序时,shell调用系统调用`execve`。这个系统调用将负责启动程序的执行。 ### 2. 内核态操作 内核处理`execve`系统调用并执行以下操作: - **可执行文件查找**:内核找到并验证要执行的可执行文件。 - **文件读取**:读取ELF(Executable and Linkable Format)可执行文件头,以获取程序入口点、程序头表等。 ### 3. 创建进程环境 - **创建新进程**:内核为新进程分配资源,如进程控制块(PCB)、内存空间等。 - **加载程序段**:根据ELF文件的程序头表,内核将可执行文件的各个段(如代码段、数据段)加载到新进程的地址空间。 - **设置堆栈**:为进程分配用户栈,并将命令行参数和环境变量拷贝到用户栈上。 - **动态链接器**:如果程序是动态链接的,内核会加载动态链接器(ld-linux.so),并将控制权移交给它。 ### 4. 动态链接器(Dynamic Linker/Loader) 如果程序是动态链接的,动态链接器将执行以下步骤: - **解析符号**:解析并加载所需的共享库,满足所有未定义的符号。 - **重定位**:修改程序中的地址引用,使其指向正确的内存地址。 - **初始化函数**:执行共享库中的初始化代码(如`.init`段中的代码),通常通过调用构造函数(constructor)。 ### 5. 用户态准备 在动态链接器完成工作后,程序的控制权会传递给程序的入口点(通常是 `_start` 函数)。 ### 6. C运行时(C Runtime, CRT)初始化 程序的入口点 `_start` 通常是由C运行时库提供的启动代码。它完成以下操作: - **堆栈和全局数据初始化**:设置堆栈指针,初始化全局变量和静态变量。 - **调用程序初始化函数**:执行编译器生成的初始化函数(如 `.init_array` 数组中的函数)。 - **准备参数**:将命令行参数和环境变量传递给 `main` 函数。 ### 7. 调用 `main` 完成所有初始化步骤后,CRT代码调用 `main` 函数,开始执行用户定义的程序逻辑。 ### 具体步骤的示例代码 下面是一些伪代码,描述了从 `_start` 到 `main` 的执行过程: ```c .section .text .globl _start _start: # 设置栈指针 # 初始化堆栈、全局变量等 call __libc_start_main __libc_start_main: # 初始化C运行时环境 # 调用全局和静态构造函数 call init_functions # 调用用户定义的main函数 call main # main函数返回后调用退出处理函数 call exit ``` ### 小结 综上所述,从Linux系统下启动一个应用程序到调用`main`函数,需要经过以下主要步骤: 1. 加载器处理`execve`系统调用,加载程序到内存。 2. 内核创建进程环境,加载程序段和动态链接器。 3. 动态链接器解析符号,加载共享库,执行初始化代码。 4. C运行时库初始化,设置堆栈和全局数据,准备`main`函数的参数。 5. 最后调用`main`函数,开始执行用户程序逻辑。 每个步骤都涉及复杂的底层操作,确保程序能够正确加载并运行。
    编辑于 2024-06-03 05:28 •IP属地 北京
    举报
    评论
    收藏1