在Python中正确丢弃mmap内存的ctypes指针

7

我遇到了一个问题,就是在Python中创建指向mmap后无法正确关闭它们。我的使用情况是打开文件(通常是UIO设备以便操作硬件,但是这个问题也会出现在普通文件上),将它们映射到内存并将其用作ctypes结构的缓冲区。通常是数据结构体或数据数组。最小化的示例代码如下:

import ctypes as ct
import mmap
import os

fileno = os.open('/tmp/testfile', os.O_RDWR | os.O_SYNC)
map = mmap.mmap(fileno, 32768, flags=mmap.MAP_SHARED)
memory = (ct.c_uint32 * 8192).from_buffer(map)

# Use the memory object to do things here

del memory
map.close()
os.close(fileno)

目前为止一切都很顺利。

但是,有时我需要调用一些需要访问该内存的C库函数,因此我必须向它们传递一个指针。我使用以下方式创建该指针:

ptr = ct.cast(memory, ct.c_void_p)

所有这一切都非常好,只有一个问题。一旦我创建这样一个指针,我就不能再关闭内存映射。看下这个稍微扩展的例子:

import ctypes as ct
import mmap
import os

fileno = os.open('/tmp/testfile', os.O_RDWR | os.O_SYNC)
map = mmap.mmap(fileno, 32768, flags=mmap.MAP_SHARED)
memory = (ct.c_uint32 * 8192).from_buffer(map)

# Use the memory object to do things here
ptr = ct.cast(memory, ct.c_void_p)
del ptr

del memory
map.close()
os.close(fileno)

运行此代码将导致以下异常:
Traceback (most recent call last):
  File "pointer_test.py", line 14, in <module>
    map.close()
BufferError: cannot close exported pointers exist

Process finished with exit code 1

我已经运行了一些分析(使用gc.get_referrers),查看引用map实例的内容,发现还存在一个memoryview实例。最终追踪到ctypes数组:

[<__main__.c_uint_Array_8192 object at 0x7f954bd1e0>,
 [{547965620704: <__main__.c_uint_Array_8192 object at 0x7f954bd1e0>,
   'ffffffff': <memory at 0x7f95621a08>},
  [<memory at 0x7f95621a08>,
   [<managedbuffer object at 0x7f95746d08>,

然而,这并没有真正帮助我。我想知道如何摆脱那个指针。我知道这可能并不完全安全,因为我当然可以随时在其他地方有指针的副本。但是完全防止在创建指针后关闭 mmap 也不是一个好主意。有人知道如何让 Python 相信我已经丢弃了所有指针,并且现在可以安全地关闭 mmap 吗?


1
我做了和你一样的测试,得到了相同的结果。我认为这只是Python在某个地方保留了一个引用,所以我只是做了:import gcgc.collect(),然后尝试立即关闭内存映射文件:它起作用了...我看不到其他任何方法来告诉Python在内存映射上没有引用了 :( - Neitsa
我也遇到了这个问题。在一个涉及类和终结器的较大代码环境中,要正确追踪所有 mmap 的视图几乎是不可能的。为什么我们不能得到一个“不安全”模式,即使在视图仍然存在的情况下也可以释放 mmap? - undefined
我也被这个问题困扰。在一个涉及类和终结器的较大代码环境中,正确追踪 mmap 的所有视图几乎是不可能的。为什么我们不能只得到一个允许在存在视图的情况下释放 mmap 的“不安全”模式呢? - mara004
在浪费了很多时间之后,我终于找到了原因。其中一个原因是将对视图的引用作为最后一个for循环变量的值,需要显式地删除(del)。另一个原因是类级别上的引用。我们在该类的弱引用(finalizer)中关闭了mmap。由于正在进行终结的对象不可访问,我认为引用已经消失,但显然对象在finalizer运行时仍然存在。这造成了一个相当严重的设计问题,因为我们无法在finalizer中摆脱这个引用。参考:https://github.com/pypdfium2-team/pypdfium2/pull/237 - mara004
可能的解决方法是将所讨论的属性存储在列表(或其他可变对象)中,并通过终结器传递它,这样我们就可以清除引用。为了方便在类级别上进行访问,我猜应该可以创建一个包装属性,返回列表属性的索引0。 - mara004
1个回答

0
找到 memoryview 实例并先调用 memoryview-instance.release(),然后才能关闭 mmap 实例。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接