0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

从进程启动是怎么一步步到main函数的

strongerHuang 来源:strongerHuang 作者:轩辕之风 2020-11-03 15:51 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

首先先划定一下这个问题的讨论范围:C/C++语言

这篇文章主要讨论的是操作系统层面上对于进程、线程的创建初始化等行为,而像PythonJava等基于解释器、虚拟机的语言,如何进入到main函数执行,这背后的路径则更长(包含了解释器和虚拟机内部的执行流程),以后有机会再讨论。所以这里就重点关注C/C++这类native语言的main函数是如何进入的。

本文会兼顾叙述Linux和Windows两个主要平台上的详细流程。

创建进程

第一步,创建进程。

在Linux上,我们要启动一个新的进程,一般通过fork+exec系列函数来实现,前者将当前进程“分叉”出一个孪生子进程,后者负责替换这个子进程的执行文件,来执行子进程的新程序文件。

这里的fork、exec系列函数,是操作系统提供给应用程序的API函数,在其内部最终都会通过系统调用,进入操作系统内核,通过内核中的进程管理机制,来完成一个进程的创建。

操作系统内核将负责进程的创建,主要有下面几个工作要做:

创建内核中用于描述进程的数据结构,在Linux上是task_struct

创建新进程的页目录、页表,用于构建新进程的内存地址空间

在Linux内核中,由于历史原因,Linux内核早期并没有线程的概念,而是用任务:task_struct来描述一个程序的执行实例:进程。

在内核中,一个任务对应就是一个task_struct,也就是一个进程,内核的调度单元也是一个个的个task_struct。

后来,多线程的概念兴起,Linux内核为了支持多线程技术,task_struct实际上表示的变成了一个线程,通过将多个task_struct合并为一组(通过该结构内部的组id字段)再来描述一个进程。因此,Linux上的线程,也称为轻量级进程。

系统调用fork的一个重要使命就是要去创建新进程的task_struct结构,创建完成后,进程就拥有了调度单元。随后将开始可以参与调度并有机会获得执行。

加载可执行文件

通过fork成功创建进程后,此时的子进程和父进程相当于一个细胞进行了有丝分裂,两个进程“几乎”是一模一样的。

而要想子进程执行新的程序,在子进程中还需要用到exec系列函数来实现对进程可执行程序的替换。

exec系列函数同样是系统调用的封装,通过调用它们,将进入内核sys_execve来执行真正的工作。

这个工作细节比较多,其中有一个重要的工作就是加载可执行文件到进程空间并对其进行分析,提取出可执行文件的入口地址。

我们使用C、C++等高级语言编写的代码,最终通过编译器会编译生成可执行文件,在Linux上,是ELF格式,在Windows上,称之为PE文件。

无论是ELF文件还是PE文件,在各自的文件头中,都记录了这个可执行文件的指令入口地址,它指示了程序该从哪里开始执行。

这个入口指向哪里,是我们的main函数吗?这里卖一个关子,先来解决在这之前的一个问题:进程创建后,是如何来到这个入口地址的?

不管在Windows还是Linux上,应用线程都会经常在用户空间和内核空间来回穿梭,这可能出现在以下几种情况发生时:

系统调用

中断

异常

从内核返回时,线程是如何知道自己从哪里进来的,该回到应用空间的哪里去继续执行呢?

答案是,在进入内核空间时,线程将自动保存上下文(其实就是一些寄存器的内容,比如指令寄存器EIP)到线程的堆栈上,记录自己从哪里来的,等到从内核返回时,再从堆栈上加载这些信息,回到原来的地方继续执行。

前面提到,子进程是通过sys_execve系统调用进入到内核中的,在后面完成可执行文件的分析后,拿到了ELF文件的入口地址,将会去修改原来保存在堆栈上的上下文信息,将EIP指向ELF文件的入口地址。这样等sys_execve系统调用结束时,返回到用户空间后,就能够直接转到新的程序入口开始执行代码。

所以,一个非常重要的特点是:exec系列函数正常情况下是不会返回的,一旦进入,完成使命后,执行流程就会转向新的可执行文件入口。

另外需要提一下的是,在Linux上,除了ELF文件,还支持一些其他格式的可执行文件,如MS-DOS、COFF

除了二进制的可执行文件,还支持shell脚本,这个情况下将会将脚本解释器程序作为入口来启动

从ELF入口到main函数

上面交代了,一个新的进程,是如何执行到可执行文件的入口地址的。

同时也留了一个问题,这个入口地址是什么?是我们的main函数吗?

这里有一个简单的C程序,运行起来后输出经典的hello world:

#include intmain(){ printf("hello,world! "); return0; }

通过gcc编译后,生成了一个ELF可执行文件,通过readelf指令,可以实现对ELF文件的分析,这里可以看到ELF文件的入口地址是0x400430:

