返回资讯中心
外部精选
软件工程
#开发者实时流

开发者实现不依赖标准C库的Linux C程序开发

来源标题:C development for Linux without a standard library

文章探讨了在不使用标准C库、仅依赖Linux内核的情况下创建C应用程序所面临的挑战。作者通过实现一个名为shnolib的简单Linux shell来展示标准C库扮演的各种角色,以及如何弥补其缺失。适用场景包括无C库的嵌入式平台、需要极小内存占用和亚毫秒启动时间的程序,以及可在同架构Linux机器上无依赖分发的可执行文件。

Hacker News 最新andsoitis2026/11/8 · 今天30 分钟阅读

以下正文由 AI 翻译自 Hacker News 最新(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。

为什么??? 本文探讨在创建不依赖标准C库、甚至除Linux内核外不依赖任何其他组件的C应用程序时所涉及的一些挑战。考虑到目前已有许多功能完备且维护良好的标准C库,人们当然有理由问:你为什么会想做这种事?我能想到几种可能的情况。 你正在开发某种高度优化的嵌入式平台,而该平台实际上并没有可用的C库。 你有其他原因需要创建一个内存占用极小、启动时间不到一毫秒的程序。 你想编写一个可以以可执行文件形式分发到任何同架构Linux机器上的工具。也就是说,这个可执行文件完全不依赖其他组件。 你想了解事物真正是如何运作的。 我将通过实现一个非常简单的Linux shell——shnolib——来说明C标准库所扮演的各种角色。这个程序本身并没有什么特别出众甚至有趣的地方,只是它迫使我们去思考标准库到底做了什么,以及我们如何弥补它的缺失。 shnolib并不是一个令人印象深刻的shell——它只是提示用户输入一行文本,解析它,在$PATH中搜索匹配的程序,然后执行它。它还可以逐行执行一个命令文件,以演示缓冲输入的原理(下文会进一步说明)。 shnolib有一个稍微有趣的特点,可以通过对它运行top -p看到: PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 3083 kevin 20 0 184 8 0 S 0.0 0.0 0:00.00 shnolib 如果不清楚的话,那是映射的8千字节物理内存,而总地址空间为184 kB。 在本文中,我会多次提到shnolib的实现;源代码可在github上获取。 注意: 本文主要描述AMD64架构,尽管实际上只有大约30行汇编代码是架构相关的。AMD64可能是最不需要本文所述技术的平台;但它无处不在,使实验变得容易。源代码还包括一个用于ARMv7的汇编模块,例如Raspberry Pi。 标准C库实际做了什么 大多数C开发者不太思考标准库;许多人对它的作用只有一个模糊的概念。实际上,只有在嵌入式系统或像Android这样的非标准Linux变体中工作时,我们才真正需要思考标准库。 如果你使用GCC,那么有99.9%的概率你在使用GNU标准库glibc。它是Linux中如此基础的一部分,以至于几乎被视为内核的一部分。很少能找到未安装glibc的桌面或服务器Linux,而且几乎其他所有软件都在使用它。 但glibc并不是唯一的标准库,即使对于Linux也是如此。Android默认不使用它——它有自己的标准C库,称为Bionic。Bionic实现了glibc功能的一个子集,但以适合Android平台的方式实现。许多嵌入式Linux系统使用ucLibC,它试图比glibc小得多,同时提供大部分相同的基本功能。 但标准C库实际做了什么?以下事情中的一些或全部,也许还有其他事情: 它充当内核启动程序的入口点,设置适合C程序的内存环境,并提供命令行参数; 它提供函数来简化与内核系统调用(syscalls)的集成; 它为I/O提供缓冲区,以减少应用程序进行的(缓慢的)内核调用次数; 它支持许多编程构造,比如可变长度参数列表; 它提供一个有用的函数库,用于字符串操作、数据格式化、错误报告等; 它实现CPU不支持的基本算术运算。 这些都是大多数C程序员——事实上,大多数任何语言的程序员——完全视为理所当然的特性。你可能直到它消失时,才意识到标准库做了多少事情。 shnolib所需的“有用函数”主要集中在操作文本字符串上。程序需要一种方法来解析命令行、对$PATH进行标记化,并构造文件名。它需要一种从系统调用错误码生成文本的方法。它实际上并不需要缓冲输入和输出,但实现它是有用的练习。 在接下来的各节中,我将参考shnolib示例,演示上述每个标准库函数。然而,在深入细节之前,我们需要简要离题谈一下C语言调用约定。 “调用约定”是向函数传递参数以及从函数返回值的规范。开发者很少需要担心这个——编译器会生成必要的代码。实际上,只有在编写从汇编调用C或反之亦然的代码时,我们才必须面对它。 在C代码中,参数传递主要有两种方式。 调用函数将参数压入栈中,通常按相反顺序,以便第一个参数位于栈顶。被调用函数从栈中读取它们。 调用函数通过CPU寄存器将参数传递给被调用函数。 这些方法经常组合使用。通过寄存器传递参数比使用栈快得多,但CPU的寄存器数量有限。调用约定需要定义哪些类型的寄存器用于哪种类型的数据。 作为记录,在AMD64上,GCC按以下顺序使用这些寄存器来传递整数和指针数据:RDI、RSI、RDX、RCX、R8和R9。如果应用程序需要超过六个参数,剩余的参数

(原文较长,此处译出前 6000 字,完整内容请点「打开原文」。)

正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。

打开原文
去社区讨论 →