C中的printf背后是什么? [英] What goes behind printf in C?

查看:14
本文介绍了C中的printf背后是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

可能重复:
了解printf的硬件

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

I am not looking for the implementation of printf function but I want to know, what all happens when the call to printf is made in C? What all activities take place at the software and hardware level.

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

This is what I think PrintfCAll -> KernelModeOn -> SystemCallMade -> Data Put On Output Buffer Of Some Sort -> Output To Be Dumped On Some Controller's Buffer -> Controller Dumps It On The Monitor -> Interrupts CPU saying That work is done.

我有多正确?谢谢.

Unix 可以作为一个平台.说ubuntu.有人能告诉我数据从哪里流出,还有监视器的控制器吗?上面的时间线在多大程度上是正确的?

Unix Can be taken as a platform. say ubuntu. And can someone tell, me where the data flows out from, and is there any controller for the monitor too? and to what extent is the timeline presented above is correct?

推荐答案

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

The following is a general description and summary, based on programming concepts in general rather than any specific implementation.

printf 的调用以普通的子程序调用开始;不涉及内核模式.在很大程度上,printf 是普通代码,可以用 C 编写.printf 代码本身的大部分涉及解释格式字符串,将参数转换为字符串被写入,并将这些字符串写入输出文件.大部分工作将通过 printf 调用的子例程完成,例如将数字(像 intfloat 等对象)转换为数字的子例程(表示数字的字符串).

The call to printf starts with an ordinary subroutine call; kernel mode is not involved. In large part, printf is ordinary code and may be written in C. The bulk of the printf code itself is concerned with interpreting the format string, converting the arguments to strings to be written, and writing those strings to the output file. Much of this work will be done through subroutines that printf calls, such as subroutines to convert numbers (objects like int or float) to numerals (strings of characters that represent the numbers).

printf 也可能调用 malloc 或相关例程来为准备字符串的缓冲区获取内存.我将避免在此答案中描述 malloc 调用.

printf also likely calls malloc or a related routine to get memory for a buffer where it prepares the strings. I will refrain from describing the malloc call in this answer.

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

All of the work of interpreting the format string, converting the arguments, and preparing strings to be written can be done in C, although high-quality libraries may use a variety of target-specific optimizations, including assembly language, for speed or efficiency.

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

At some point, when printf has a string to print, it will call a routine to write the string to stdout. This may be fwrite or some similar subroutine. For discussion, I will suppose it is fwrite.

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

Usually, streams are buffered. So, when printf calls fwrite, fwrite checks how full its buffer is. If the new string from printf fits into the buffer, fwrite merely adds the string to the buffer and returns. If the buffer is full, then fwrite calls another routine to actually write the buffer contents to a file. (Typically, this involves filling the buffer with part of the incoming string, writing the buffer to a file [and marking the buffer empty], and then copying the rest of the incoming string into the newly empty buffer.) Certain other things might also trigger writing the buffer, such as detecting a newline character in the incoming string, depending on circumstances.

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

Let’s say that, to write the buffer, fwrite calls the system routine write. The face of write is a library routine; fwrite performs an ordinary subroutine call to call write. System routines will have some portion that is an ordinary subroutine, but, when they need to do the nitty-gritty work, there is some sort of system-call instruction (sometimes called a trap).

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

When you execute a system-call instruction, the processor does several things. It saves processor registers in specified locations. This includes both general registers and special registers that describe the state of the user process. Then the processor switches to kernel mode, which typically involves setting bits to indicate the new execution state is privileged (allowed to change special processor registers, execute special instructions, et cetera) and loading the registers from some other location, or setting them to known values. In particular, the program counter (the location where the processor reads instructions to execute) is set to point to a particular place, where the operating system has code to handle system calls.

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

Now the processor is executing in kernel mode. Usually, the job of the processor at this point is to get out of kernel mode as quickly as possible, so that it can resume time-sharing between processes and being ready for other work. Additionally, there are many layers to modern operating systems, so it is difficult to say precisely what happens at this point.

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

One scenario is that the system call handler (the software that is called when a system call occurs) reads the saved registers and memory of the user process to determine what the process asked for. On each system, some method of passing parameters to a system call is specified. For example, a certain register might contain a number that indicates what the request is (0 means write, 1 means read, 2 means get current time, 3 means change memory map, et cetera), and each request will have certain parameters passed in other registers or in memory (one register might contain an address in memory, while another contains the length to write).

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

So, the system call handler figures out what request is being made and dispatches to code to handle that. This might involve collecting parameters for the request and forming them into a description of the work to be done, then putting that work on a queue and leaving the system call handler.

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

While there is work to be done, the operating system probably does not return to a user process. As I mentioned before, there are many layers in modern operating systems. There are device drivers, kernel extensions, microkernels, libraries of software within the operating system, and more. However the operating system is organized, at some time, it decides to do the work requested by the system call.

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

In the case of a write to standard output, the work is sent to a "device driver", which is a name for software that handles the work for a "device". Originally, devices were pieces of hardware connected to the system. A device driver would copy the data to be written to a special place in memory and issue a command to the device (using special instructions) to read that data from memory and send it to wherever the device sends it (a terminal, a disk drive, whatever). Another part of the device driver would be a routine that is called when the work is done. (This call is similar to a system call but is usually called an interrupt.) When the work is done, the device driver would pass a message back to other parts of the operating system, and eventually information about the result of the system call would be written into the memory or registers of a user process, and execution of the user process would be restarted.

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

Today, many "devices" are software that implement virtual devices. The standard output of a user process is likely some sort of pseudo-terminal. Since that pseudo-terminal has no actual hardware terminal, it has to handle write requests by asking other software to help.

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

When the pseudo-terminal is part of a terminal window on a graphic display, there is some software that implements the terminal window. That software accepts text being written to standard output, decides where in the window it should be placed, and calls other software to convert the characters into changes in pixels in the window. That is, some software is reading the characters, looking up descriptions of them in some tables and other data (descriptions of the typeface and so on), and drawing those characters in an image buffer.

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

When the image buffer is ready, more software is called to write the image buffer to the display. Again, this involves passing data to another device driver. Ultimately, it reaches an actual hardware device, which takes the data and makes it appear on the display.

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

To wrap up, there is a huge chain of events. Data goes up and down through multiple layers, likely involving several different user processes and several different device drivers, and many software libraries. It is difficult to get a comprehensive view of the entire process. Generally, one would not want to try to understand the entire process all at once but would learn separately about each of the steps. For example, at times in my career, I have had to deal with the minute details of a system call instruction. But, when thinking about how my entire system is working, I think about larger-level processes communicating with each other, without thinking about the details of how those communications are made to work.

这篇关于C中的printf背后是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