【编程语言】从内核到应用:操作系统与六大编程语言底层原理完全指南(上)
本文已拆分为上下两篇,此为上篇。 上篇聚焦操作系统内核与 C/C++ 底层原理,下篇聚焦 Java/Python/Go/Rust 运行时与并发模型。
📖 目录
-
上篇:操作系统内核与 C/C++ 底层原理
-
一、操作系统内核有哪些功能模块?运行完整流程是怎样的?
-
二、操作系统内核是 C 语言实现的,为什么 C 标准库还依赖操作系统?底层原理是什么?
-
三、C++ 和 C 语言的区别是什么?C++ 有哪些场景应用?
-
四、C 语言从编译到运行的完整流程是什么?动态链接的作用是什么?
-
-
下篇:Java/Python/Go/Rust 运行时与并发模型
-
五、Java 和 Python 底层是 C/C++ 实现的,底层原理是什么?
-
六、Go 和 Rust 都编译成机器码执行,和 C/C++ 有何区别?
-
七、Rust 如何实现安全?Go 为什么内存占用少、适合高并发?
-
八、操作系统内核各模块属于进程吗?第一个进程如何启动?进程如何切换?
-
👨💻 作者介绍
大家好,我是 CodeStats。
一个在底层技术上“考古”了四年的硬核爱好者,也是 WWAIC(全周项目 AI 编程) 范式的提出者和实践者。我曾手写过一个完整的 Java Web 框架(从 IoC 容器到嵌入式 Tomcat,代码全开源),也喜欢用通俗的语言拆解 CPU、JVM、操作系统的运行本质。
本文适合谁? 如果你是刚入门的开发者,本文能帮你建立从硬件到软件的全景认知;如果你是有经验的工程师,本文的底层视角或许能帮你解开一些长期困惑。无论你主攻哪门语言,理解底层原理都会让你走得更远。
一、操作系统内核有哪些功能模块?运行完整流程是怎样的?
1.1 内核的五大核心功能模块
Linux 内核根据核心功能划分为 5 大子系统:
| 子系统 | 核心职责 | 关键机制 |
|---|---|---|
| 进程调度(Process Scheduler) | 管理 CPU 资源,决定哪个进程何时使用 CPU | 调度策略、上下文切换、系统调用接口 |
| 内存管理(Memory Manager) | 管理物理内存,提供虚拟内存机制 | 页表映射、按需分页、Swap 交换 |
| 虚拟文件系统(VFS) | 将不同设备抽象为统一文件接口 | open/close/read/write,一切皆文件 |
| 网络子系统(Network) | 管理网络设备,实现网络协议栈 | TCP/IP、套接字、网卡驱动 |
| 进程间通信(IPC) | 实现进程之间的数据交换 | 管道、信号、共享内存、消息队列 |
1.2 内核的完整运行流程
从按下电源键到用户程序运行,内核经历了以下阶段:
阶段一:引导加载(Boot) —— BIOS/UEFI 从硬盘加载 Bootloader(如 GRUB),Bootloader 再将内核镜像(vmlinuz)加载到物理内存。
阶段二:内核初始化(Startup) —— 内核入口(startup_64)开启分页(设置 CR3 寄存器)、建立中断描述符表(IDT)、初始化内核栈,然后跳入 C 语言主函数 start_kernel()。
阶段三:核心子系统初始化 —— 依次初始化中断(trap_init)、调度器(sched_init)、内存管理(mm_init)、文件系统和网络。
阶段四:启动第一个进程 —— start_kernel() 最后调用 rest_init(),创建 PID=0 的 Idle 进程和 PID=1 的 init 进程(systemd)。
阶段五:进入用户态 —— init 进程启动 Shell 和系统服务,等待用户输入命令。
阶段六:运行用户程序 —— 用户执行 ./hello,Shell 调用 fork() + execve(),内核加载程序,进程开始运行。
关键认知:在 PID 1 启动之前,整个系统处于 “无进程” 状态——CPU 执行的是内核本身的机器码,这些代码不属于任何进程。
二、操作系统内核是 C 语言实现的,为什么 C 标准库还依赖操作系统?底层原理是什么?
2.1 核心答案:完全不矛盾
这个问题的关键在于区分 “C 语言语法” 和 “C 标准库(libc)”。
C 语言本身(语法)不依赖操作系统 —— int a = b + c;、if、for、指针运算,这些直接被编译器翻译成 MOV、ADD、JMP 等 CPU 指令,不需要任何操作系统帮忙。
C 标准库(libc)依赖操作系统 —— printf、malloc、fopen 这些函数要操作显示器、分配内存、读写文件,而这些操作必须通过操作系统内核(因为用户态程序不能直接执行 IN/OUT 等特权指令)。
2.2 内核如何用 C 语言但不依赖 libc?
Linux 内核编译时使用 GCC 的 -ffreestanding(独立环境编译) 选项:
-
禁用标准库(没有
stdio.h、stdlib.h) -
没有
malloc/free(内核自己实现kmalloc/kfree) -
没有
printf(内核自己实现printk) -
部分底层操作必须用内联汇编(如切换 CR3 寄存器、执行
IN/OUT指令)
c
// 内核源码示例:直接操作显存地址(不依赖任何库函数) char *video_memory = (char *)0xB8000; *video_memory = 'A'; // 纯 C 指针操作,直接写显存
2.3 系统调用的底层指令
当用户程序调用 printf 时,glibc 最终会执行 SYSCALL 指令(x86-64 架构),CPU 从用户态切换到内核态,由内核代为执行。
结论:内核用 C 语言的核心语法写,但不依赖 C 标准库。系统调用是内核提供给用户程序的接口,内核自己不需要“调用自己”。
三、C++ 和 C 语言的区别是什么?C++ 有哪些场景应用?
3.1 核心区别:不仅仅是“面向对象”
| 维度 | C 语言 | C++ |
|---|---|---|
| 编程范式 | 过程式 | 多范式(过程式 + OOP + 泛型) |
| 内存管理 | 手动 malloc/free |
RAII + 智能指针,也可手动 new/delete |
| 抽象代价 | 无抽象,直接映射汇编 | 零开销抽象——不用的功能不付出代价 |
| 函数调用 | 静态绑定 | 支持虚函数(动态绑定,有 vtable 开销) |
| 内存布局 | struct 只有数据 |
无虚函数的 class 内存布局与 C 的 struct 完全一致 |
3.2 C++ 的四大核心特性
① RAII(资源获取即初始化) —— 利用栈对象的析构函数自动管理资源生命周期。无论函数如何 return,资源都会被自动释放。
② 零开销抽象(Zero-overhead Abstraction) —— 模板在编译期展开,生成的机器码与手写 C 代码一样高效。
③ constexpr(编译期计算) —— 在编译阶段执行数学运算,生成的二进制只有结果,没有计算过程。
④ 编译期多态(CRTP) —— 通过模板在编译期确定调用关系,无运行时开销。
3.3 C++ 的典型应用场景
| 场景 | 为什么用 C++ |
|---|---|
| 游戏引擎(Unreal) | 帧率敏感,需要极致性能和底层硬件控制 |
| 浏览器内核(Chrome/Blink) | 海量 DOM 操作 + JIT 编译,需要高性能内存管理 |
| 高频交易系统 | 纳秒级延迟,需绕过内核直接操作网卡 |
| 数据库引擎(MySQL/LevelDB) | 需要精细控制内存和 I/O 缓存 |
| 嵌入式/自动驾驶 | 实时性要求高,需禁用异常和 RTTI 以精简二进制 |
四、C 语言从编译到运行的完整流程是什么?动态链接的作用是什么?
4.1 编译的四个阶段
C 语言的编译过程分为 4 个阶段:
| 阶段 | 命令 | 输入 → 输出 | 做了什么 |
|---|---|---|---|
| ① 预处理 | gcc -E |
.c → .i |
处理 #include、#define,宏展开,纯文本替换 |
| ② 编译 | gcc -S |
.i → .s |
词法/语法/语义分析,生成汇编代码 |
| ③ 汇编 | gcc -c |
.s → .o |
汇编代码转成二进制机器码(目标文件) |
| ④ 链接 | gcc(调用 ld) |
.o + 库 → 可执行文件 |
合并目标文件,解析符号引用 |
4.2 动态链接 vs 静态链接
静态链接:库的机器码被直接复制进最终的可执行文件。优点:独立运行;缺点:文件大、内存占用高、更新库需重新编译。
动态链接:可执行文件只记录“需要哪个库的哪个函数”,运行时由动态链接器(/lib64/ld-linux-x86-64.so.2)加载库并绑定地址。优点:节省磁盘/内存、库可独立更新;缺点:运行时需能找到依赖库。
动态链接的底层机制:可执行文件的 .interp 段记录了动态链接器的路径。内核加载可执行文件时,先启动动态链接器,由它加载 libc.so.6 等共享库,完成符号重定位后,才跳转到程序的 _start 入口。
4.3 运行时:从 ./hello 到进程
执行 ./hello 时,Shell 调用 execve 系统调用陷入内核,内核加载 ELF 文件,发现 .interp 段后启动动态链接器,动态链接器加载 libc.so.6 并重定位符号,最后跳转到 _start → __libc_start_main → main()。
📌 下篇预告
上篇我们聊完了操作系统内核的底层机制和 C/C++ 的编译运行原理。下篇将深入:
-
Java:JVM 如何解释执行 + JIT 编译?本地方法(JNI)如何调用 C/C++ 代码?
-
Python:CPython 解释器如何执行字节码?GIL 为什么是性能瓶颈?
-
Go:Goroutine 为什么极其轻量?GMP 调度模型如何实现高并发?
-
Rust:所有权和借用检查如何在编译期保证内存安全?
-
终极对比:六大语言从源码到 CPU 指令的完整路径对比
如果觉得本文有帮助,欢迎点赞、收藏、关注! 你的支持是我持续输出硬核技术内容的动力。有任何疑问或想深入了解的方向,欢迎在评论区留言讨论。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)