这个问题在这里已经有了答案:




8年前关闭。






我不是在寻找 printf 函数的实现,但我想知道,当在 C 中调用 printf 时会发生什么?
所有事件都发生在软件和硬件级别。

这就是我的想法
PrintfCAll -> KernelModeOn -> SystemCallMade -> 数据放在某种类型的输出缓冲区 -> 输出要转储到某些 Controller 的缓冲区 -> Controller 将其转储到监视器上 -> 中断 CPU 说工作已完成。

我有多正确?
谢谢。

编辑:Unix 可以作为一个平台。说 ubuntu。
有人可以告诉我数据从哪里流出,监视器是否也有任何 Controller ?上面提出的时间表在多大程度上是正确的?

最佳答案

以下是一般性的描述和总结,基于一般的编程概念而不是任何特定的实现。

printf 的调用从一个普通的子程序调用开始;不涉及内核模式。在很大程度上,printf 是普通代码,可以用 C 编写。 printf 代码本身的大部分内容与解释格式字符串、将参数转换为要写入的字符串以及将这些字符串写入输出文件有关。大部分工作将通过 printf 调用的子例程完成,例如将数字(像 intfloat 这样的对象)转换为数字(表示数字的字符串)的子例程。
printf 也可能调用 malloc 或相关例程来为缓冲区获取内存,在该缓冲区中准备字符串。我将避免在此答案中描述 malloc 调用。

解释格式字符串、转换参数和准备要写入的字符串的所有工作都可以在 C 中完成,尽管高质量的库可能会使用各种特定于目标的优化,包括汇编语言,以提高速度或效率。

在某些时候,当 printf 有一个要打印的字符串时,它会调用一个例程将字符串写入 stdout 。这可能是 fwrite 或一些类似的子程序。为了讨论,我假设它是 fwrite

通常,流被缓冲。因此,当 printf 调用 fwrite 时, fwrite 会检查其缓冲区是否已满。如果来自 printf 的新字符串适合缓冲区,则 fwrite 仅将字符串添加到缓冲区并返回。如果缓冲区已满,则 fwrite 调用另一个例程将缓冲区内容实际写入文件。 (通常,这涉及用传入字符串的一部分填充缓冲区,将缓冲区写入文件 [并将缓冲区标记为空],然后将传入字符串的其余部分复制到新的空缓冲区中。)某些其他事情也可能根据情况触发写入缓冲区,例如检测传入字符串中的换行符。

假设要写入缓冲区, fwrite 调用系统例程 writewrite的面是一个库例程; fwrite 执行一个普通的子程序调用来调用 write 。系统例程会有一些是普通子例程的部分,但是,当它们需要完成基本工作时,会有某种系统调用指令(有时称为陷阱)。

当你执行一条系统调用指令时,处理器会做几件事情。它将处理器寄存器保存在指定位置。这包括描述用户进程状态的通用寄存器和特殊寄存器。然后处理器切换到内核模式,这通常涉及设置位以指示新的执行状态是特权的(允许更改特殊处理器寄存器,执行特殊指令等)并从某个其他位置加载寄存器,或将它们设置为已知值。特别是,程序计数器(处理器读取要执行的指令的位置)被设置为指向特定位置,在那里操作系统具有处理系统调用的代码。

现在处理器在内核模式下执行。通常,此时处理器的工作是尽快退出内核模式,以便它可以恢复进程之间的分时并为其他工作做好准备。此外,现代操作系统有很多层,因此很难准确说出此时会发生什么。

一种情况是系统调用处理程序(发生系统调用时调用的软件)读取用户进程保存的寄存器和内存,以确定进程请求什么。在每个系统上,都指定了一些将参数传递给系统调用的方法。例如,某个寄存器可能包含一个数字,表示请求是什么(0 表示写入,1 表示读取,2 表示获取当前时间,3 表示更改内存映射等),并且每个请求都会传入某些参数其他寄存器或内存中(一个寄存器可能包含内存中的地址,而另一个包含要写入的长度)。

因此,系统调用处理程序会找出正在发出的请求并将其分派(dispatch)给代码来处理。这可能涉及收集请求的参数并将它们形成对要完成的工作的描述,然后将该工作放入队列并离开系统调用处理程序。

虽然有工作要做,但操作系统可能不会返回到用户进程。正如我之前提到的,现代操作系统中有很多层。操作系统中有设备驱动程序、内核扩展、微内核、软件库等等。不管操作系统是如何组织的,在某个时候,它决定做系统调用所要求的工作。

在写入标准输出的情况下,工作被发送到“设备驱动程序”,这是处理“设备”工作的软件的名称。最初,设备是连接到系统的硬件。设备驱动程序会将要写入的数据复制到内存中的特殊位置,并向设备发出命令(使用特殊指令)从内存中读取该数据并将其发送到设备发送它的任何地方(终端、磁盘驱动器) , 任何)。设备驱动程序的另一部分是在工作完成时调用的例程。 (此调用类似于系统调用,但通常称为中断。)当工作完成时,设备驱动程序会将消息传递回操作系统的其他部分,最终有关系统调用结果的信息将写入用户进程的内存或寄存器,用户进程的执行将重新启动。

今天,许多“设备”是实现虚拟设备的软件。用户进程的标准输出可能是某种伪终端。由于该伪终端没有实际的硬件终端,它必须通过请求其他软件的帮助来处理写请求。

当伪终端是图形显示器上终端窗口的一部分时,有一些软件可以实现终端窗口。该软件接受写入标准输出的文本,决定应将其放置在窗口中的哪个位置,并调用其他软件将字符转换为窗口中像素的变化。也就是说,一些软件正在读取字符,在一些表格和其他数据中查找它们的描述(字体描述等),然后在图像缓冲区中绘制这些字符。

当图像缓冲区准备好时,会调用更多的软件将图像缓冲区写入显示器。同样,这涉及将数据传递给另一个设备驱动程序。最终,它到达一个实际的硬件设备,该设备获取数据并将其显示在显示器上。

总而言之,有一个巨大的事件链。数据在多个层中上下移动,可能涉及多个不同的用户进程和多个不同的设备驱动程序,以及许多软件库。很难对整个过程有一个全面的了解。一般来说,人们不会想一下子理解整个过程,而是要分别了解每个步骤。例如,在我的职业生涯中,有时我不得不处理系统调用指令的微小细节。但是,在考虑我的整个系统如何工作时,我会考虑更大级别的进程相互通信,而没有考虑这些通信如何工作的细节。

关于c - C 中 printf 的背后是什么?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/13657890/

10-17 00:12