随后,我们通过反汇编神器,IDA打开分析这个文件,看一下位于0x400430入口的地方是什么函数?

可以看到,入口地方是一个叫做_start的函数,并不是我们的main函数。

在_start的结尾,调用了__libc_start_main函数,而这个函数,位于libc.so中。

你可能疑惑,这个函数是哪里冒出来的,我们的代码中并没有用到它呢?

其实,在进入main函数之前,还有一个重要的工作要做,这就是:C/C++运行时库的初始化。上面的__libc_start_main就是在完成这一工作。

在通过GCC进行编译时,编译器将自动完成运行时库的链接,将我们的main函数封装起来,由它来调用。

glibc是开源的,我们可以在GitHub上找到这个项目的libc-start.c文件,一窥__libc_start_main的真面目,我们的main函数正是被它在调用。

完整流程

到这里,我们梳理了,从进程创建fork,到通过exec系列函数完成可执行文件的替换,再到执行流程进入到ELF文件的入口,再到我们的main函数的完整流程。

Windows上的一些区别

下面简单介绍下Windows上这一流程的一些差异。

首先是创建进程的环节,Windows系统将fork+exec两步合并了一步,通过CreateProcess系列函数一步到位,在其参数中指定子进程的可执行文件路径。

不同于Linux上进程和线程的边界模糊,在Windows操作系统上,内核是有明确的进程和线程概念定义,进程用EPROCESS结构表示,线程用ETHREAD结构表示。

所以在Windows上,进程相关的工作准备就绪后,还需要单独创建一个参与内核调度的执行单元,也就是进程中的第一个线程:主线程。当然,这个工作也封装在了CreateProcess系列函数中了。

新进程的主线程创建完成后,便开始参与系统调度了。主线程从哪里开始执行呢?内核在创建时就明确进行了指定:nt!KiThreadStartup,这是一个内核函数,线程启动后就从这里开始执行。

线程从这里启动后,再通过Windows的异步过程调用APC机制执行提前插入的APC,进而将执行流程引入应用层,去执行Windows进程应用程序的初始化工作,比如一些核心DLL文件的加载(Kernel32.dll、ntdll.dll)等等。

随后,再次通过APC机制,再转向去执行可执行文件的入口点。

这后面和Linux上的机制类似,同样没有直接到main函数,而是需要先进行C/C++运行时库的初始化,这之后经过运行时函数的包装,才最终来到我们的main函数。

