缓冲区的类型

时间:2010-07-06 04:33:04

标签: c operating-system

最近,一位采访者向我询问了缓冲的类型。有哪些类型的缓冲区?实际上,当我说我将所有系统调用写入日志文件以监视系统时,出现了这个问题。他说,每次调用文件都会很慢。如何防止它。我说我会用缓冲区,他问我什么类型的缓冲区?有人可以解释我的缓冲类型。

5 个答案:

答案 0 :(得分:6)

在UNIX下的C语言(也可能是其他操作系统)中,通常有两个缓冲区,至少在您的给定方案中。

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

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

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

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

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

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

说实话,我不完全确定为什么你的面试官认为除非你正在撰写很多的信息,否则它会很慢。已经存在的两个级别的缓冲应该非常充分。如果 是一个问题,那么你可以自己引入另一个层,它将消息写入内存缓冲区,然后将其传递给单个fprint类型的调用。溢出。

但是,除非你在没有任何函数调用的情况下执行此操作,否则我看不到它比fprint类缓冲已经给你的速度快得多。


在评论中澄清这个问题实际上是关于内核中的缓冲:

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

可能最好的选择是缓冲区,无论是静态分配还是在引导时动态分配一次(您希望避免动态重新分配失败的可能性)。

其他人提出了一个环形(或循环)缓冲区,但由于以下原因,我不会那样(技术上):使用经典的循环缓冲区意味着在数据包裹时​​写出数据将需要两个独立的写作。例如,如果您的缓冲区具有:

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|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指针,如果缓冲区中的字节加上要添加的字节小于缓冲区大小,只需将它们添加到缓冲区中,不要对底层媒体进行物理写入。 / p>

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

您可以选择以下两种方法之一:

  • 按原样刷新缓冲区,将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

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

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

  

除此之外:从技术上讲,我猜这个类型的循环缓冲区,但它具有特殊的案例处理功能,可以最大限度地减少写入次数,并且不需要尾部指针,因为这种优化。

答案 1 :(得分:2)

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

答案 2 :(得分:0)

我想到的是基于时间的缓冲区和基于大小的缓冲区。因此,您可以每隔x秒/分钟/小时或者其他任何内容写入缓冲区中的任何内容。或者,您可以等到有x个日志条目或x个字节的日志数据并立即写入它们。这是log4net和log4J执行此操作的方法之一。

答案 3 :(得分:0)

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

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

在动态内存可用的情况下,FIFO可以通过多种方式实现,特别是对于链表导出的数据结构。

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

FIFO和LIFO缓冲区的特定情况是TCP段重组缓冲区。这些可能会在收到尚未到达的中间段之前持有无序(“从未来”)收到的段。

*我的首字母缩略词更好,但大多数人会将LIFO称为“ Last In,First Out”,而不是最新

答案 4 :(得分:0)

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