进程启动时,main 函数是如何被找到的?

Linux中一个进程是如何被启动起来的?

  • 一、进程是怎么启动的?
  • 二、进程内存空间分段
  • 三、进程的入口函数
  • 四、总结

一、进程是怎么启动的?

当一个程序被执行时,怎么看出进程的运行呢?一个进程是怎么启动的?为什么我们在命令行执行程序时敲回车这个进程就启动了呢?
在这里插入图片描述

可以使strace命令看进程的启动。strace 可以跟踪一个进程执行期间的所有系统调用,并显示系统调用及其参数和返回值。它本质上是扮演了一个“中间人”角色,截获进程发出的系统调用请求,并记录其执行过程。

简单来说,strace 就像一个“监视器”,可以让你看到一个进程在后台默默地与操作系统进行的各种交互。了解 strace 的用法,可以参考其官方文档: man stracestrace 命令输出的信息量比较大,建议使用管道或重定向将输出保存到文件中,方便分析。

因此,使用strace看我们的进程是怎么启动的:

execve("./server", ["./server"], 0x7ffee9ec6a10 /* 36 vars */) = 0
brk(NULL)                               = 0x5566b9ac9000
arch_prctl(0x3001 /* ARCH_??? */, 0x7fff025c9720) = -1 EINVAL (Invalid argument)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe2a66f6000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=16915, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 16915, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fe2a66f1000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libstdc++.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\0\0\0\0\0\0\0"..., 832) = 832
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=2260296, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 2275520, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fe2a64c5000
mprotect(0x7fe2a655f000, 1576960, PROT_NONE) = 0
mmap(0x7fe2a655f000, 1118208, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x9a000) = 0x7fe2a655f000
mmap(0x7fe2a6670000, 454656, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1ab000) = 0x7fe2a6670000
mmap(0x7fe2a66e0000, 57344, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x21a000) = 0x7fe2a66e0000
mmap(0x7fe2a66ee000, 10432, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fe2a66ee000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\237\2\0\0\0\0\0"..., 832) = 832
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64(3, "\4\0\0\0 \0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0"..., 48, 848) = 48
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0I\17\357\204\3$\f\221\2039x\324\224\323\236S"..., 68, 896) = 68
newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=2220400, ...}, AT_EMPTY_PATH) = 0
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
mmap(NULL, 2264656, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fe2a629c000
mprotect(0x7fe2a62c4000, 2023424, PROT_NONE) = 0
mmap(0x7fe2a62c4000, 1658880, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x28000) = 0x7fe2a62c4000
mmap(0x7fe2a6459000, 360448, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1bd000) = 0x7fe2a6459000
mmap(0x7fe2a64b2000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x215000) = 0x7fe2a64b2000
mmap(0x7fe2a64b8000, 52816, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fe2a64b8000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libm.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\0\0\0\0\0\0\0"..., 832) = 832
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=940560, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 942344, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fe2a61b5000
mmap(0x7fe2a61c3000, 507904, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xe000) = 0x7fe2a61c3000
mmap(0x7fe2a623f000, 372736, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x8a000) = 0x7fe2a623f000
mmap(0x7fe2a629a000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xe4000) = 0x7fe2a629a000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libgcc_s.so.1", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\0\0\0\0\0\0\0"..., 832) = 832
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=125488, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 127720, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fe2a6195000
mmap(0x7fe2a6198000, 94208, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x3000) = 0x7fe2a6198000
mmap(0x7fe2a61af000, 16384, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1a000) = 0x7fe2a61af000
mmap(0x7fe2a61b3000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1d000) = 0x7fe2a61b3000
close(3)                                = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe2a6193000
arch_prctl(ARCH_SET_FS, 0x7fe2a61943c0) = 0
set_tid_address(0x7fe2a6194690)         = 41573
set_robust_list(0x7fe2a61946a0, 24)     = 0
rseq(0x7fe2a6194d60, 0x20, 0, 0x53053053) = 0
mprotect(0x7fe2a64b2000, 16384, PROT_READ) = 0
mprotect(0x7fe2a61b3000, 4096, PROT_READ) = 0
mprotect(0x7fe2a629a000, 4096, PROT_READ) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe2a6191000
mprotect(0x7fe2a66e0000, 45056, PROT_READ) = 0
mprotect(0x5566b8336000, 4096, PROT_READ) = 0
mprotect(0x7fe2a6730000, 8192, PROT_READ) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
munmap(0x7fe2a66f1000, 16915)           = 0
getrandom("\xaa\xa1\x40\x73\x3f\x72\xa2\x6e", 8, GRND_NONBLOCK) = 8
brk(NULL)                               = 0x5566b9ac9000
brk(0x5566b9aea000)                     = 0x5566b9aea000
futex(0x7fe2a66ee77c, FUTEX_WAKE_PRIVATE, 2147483647) = 0
socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3
exit_group(0)                           = ?
+++ exited with 0 +++

这里可以看到一个这样一段打印:

execve("./server", ["./server"], 0x7ffee9ec6a10 /* 36 vars */) = 0

执行的时候执行了 “./server” 这个进程,后面的0x7ffee9ec6a10 /* 36 vars */是对应的命令行参数或程序的环境变量列表,这里包含 36 个变量。

brk(NULL) = 0x5566b9ac9000调整程序数据段的大小并返回当前数据段的地址。mmap内存映射将文件或设备映射到进程的地址空间。

所以,从这个系统调用可以清楚的分析出,进程在启动的时候是由 shell 命令启动的,shell也是一个进程,这个进程里通过execve()函数执行了我们的 “./server” 程序。

调用
shell命令进程
execve函数
我们的进程

二、进程内存空间分段

在了解一个进程是如何执行之前,需要清楚进程的内存空间分区。进程的内存分配包含:堆 (heap)、栈 (stark)、全局静态区(.bss、.data)、文字常量区(.rodata)、代码段。

在这里插入图片描述
堆栈区在编译时是不会给他们开辟并占用文件空间的,堆栈段是在进程执行过程中动态开辟的内存空间。

程序在编译时, 链接器会寻找一个叫做 _start 的函数,这个函数是程序真正的入口点,它通常位于 C 标准库中。_start 函数会调用 main() 函数,并传递程序的命令行参数和环境变量。

进程启动时,操作系统通过 execve 系统调用加载可执行文件,并从 _start 函数开始执行。_start 函数会找到 main 函数的地址,并将控制权交给它,从而执行 main 函数。

三、进程的入口函数

为什么启动函数是main()函数呢?

因为在 C 语言的发展初期,main() 函数被选定为程序的入口点,并被广泛接受为标准。 编译器会将 main() 函数编译成可执行文件的一部分,链接器会将 main() 函数作为程序的入口点,并根据 main() 函数的地址设置程序执行的起始位置。

在执行的时候,进程中有一个叫做代码段的区域。进程执行的时候,编译器会教它从代码段里面找到main函数,而且命名规则是确定的,所以这个函数的入口就叫做main函数。注意,也就是说应用程序的入口函数是main函数,在Linux内核(Kernel)中是没有main函数的,只有应用程序的入口函数是规定从main函数开始执行,对于操作系统而言,每个进程是都是它的一个模块,只是这些进程的入口函数是main函数。很多的模块开发(比如 NGINX的一个模块或者Linux kernel的一个模块),其入口函数都是规定好的。

虽然 main 函数是 C/C++ 程序的默认入口点,但实际上可以通过一些技术手段来改变入口点,例如使用 __attribute__((constructor))__attribute__((destructor)) 关键字来定义在 main 函数之前或之后执行的函数。 但是,这通常是比较高级的应用场景,在大多数情况下,main 函数仍然是程序的入口点。

四、总结

进程启动时操作系统通过 execve 系统调用加载并执行程序,以及使用 strace 工具跟踪系统调用的方法。进程内存空间的划分,包括代码段、数据段、堆、栈、环境变量段和命令行参数段,以及它们各自的特点和用途。 main 函数作为进程入口函数的原因,以及编译器和链接器如何找到并执行 main 函数。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/912785.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

关于 el-table 的合计行问题

目录 一.自定义合计行 二.合计行不展示,只有缩放/变大窗口或者F12弹出后台时才展示 三.合计行出现了表格滚动条下方 四.合计行整体样式的修改 五.合计行单元格样式修改 1.css 2.jsx方式 六.合计行单元格合并 一.自定义合计行 通过 show-summary 属性开启合计…

十三:java web(5)-- Spring数据持久层

目录 Spring 数据持久层 1. Spring 与 JDBC 1.1 使用 Spring 管理数据库连接 1.1.2 Apache Commons DBCP 基于配置文件xml 使用 1.1.3 Apache Commons DBCP 基于配置类使用 1.1.4 HikariCP 基于配置文件xml 使用 推荐使用 Spring Boot 默认连接池 1.1.5 HikariCP 基于配置…

初学者指南:用例图——开启您的软件工程之旅

目录 背景: 基本组成: 关联(Assciation): 包含(Include): 扩展(Extend): 泛化(Inheritance): 完整银行…

基于单片机洗衣机控制器的设计(论文+源码)

1需求分析 在智能洗衣机系统设计中,考虑到洗衣机在实际应用过程中,需要满足用户对于不同衣物清洁、消毒的应用要求,对设计功能进行分析,具体如下: 通过按键实现洗衣机不同工作模式的切换,包括标准模式&am…

qt QFontDialog详解

1、概述 QFontDialog 是 Qt 框架中的一个对话框类,用于选择字体。它提供了一个可视化的界面,允许用户选择所需的字体以及相关的属性,如字体样式、大小、粗细等。用户可以通过对话框中的选项进行选择,并实时预览所选字体的效果。Q…

Python学习从0到1 day27 第三阶段 Spark ③ 数据计算 Ⅱ

目录 一、Filter方法 功能 语法 代码 总结 filter算子 二、distinct方法 功能 语法 代码 总结 distinct算子 三、SortBy方法 功能 语法 代码 总结 sortBy算子 四、数据计算练习 需求: 解答 总结 去重函数: 过滤函数: 转换函数: 排…

今天,智谱「新清影」上线,率先进入有声视频生成时代!还要继续开源宠粉

来,你先把手机音量打开,然后去“听”下面一段视频: 你是不是一脸懵逼?不知道我想表达什么? 视频是AI生成的并不奇怪,但你可能没法相信,这个视频的音效,也是AI生成的。 火车鸣笛 你…

「Mac畅玩鸿蒙与硬件31」UI互动应用篇8 - 自定义评分星级组件

本篇将带你实现一个自定义评分星级组件,用户可以通过点击星星进行评分,并实时显示评分结果。为了让界面更具吸引力,我们还将添加一只小猫图片作为评分的背景装饰。 关键词 UI互动应用评分系统自定义星级组件状态管理用户交互 一、功能说明 …

pdf转excel;pdf中表格提取

一、问题描述 在工作中或多或少会遇到:需要将某份pdf中的表格数据提取出来,以便能够“修改使用”数据 可将pdf中的表格提取出来,解决办法还有点复杂 尤其涉及“pdf中表格不是标准的单元格”的时候,提取数据到excel不太容易 比…

IT架构管理

目录 总则 IT架构管理目的 明确组织与职责 IT架构管理旨在桥接技术实施与业务需求之间的鸿沟,通过深入理解业务战略和技术能力,推动技术创新以支持业务增长,实现技术投资的最大价值。 设定目标与范围 IT架构管理的首要目的是确立清晰的组织…

小红书图文矩阵的运营策略与引流技巧解析

内容概要 小红书图文矩阵是一种高效的内容运营方式,能够帮助品牌在竞争激烈的环境中脱颖而出。通过构建矩阵账号,品牌可以实现多维度的内容覆盖,创造出丰富而立体的用户体验。为什么要做图文矩阵?首先,这种方式能够提…

python中常见的8种数据结构之一元组

元组(tuple)是Python中常见的数据结构之一,它是一个有序、不可变的序列。元组使用圆括号来表示,可以包含任意类型的元素,包括数字、字符串、列表等。元组的元素可以通过索引访问,但是不能修改。 下面是一些…

计算机毕业设计Python+大模型动漫推荐系统 动漫视频推荐系统 机器学习 协同过滤推荐算法 bilibili动漫爬虫 数据可视化 数据分析 大数据毕业设计

作者简介:Java领域优质创作者、CSDN博客专家 、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、多年校企合作经验,被多个学校常年聘为校外企业导师,指导学生毕业设计并参与学生毕业答辩指导,…

【leetcode练习·二叉树】用「分解问题」思维解题 I

本文参考labuladong算法笔记[【强化练习】用「分解问题」思维解题 I | labuladong 的算法笔记] 105. 从前序与中序遍历序列构造二叉树 | 力扣 | LeetCode | 给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵…

产品经理晋级-Axure中继器制作美观表格

这里的效果,步骤如下: 点击中继器,输入表格信息;在中继器中创建表格内容,把你想要的效果制作在中继器中,表头有几个表格,这边就对应多少个。 按照视频的过程把中继器双击后-样式中的文本内容&am…

防火墙|WAF|漏洞|网络安全

防火墙|WAF|漏洞|网络安全 防火墙 根据内容分析数据包: 1、源IP和目的IP地址 2、有效负载中的内容。 3、数据包协议(例如,连接是否使用 TCP/IP 协议)。 4、应用协议(HTTP、Telnet、FTP、DNS、SSH 等)。 5…

【Linux系统编程】第四十四弹---从TID到线程封装:全面掌握线程管理的核心技巧

✨个人主页: 熬夜学编程的小林 💗系列专栏: 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】 目录 1、tid是什么 1.1、理解库 1.2、理解tid 1.3、tid中线程局部存储 2、封装线程 2.1、基本结构 2.2、函数实现 2.3、使用…

医学图像算法之基于Unet的视网膜血管分割

第一步:准备数据 视网膜血管分割数据比较少,但效果好,总共40张 第二步:搭建模型 UNet主要贡献是在U型结构上,该结构可以使它使用更少的训练图片的同时,且分割的准确度也不会差,UNet的网络结构…

ARM死机(HardFault)调试技巧详解(栈回溯,不破坏现场)

目录 Keil调试技巧: 一.不破坏现场连接仿真器与进入debug 二.栈回溯 死机调试示例 J-Link调试方法 示例:空指针异常 不能连接烧录器或者读取内存怎么办? 在日常开发中,经常会遇到单片机卡死机等问题,经常很难定…

nodejs 020: React语法规则 props和state

props和state 在 React 中,props 和 state 是管理数据流的两种核心机制。理解它们之间的区别和用途是构建 React 应用程序的基础。 一、props 和 state的区别 特性propsstate定义方式由父组件传递给子组件的数据组件内部管理的本地数据是否可修改不可变&#xff…