c - 缓冲区的类型

标签 c operating-system

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

最佳答案

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

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

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

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

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

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

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

老实说,我不完全确定为什么你的面试官认为除非你写了很多信息,否则它会很慢。已经存在的两个级别的缓冲应该可以充分发挥作用。如果这是一个问题,那么您可以自己引入另一个层,将消息写入内存缓冲区,然后将其传送到单个 fprint -type 即将溢出时调用。

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

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

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

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

其他人建议使用环形(或环形)缓冲区,但我不会(技术上)这样做,原因如下:使用经典的环形缓冲区意味着在数据环绕时写出数据将需要两次独立的写入.例如,如果您的缓冲区具有:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|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
    

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

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

    Aside: Technically, I guess this is sort of a circular buffer but it has special case handling to minimise the number of writes, and no need for a tail pointer because of that optimisation.

    关于c - 缓冲区的类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3183504/

    相关文章:

    c - 如果 T 的第一个字段是 C,是否将 struct T* 转换为 struct C* 未定义行为?

    c - Lua:查询用户数据对象的元表名称

    operating-system - 如果没有分页概念,虚拟内存还能存在吗?

    c - 如何在不忙等待的情况下呈现计数信号量的监视器实现?

    Python C接口(interface),不同模块共享静态变量?

    c - C 中的结构 x 与 x_t

    c - 如何在不使用字符串的情况下计算单词总数?

    c - 为什么 "read"必须是在 "Kernel Mode"中运行的系统调用?

    operating-system - CPU超线程和操作系统上下文切换之间的关系?

    linux - 如何发现正在使用的 Linux 发行版