C 缓冲区的类型

C 缓冲区的类型,c,operating-system,C,Operating System,最近一位面试官问我缓冲的类型。有哪些类型的缓冲区?实际上,当我说我将把所有系统调用写入日志文件以监视系统时,这个问题就出现了。他说,将每一个调用写入一个文件会很慢。如何预防。我说我将使用缓冲区,他问我什么类型的缓冲区?有人能给我解释一下缓冲区的类型吗?在UNIX下的C中(可能还有其他操作系统),通常有两个缓冲区,至少在给定的场景中是这样的 第一个存在于C运行时库中,其中要写入的信息在传递到操作系统之前会被缓冲 第二种是操作系统本身,在操作系统中,信息被缓冲,直到可以物理写入底层媒体 例如,我们在

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

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

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

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

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

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

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

老实说,我不能完全确定为什么你的面试官认为这会很慢,除非你写了很多信息。已经存在的两个级别的缓冲应该能够充分执行。如果这是一个问题,那么您可以自己引入另一个层,它将消息写入内存缓冲区,然后在消息即将溢出时将消息传递给单个
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
然后您必须编写
,这是“
,后跟
”要编写的字符串。

相反,请保持
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
它基本上通过减少物理写入的数量来有效地处理任何大小的消息。当然会有一些优化——消息可能已经被复制到内核空间,因此如果您仍要编写它,那么将其复制到缓冲区就没有什么意义了。您也可以将内核副本中的信息直接写入底层媒体,只将最后一位传输到缓冲区(因为您必须保存它)

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

旁白:从技术上讲,我想这是一种循环缓冲区,但它有特殊的案例处理,以尽量减少写入的数量,并且由于这种优化,不需要尾部指针


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

还有一些具有有限空间要求的缓冲区,可能在Unix
dmesg
设施中最为人所知。

总体而言,有“先进先出”(First-in-First-Out)缓冲区,也称为队列;还有“最新的*-先进先出”()缓冲区,也称为堆栈

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

在动态内存可用的情况下,FIFO可以以多种方式实现,特别是使用链表派生的数据结构

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

FIFO或LIFO缓冲区的一种特殊情况是TCP段重组缓冲区。这些可能会保存无序接收的段(“从