该实验是在Linux,x86 32位上进行的。
所以假设在我的汇编程序中,我需要定期(例如每执行100000个基本块之后)将.bss段中的数组从内存转储到磁盘。 数组的起始地址和大小是固定的。 该数组logging了执行的基本块的地址,现在的大小是16M。
我试图写一些本机代码,从.bss节到memcpy
,然后写回到磁盘。 但在我看来,这是非常乏味的,我担心的性能和内存消耗,比如说,每次都在堆栈上分配一个非常大的内存。
所以这里是我的问题,我怎样才能有效地从全局数据部分转储内存? 我清楚了吗?
首先, 不要在asm中编写这部分代码 , 不是一开始 编写一个C函数来处理这个部分,并从asm中调用它。 如果您需要在转储另一个16MiB时执行只运行的部分,则可以手动调整它。 系统级编程都是关于从系统调用(或C stdio函数)中检查错误返回的,而在asm中这样做会很痛苦。
显然你可以用asm写任何东西,因为与C相比,系统调用并没有什么特别之处。除了可能在锁定中MFENCE
之外,没有任何东西比MFENCE
C更容易使用asm。
无论如何,我已经解决了你想要发生什么与你的缓冲区的三个变化:
mmap(2)
/ msync(2)
) write(2)
或可能不工作的zero-copy vmsplice(2)
+ splice(2)
想法)。 mmap(2)
顺序块。 如果你只想每次都覆盖同一个磁盘区域,可以将mmap(2)
一个文件作为你的数组使用。 (定期调用msync(2)
强制数据到磁盘。)虽然,mmapped方法不能保证文件的一致状态。 写入可以刷新到磁盘以外的请求。 IDK,如果有一种方法可以避免任何类型的保证(即不只是选择缓冲区刷新定时器等,所以你的网页通常不会被写入,除非通过msync(2)
。)
将缓冲区附加到文件的简单方法是在write(2)
时简单地调用write(2)
。 write(2)
做你需要的一切。 如果您的程序是多线程的,则可能需要在系统调用之前锁定数据,然后再释放锁。 我不确定写入系统调用返回的速度有多快。 它可能只会在内核将数据复制到页面缓存后才会返回。
如果你只需要一个快照,但是所有写入缓冲区的都是原子事务(即缓冲区总是处于一致的状态,而不是需要彼此一致的值对),那么你不需要调用write(2)
之前锁。 在这种情况下会有少量偏差(假设内核按顺序复制,缓冲区末尾的数据比起始数据稍晚)。
IDK,如果write(2)
通过直接IO(零拷贝,绕过页面缓存)返回更慢或更快。 用O_DIRECT
open(2)
你的文件,正常write(2)
。
在这个过程中,如果你想写一个缓冲区的快照,然后继续修改它,那么在这个过程中必须有一个副本。 否则MMU抄写欺骗:
有一个用于将用户页面零拷贝写入磁盘文件的API。 Linux的vmsplice(2)
和splice(2)
可以让你告诉内核把你的页面映射到页面缓存中。 如果没有SPLICE_F_GIFT
,我认为它将它们设置为copy-on-write。 (哎呀,实际上手册页说没有SPLICE_F_GIFT
,下面的splice(2)
将不得不复制。所以IDK,如果有机制获得写时复制语义。)
假设有一种方法可以为您的页面获得写时复制语义,直到内核完成写入磁盘并释放它们:
进一步的写操作可能需要内核在数据到达磁盘之前对一个或两个页面进行memcpy,但保存复制整个缓冲区。 软的页面错误和页面表操作开销可能不值得,除非你的数据访问模式在很短的时间内被空间定位,直到写入磁盘和待写入的页面被释放。 (我认为用这种方式工作的API不存在,因为没有任何机制可以在页面到达磁盘之后立即释放页面,Linux想把它们放在页面缓存中。)
我从来没有使用vmsplice,所以我可能会得到一些细节错误。
如果有一种方法可以创建同一内存的新的写入时拷贝映射,也许可以通过创建一个新的临时文件映射(在tmpfs文件系统,概要/dev/shm
),这将使您无需快照长时间保持锁。 然后你可以传递快照来write(2)
,并在太多的复制写入页面错误发生之前尽快取消映射。
如果在每次写入之后都可以使用一个归零缓冲区来启动,那么您可以mmap(2)
连续的文件块,因此您生成的数据总是在正确的位置。
fallocate(2)
一些空间,以防止写入模式不连续时出现碎片。 mmap(2)
你的缓冲区到输出文件的第一个16MiB。 munmap(2)
你的缓冲区 mmap(2)
将下一个16MiB的文件转换为相同的地址 ,这样你就不需要通过新的地址给作者了。 这些页面将根据POSIX的要求进行预先置零(不能让内核暴露内存)。 可能mmap(buf, 16MiB, ... MAP_FIXED, fd, new_offset)
可以代替munmap
/ mmap
对。 MAP_FIXED
放弃重叠的旧MAP_FIXED
。 我认为这并不意味着对文件/共享内存的修改被丢弃,而是实际的映射改变,即使没有munmap
。
从彼得的回答中得到两个澄清追加快照的案例。
1.没有O_DIRECT
附加
正如Peter所说,如果你不使用O_DIRECT
, write()
会在数据被复制到页面缓存后立即返回。 如果页面缓存已满,则会阻塞,直到某个过时的页面被刷新到磁盘。
如果只是在未读取数据的情况下追加数据,则可以定期调用sync_file_range(2)
来调度先前写入的页面的flush,并使用POSIX_FADV_DONTNEED
标志调用posix_fadvise(2)
以从页面缓存中移除已刷新的页面。 这可以显着降低write()
会阻塞的可能性。
2.使用O_DIRECT
附加
使用O_DIRECT
, write()
通常会阻塞,直到数据被发送到磁盘(虽然它不是严格保证,见这里 )。 由于速度很慢,如果您需要非阻塞式写入,请准备好实施您自己的I / O调度。
您可以归档的好处是:更可预测的行为(您可以控制何时阻止),并且可能通过您的应用程序和内核的协作来减少内存和CPU使用量。