【问题标题】:types of buffers缓冲区类型
【发布时间】:2010-07-06 04:33:03
【问题描述】:

最近一位面试官问我缓冲区的类型。有哪些类型的缓冲区?实际上,当我说我会将所有系统调用写入日志文件以监视系统时,这个问题就出现了。他说将每次调用都写入文件会很慢。如何预防。我说我会用缓冲液,他问我用什么缓冲液?谁能解释一下缓冲区的类型。

【问题讨论】:

  • 在您的应用程序中,缓冲输出可能会降低日志文件的用处。如果“监控系统”包括“诊断崩溃”,那么延迟输出会使这变得更加困难。只是一个想法。在面试中,询问有关用例的澄清问题可能会有所帮助。
  • @Heath 非常感谢您的建议。
  • 问题是关于在操作系统内部实现日志记录(来自系统调用实现者的 POV)还是来自应用程序?
  • @Heath 它在内核中。
  • @Mousey,在这种情况下,您接受的关于 stdio 缓冲区的答案与您在面试中被问到的问题无关。内核中不存在 Stdio 缓冲区。您正在寻找的答案与 dmesg 和使用循环缓冲区有关。在内核中,您不能使用需要动态内存的缓冲区。如果您正在记录动态内存基础架构刚刚损坏怎么办?您不能分配更多内存来记录 那个

标签: c operating-system


【解决方案1】:

在 UNIX(可能还有其他操作系统)下的 C 中,通常有两个缓冲区,至少在您给定的场景中是这样。

第一个存在于 C 运行时库中,其中要写入的信息在传送到操作系统之前被缓冲。

第二个是在操作系统本身中,信息被缓冲,直到可以物理地写入底层媒体。

例如,我们在很多个月前编写了一个日志库,它强制将信息写入磁盘,以便在程序崩溃或操作系统崩溃时它会在那里。

这是通过以下顺序实现的:

fflush (fh); fsync (fileno (fh));

其中第一个实际上确保信息从 C 运行时缓冲区传递到操作系统,第二个确保将信息写入磁盘。请记住,这是一项昂贵的操作,只有在您绝对需要立即写入信息时才应执行(我们仅在 SUPER_ENORMOUS_IMPORTANT 日志记录级别执行此操作)。

说实话,我不完全确定为什么你的面试官认为它会很慢,除非你正在写大量的信息。很多。已经存在的两个缓冲级别应该可以很好地执行。如果 一个问题,那么您可以自己引入另一个层,将消息写入内存缓冲区,然后在即将溢出时将其传递给单个 fprint-type 调用.

但是,除非您在没有任何函数调用的情况下执行此操作,否则我看不出它比 fprint-type 缓冲已经为您提供的快得多。


在 cmets 中澄清这个问题实际上是关于内核内部的缓冲之后:

基本上,您希望它尽可能快速、高效和可行(不易出现故障或资源短缺)。

最好的选择可能是一个缓冲区,可以是静态分配的,也可以是在启动时动态分配一次(您希望避免动态重新分配失败的可能性)。

其他人建议使用环形(或循环)缓冲区,但我不会(技术上)采用这种方式,原因如下:使用经典的循环缓冲区意味着在数据回绕时写出数据需要两个独立的写入。例如,如果您的缓冲区有:

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|s|t|r|i|n|g| |t|o| |w|r|i|t|e|.| | | | | | |T|h|i|s| |i|s| |t|h|e| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                                 ^           ^
                                 |           |
                   Buffer next --+           +-- Buffer start

那么你必须写"This is the ",然后是"string to write."

相反,维护next 指针,如果缓冲区中的字节加上要添加的字节小于缓冲区大小,则只需将它们添加到缓冲区,而不对底层媒体进行物理写入。

只有当你要溢出缓冲区时,你才会开始做一些棘手的事情。

您可以采取以下两种方法之一:

  • 要么按原样刷新缓冲区,要么将next 指针设置回起点以处理新消息;或
  • 添加部分消息以填满缓冲区,然后刷新缓冲区并将next 指针设置回起点以处理其余消息。

我可能会选择第二个,因为您将不得不考虑对于缓冲区来说太大的消息。

我在说的是这样的:

initBuffer:
    create buffer of size 10240 bytes.
    set bufferEnd to end of buffer + 1
    set bufferPointer to start of buffer
    return

