如何在Python 3中实现POSIX文件描述符?
我想写一个类,让它能像真正的文件描述符一样工作。它的 .fileno() 方法应该返回一个文件描述符,这个描述符能提供 POSIX 系统所期望的所有服务。
这是我第一次尝试 POSIX 系统编程,所以我可能会有很多误解。
我这么做的主要原因是想用一个在内存中的 Python 对象,作为 subprocess.Popen
构造函数的 stdin
或 stdout
参数,而不想依赖临时文件或内存映射文件。但我并不想要一些聪明的技巧来完成这个任务——我真的想要一个能够处理所有相关系统调用的 Python 实现。
3 个回答
这是我第一次接触POSIX系统编程,所以我可能理解得很糟糕。
没错。
POSIX文件描述符其实就是一些数字——它们不是对象,所以你不能像操作对象那样去重写它们的方法。比如,0、1和2通常都是有效的文件描述符。
“相关的系统调用”是内置在Linux内核里的。Linux内核本身维护着一个列表,把文件描述符和一些内部的内核对象(那些对象是有方法的!)对应起来,但你不能从Python中插入一个新的文件描述符。在内核空间运行的代码和普通的“用户模式”代码是非常不同的。
我建议你看看subprocess.PIPE,以及subprocess.Popen对象上的stdout、stdin、stderr属性或者communicate()方法。这可以让你启动一个子进程,读取它输出的数据,并完全控制发送给它的数据。(我觉得这正是你想做的……)如果你感兴趣的话,玩玩这个之后可以看看subprocess.py的源代码,了解它是怎么工作的。
这里有一个关于subprocess.PIPE的例子:这里.
另外,如果你真的想在Python中实现一个完整的文件系统,可以看看FUSE,还有它的Python绑定。FUSE包含一个在内核中运行的C模块,处理某个目录的文件系统请求。它通过将请求传递给一个用户空间程序来处理,这个程序可以用Python编写。你可以从一个独立的Python程序中打开这些文件,以获取它们的文件描述符。这有点复杂,可能不是初学者的最佳起点。
如果你想创建一个可以在系统调用中当作文件使用的类,它需要有一个 fileno() 方法,这个方法返回的是真正的操作系统文件描述符。一个不需要接触硬盘的实现方式是使用管道,因为管道有文件描述符,系统调用就可以写入这些文件描述符。
我确实写过一个类,使用这种技术来实现某些功能,具体可以参考 另一个回答。虽然这个类并不完全符合你的需求,但使用管道的这个技术对你来说应该是可行的:
import io
import logging
import os
import select
import subprocess
import time
import threading
LOG_FILENAME = 'output.log'
logging.basicConfig(filename=LOG_FILENAME,level=logging.DEBUG)
class StreamLogger(io.IOBase):
def __init__(self, level):
self.level = level
self.pipe = os.pipe()
self.thread = threading.Thread(target=self._flusher)
self.thread.start()
def _flusher(self):
self._run = True
buf = b''
while self._run:
for fh in select.select([self.pipe[0]], [], [], 0)[0]:
buf += os.read(fh, 1024)
while b'\n' in buf:
data, buf = buf.split(b'\n', 1)
self.write(data.decode())
time.sleep(1)
self._run = None
def write(self, data):
return logging.log(self.level, data)
def fileno(self):
return self.pipe[1]
def close(self):
if self._run:
self._run = False
while self._run is not None:
time.sleep(1)
os.close(self.pipe[0])
os.close(self.pipe[1])
你不能这么做。POSIX文件描述符是在操作系统的内核中管理的,和Python的世界是分开的;你无法在Python代码中模拟它们。