下面是Windows上,从创建进程到我们的main函数的完整流程(高清大图:https://bbs.pediy.com/upload/attach/201604/501306_qz5f5hi1n3107kt.png):

现在你清楚,从进程启动是怎么一步步到你的main函数的了吗?

责任编辑:xj

原文标题:从创建进程到进入main函数,发生了什么?

文章出处:【微信公众号:strongerHuang】欢迎添加关注!文章转载请注明出处。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • WINDOWS
    +关注

    关注

    4

    文章

    3697

    浏览量

    93239
  • C语言
    +关注

    关注

    183

    文章

    7642

    浏览量

    144639
  • 函数
    +关注

    关注

    3

    文章

    4406

    浏览量

    66853
  • main
    +关注

    关注

    0

    文章

    38

    浏览量

    6556

原文标题:从创建进程到进入main函数,发生了什么?

文章出处:【微信号:strongerHuang,微信公众号:strongerHuang】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    迅为RK3588开发环境搭建“三曲”,轻松上手!

    迅为RK3588开发环境搭建“三曲”,轻松上手!
    的头像 发表于 12-01 11:51 641次阅读
    迅为RK3588开发环境搭建“三<b class='flag-5'>步</b>曲”,<b class='flag-5'>从</b>零<b class='flag-5'>到</b><b class='flag-5'>一</b>轻松上手!

    矢量字库实操指南:零基础高手进阶全解析!

    的全流程,带你一步步跨越入门门槛,最终实现对矢量字库的灵活运用,完成“会用”“精通”的进阶。 、演示功能概述   AirFONT_1000 是 SPI 接口支持 16-192 矢
    的头像 发表于 11-24 13:15 1366次阅读
    矢量字库实操指南:<b class='flag-5'>从</b>零基础<b class='flag-5'>到</b>高手进阶全解析!

    一步步完成安路飞龙 DR1M90 Linux 系统固化:启动卡制作 + eMMC 固化

    本手册由创龙科技研发,针对安路飞龙 DR1M90,详述 Linux 系统启动卡制作(含工具包使用、PV 工具安装等)与 eMMC 固化步骤,说明启动卡和 eMMC 分区结构,提供 eMMC 剩余空间
    的头像 发表于 11-21 10:48 5166次阅读
    <b class='flag-5'>一步步</b>完成安路飞龙 DR1M90 Linux 系统固化:<b class='flag-5'>启动</b>卡制作 + eMMC 固化

    “UPS不间断电源罢工不开机?三专业排查,市电电池维修全解析!”

    服务器宕机、业务中断——当UPS电源突然罢工不开机,每秒都在吞噬企业的金钱与信誉。别慌,这篇指南将带你市电主板,一步步排查故障根源,让设备快速恢复运行。
    的头像 发表于 10-15 15:16 832次阅读
    “UPS不间断电源罢工不开机?三<b class='flag-5'>步</b>专业排查,<b class='flag-5'>从</b>市电<b class='flag-5'>到</b>电池维修全解析!”

    12 个关键节点!文看懂 PCBA 如何实现零缺陷

    想知道 PCBA 加工怎么保证质量零缺陷?关键在 12 个核心管控节点,一步步看:​
    的头像 发表于 09-15 15:14 526次阅读
    12 个关键节点!<b class='flag-5'>一</b>文看懂 PCBA 如何实现零缺陷

    中国智能焊接机器人冲刺百亿蓝海

    随着AI技术逐步渗透千行百业,焊接这门古老的工艺,一步步被插上“智能化”的翅膀——被动编程的工业机械臂,能够自主识别焊缝的眼睛和大脑,迎来
    的头像 发表于 09-11 14:21 1340次阅读
    中国智能焊接机器人冲刺百亿蓝海

    一步步教你正确的电磁流量计安装

    看似微小的失误,却会直接导致系统运行异常。今天简单出期电磁流量计安装“避坑”指南,帮你根源避开这些坑。 首先了解下电磁流量计,电磁流量计是应用法拉第电磁感应定律,根据导电流体通过外夹磁场时感生的电动势来
    的头像 发表于 09-06 10:38 3310次阅读
    <b class='flag-5'>一步步</b>教你正确的电磁流量计安装

    淘宝API实时竞品监控,市场策略快人一步

    在当今激烈的电商竞争中,实时掌握竞品动态是企业制胜的关键。淘宝作为中国最大的电商平台,其开放API为商家提供了强大的工具,帮助实现实时竞品监控,从而优化市场策略,抢占先机。本文将一步步解析如何利用
    的头像 发表于 08-06 14:38 546次阅读

    智驾安全,发展一步了?

    智驾安全,发展一步了?
    的头像 发表于 06-10 11:28 540次阅读

    解锁树莓派集群:一步步打造你的超级计算阵列!

    树莓派集群简介树莓派集群是由多台联网的树莓派计算机组成的网络,它们作为个统、协调的单元协同工作。通过连接多台树莓派,用户可以创建个低成本的并行计算环境,能够处理基础模拟和网页托
    的头像 发表于 04-25 16:17 1953次阅读
    解锁树莓派集群:<b class='flag-5'>一步步</b>打造你的超级计算阵列!

    CoT 数据集如何让大模型学会一步一步思考?

    目前,大模型的回答路径基本遵循 input-output 的方式,在面对复杂任务时表现不佳。反之,人类会遵循套有条理的思维流程,逐步推理得出正确答案。这种差异促使人们深入思考:如何才能让大模型“智能涌现”,学会像人类样“一步
    的头像 发表于 04-24 16:51 1058次阅读
    CoT 数据集如何让大模型学会<b class='flag-5'>一步</b><b class='flag-5'>一步</b>思考?

    【迅为电子】一步步教你完成iTOP-RK3568 EDP屏幕适配

    【迅为电子】一步步教你完成iTOP-RK3568 EDP屏幕适配
    的头像 发表于 04-23 15:08 1634次阅读
    【迅为电子】<b class='flag-5'>一步步</b>教你完成iTOP-RK3568 EDP屏幕适配

    ST EDGE AI云服务最后一步无法下载工程是怎么回事?

    ST EDGE AI云服务我选择使用ST提供的模型,使用cube ai 9.0.0,选择STM32板卡。之后就按照文档一步一步操作,基准测试也能运行的结果(说明云端是生成工程并编译下载到开发板中
    发表于 03-13 08:17

    如果需要将DDC112U设置为非连续模式工作,应该如何一步一步正确地设置芯片?

    或状态8,如果没有CONV的切换发生,是否会直停留在上电时的这个状态?如果需要将DDC112U设置为非连续模式工作,应该如何一步一步正确地设置芯片?
    发表于 01-09 07:43

    沙子变芯片,一步步带你走进高科技的微观世界

    在科技飞速发展的今天,芯片作为现代科技的核心元器件,其制造过程复杂且充满挑战。芯片不仅推动了信息技术、人工智能、物联网等领域的进步,还成为衡量个国家科技实力的重要指标。然而,芯片制造并非易事,沙子芯片的每
    的头像 发表于 12-19 10:44 1081次阅读
    沙子变芯片,<b class='flag-5'>一步步</b>带你走进高科技的微观世界