addToBuffer (size, message):
    while size != 0:
        xfersz = minimum (size, bufferEnd - bufferPointer)
        copy xfersz bytes from message to bufferPointer
        message = message + xfersz
        bufferPointer = bufferPointer + xfersz
        size = size - xfersz
        if bufferPointer == bufferEnd:
            write buffer to underlying media
            set bufferPointer to start of buffer
        endif
    endwhile

这基本上通过减少物理写入的数量来有效地处理任何大小的消息。当然会有优化 - 消息可能已被复制到内核空间,所以如果你要写它,将它复制到缓冲区几乎没有意义。您也可以将内核副本中的信息直接写入底层媒体,并且只将最后一位传输到缓冲区(因为您必须保存它)。

此外,如果有一段时间没有写入任何内容,您可能希望将不完整的缓冲区刷新到底层媒体。这将减少因内核本身崩溃而丢失信息的可能性。

旁白:从技术上讲,我猜这 一种循环缓冲区,但它具有特殊情况处理以最大限度地减少写入次数,并且由于优化,不需要尾指针。 p>

【讨论】:

  • +1 了解详细说明。如果在采访中被问到这个问题,我只会说“stdio buffers”。
  • 非常感谢您的详细解释
【解决方案2】:

还有ring buffers 具有有限的空间要求,并且可能在Unix dmesg 设施中最为人所知。

【讨论】:

    【解决方案3】:

    我想到的是基于时间的缓冲区和基于大小的缓冲区。因此,您可以每 x 秒/分钟/小时或其他任何时间将缓冲区中的任何内容写入文件一次。或者,您可以等到有 x 个日志条目或 x 个字节的日志数据,然后一次将它们全部写入。这是 log4net 和 log4J 做的方式之一。

    【讨论】:

      【解决方案4】:

      总的来说,有“先进先出”(FIFO)缓冲区,也称为队列;并且有“最新*-In-First-Out”(LIFO)缓冲区,也称为堆栈。

      为了实现 FIFO,有circular buffers,通常用于已分配固定大小字节数组的情况。例如,键盘或串行 I/O 设备驱动程序可能会使用此方法。当无法动态分配内存时(例如,在操作系统的虚拟内存 (VM) 子系统运行所需的驱动程序中),这是通常使用的缓冲区类型。

      在动态内存可用的情况下,FIFO 可以通过多种方式实现,尤其是链表派生数据结构。

      此外,实现priority queues 的二项式堆可用于 FIFO 缓冲区实现。

      既不是 FIFO 也不是 LIFO 缓冲区的特殊情况是 TCP 段重组缓冲区。这些可能会保存乱序接收的片段(“来自未来”),这些片段会在接收尚未到达的中间片段之前被保留。

      * 我的缩写更好,但大多数人会称 LIFO“Last In, First Out”,而不是 Latest

      【讨论】:

      • 您的首字母缩写词再好不过了。最好保持技术术语的既定含义。发明自己的意思或拼写可能会导致混乱,甚至可能被视为无能的标志。
      • 也就是说,发明自己的概念碰巧与已经存在的首字母缩略词发生冲突当然没有错,因为首字母缩略词的数量有限。只需避免重新定义现有概念即可。
      • 哇,您似乎对我的反对意见和评论感到侮辱,阅读了我所有的旧答案并反对其中很多。为此事道歉。但这种反应肯定会在未来实际解释原因。
      • 你说的既对又错,但大多是后者。我已经给了你拒绝投票的理由,他们和你的一样蹩脚。你太自满和说教了。您的答案和 cmets 是您的意见,而不是技术材料。我很高兴我们已经开始了。我觉得你很蹩脚。
      • 最后,SO 上的私人敌人。我很荣幸……我想。请随时评论我所有的旧答案并根据需要对它们投反对票,因为我不玩 SO 以获得高分。只是不要指望我对这个话题有更多的回答,因为我不会降到这个幼儿园的水平。如果您对特定点有明确的具体问题,请提出,我会回答。谢谢。
      【解决方案5】:

      如果我错了,请纠正我,但不会使用 mmap'd 文件作为日志,避免小型 write 系统调用的开销以及如果应用程序(但不是操作系统)可能导致数据丢失) 坠毁?对我来说,这似乎是性能和可靠性之间的理想平衡。

      【讨论】:

        猜你喜欢
        • 2020-04-20
        • 1970-01-01
        • 1970-01-01
        • 2017-03-13
        • 2018-02-02
        • 2010-12-24
        • 2011-03-26
        • 2016-10-14
        • 2022-01-17
        相关资源
        最近更新 更多