Python ctypes从带有上下文管理器的缓冲区映射到内存映射文件(mmap)
我正在使用Python ctypes从带有上下文管理器的缓冲区映射到内存映射文件(mmap),python,python-3.x,ctypes,mmap,contextmanager,Python,Python 3.x,Ctypes,Mmap,Contextmanager,我正在使用ctypes.from_buffer()为某些任务将ctypes结构映射到内存映射文件。通常,这些文件包含结构化头和二进制数据的串联。ctypes结构允许稳定的二进制表示和轻松的pythonic字段访问—这是一个真正的获胜团队 这些内存映射文件随时间动态增长。除了只接受mmap.PAGESIZE粒度增长的复杂性外,如果在调整大小尝试期间将一些(隐藏的)引用保留在映射区域中,mmap会产生过敏反应 这就是上下文管理器发挥作用的地方 # -*- coding: utf8 -* impor
ctypes.from_buffer()
为某些任务将ctypes结构映射到内存映射文件。通常,这些文件包含结构化头和二进制数据的串联。ctypes结构允许稳定的二进制表示和轻松的pythonic字段访问—这是一个真正的获胜团队
这些内存映射文件随时间动态增长。除了只接受mmap.PAGESIZE
粒度增长的复杂性外,如果在调整大小尝试期间将一些(隐藏的)引用保留在映射区域中,mmap会产生过敏反应
这就是上下文管理器发挥作用的地方
# -*- coding: utf8 -*
import io
import mmap
import ctypes
import logging
log = logging.getLogger(__file__)
def align(size, alignment):
"""return size aligned to alignment"""
excess = size % alignment
if excess:
size = size - excess + alignment
return size
class CtsMap:
def __init__(self, ctcls, mm, offset = 0):
self.ctcls = ctcls
self.mm = mm
self.offset = offset
def __enter__(self):
mm = self.mm
offset = self.offset
ctsize = ctypes.sizeof(self.ctcls)
if offset + ctsize > mm.size():
newsize = align(offset + ctsize, mmap.PAGESIZE)
mm.resize(newsize)
self.ctinst = self.ctcls.from_buffer(mm, offset)
log.debug('add mapping: %s', ctypes.addressof(self.ctinst))
return self.ctinst
def __exit__(self, exc_type, exc_value, exc_traceback):
# free all references into mmap
log.debug('remove mapping: %s', ctypes.addressof(self.ctinst))
del self.ctinst
self.ctinst = None
class MapFile:
def __init__(self, filename):
self._offset = 0
try:
mapsize = mmap.PAGESIZE
self._fd = open(filename, 'x+b')
self._fd.write(b'\0' * mapsize)
self._created = True
except FileExistsError:
self._fd = open(filename, 'r+b')
self._fd.seek(0, io.SEEK_END)
mapsize = self._fd.tell()
self._fd.seek(0)
self._mm = mmap.mmap(self._fd.fileno(), mapsize)
def add_data(self, data):
datasize = len(data)
log.debug('add_data: header')
hdtype = ctypes.c_char * 4
with CtsMap(hdtype, self._mm, self._offset) as hd:
hd.raw = b'HEAD'
self._offset += 4
#del hd
log.debug('add_data: %s', datasize)
blktype = ctypes.c_char * datasize
with CtsMap(blktype, self._mm, self._offset) as blk:
blk.raw = data
self._offset += datasize
#del blk
return 4 + datasize
def size(self):
return self._mm.size()
def close(self):
self._mm.close()
self._fd.close()
if __name__ == '__main__':
import sys
logconfig = dict(
level = logging.DEBUG,
format = '%(levelname)5s: %(message)s',
)
logging.basicConfig(**logconfig)
mapfile = sys.argv[1:2] or 'mapfile'
datafile = sys.argv[2:3] or __file__
data = open(datafile, 'rb').read()
maxsize = 10 * mmap.PAGESIZE
mf = MapFile(mapfile)
while mf.size() < maxsize:
mf.add_data(data)
mf.close()
要使代码正常工作,必须删除MapFile.add_data中两个del语句前面的注释
显然,它们是必需的,因为with
语句中分配的变量仍然存在于本地名称空间中,这足以将引用保留到mmap.resize()
偶然发现的mmap区域中
我怎样才能摆脱这些del语句,因为它们是真正的PITA,而上下文管理器不是为了避免这种扭曲而发明的吗
有没有办法更有效地删除这些映射?例如,在CtsMap.\uu退出
中以编程方式从缓冲区()恢复ctypes.的操作
可以找到一个相关的问题,它进一步说明了ctypes.from_buffer与mmaped files方法的结合。Hmm,代码在Windows上运行时没有错误,并创建了mapfile
。不能复制。不过,我怀疑\uuuu exit\uuuu
中的self.mm=None
会释放出del
正在释放的东西。嗯,我在Linux上(相当流行的内核FWIW)。不幸的是,这两个操作系统之间的mmap实现存在显著差异。我不知道的是with
变量的生命周期语义。当带
的块结束时,var超出范围,我认为,这足以触发析构函数,因此理论上,\uuuuuuuu退出
中的代码就足够了。但实际上,在Linux下的Python 3.4.5中,情况并非如此。感谢您在Windows下对此进行测试。with
变量没有超出范围;只有\uuuuu退出\uuuuu
被调用。由于del
为您修复了一些问题,因此将实例变量设置为None将释放这些对象。
DEBUG: add_data: header
DEBUG: add mapping: 139989829832704
DEBUG: remove mapping: 139989829832704
DEBUG: add_data: 3275
DEBUG: add mapping: 139989829832708
DEBUG: remove mapping: 139989829832708
DEBUG: add_data: header
DEBUG: add mapping: 139989829835983
DEBUG: remove mapping: 139989829835983
DEBUG: add_data: 3275
Traceback (most recent call last):
File "ctxmmapctypes.py", line 110, in <module>
mf.add_data(data)
File "ctxmmapctypes.py", line 78, in add_data
with CtsMap(blktype, self._mm, self._offset) as blk:
File "ctxmmapctypes.py", line 39, in __enter__
mm.resize(newsize)
BufferError: mmap can't resize with extant buffers exported.