在Linux中使用python ctypes调用io_submit
我正在尝试使用 Python 的 ctypes 来调用 io_submit。我的代码应该能够在 32 位和 64 位的 Intel/AMD 架构上运行,不过这里我主要关注 64 位的部分。
我定义了以下内容:
def PADDED64(type, name1, name2):
return [(name1, type), (name2, type)]
def PADDEDptr64(type, name1, name2):
return [(name1, type)]
def PADDEDul64(name1, name2):
return [(name1, ctypes.c_ulong)]
class IOVec(ctypes.Structure):
_fields_ = [("iov_base", ctypes.c_void_p), ("iov_len", ctypes.c_size_t)]
class IOCBDataCommon64(ctypes.Structure):
_fields_ = PADDEDptr64(ctypes.c_void_p, "buf", "__pad1") + \
PADDEDul64("nbytes", "__pad2") + \
[("offset", ctypes.c_longlong), ("__pad3", ctypes.c_longlong), ("flags", ctypes.c_uint), ("resfd", ctypes.c_uint)]
class IOCBDataVector(ctypes.Structure):
_fields_ = [("vec", ctypes.POINTER(IOVec)), ("nr", ctypes.c_int), ("offset", ctypes.c_longlong)]
class IOCBDataPoll64(ctypes.Structure):
_fields_ = PADDED64(ctypes.c_int, "events", "__pad1")
class SockAddr(ctypes.Structure):
_fields_ = [("sa_family", ctypes.c_ushort), ("sa_data", ctypes.c_char * 14)]
class IOCBDataSockAddr(ctypes.Structure):
_fields_ = [("addr", ctypes.POINTER(SockAddr)), ("len", ctypes.c_int)]
class IOCBDataUnion64(ctypes.Union):
_fields_ = [("c", IOCBDataCommon64), ("v", IOCBDataVector), ("poll", IOCBDataPoll64), ("saddr", IOCBDataSockAddr)]
class IOCB64(ctypes.Structure):
_fields_ = PADDEDptr64(ctypes.c_void_p, "data" , "__pad1") + \
PADDED64(ctypes.c_uint, "key", "__pad2") + \
[("aio_lio_opcode", ctypes.c_short), ("aio_reqprio", ctypes.c_short), ("aio_fildes", ctypes.c_int), ("u", IOCBDataUnion64)]
class Timespec(ctypes.Structure):
_fields_ = [("tv_sec", ctypes.c_long), ("tv_nsec", ctypes.c_long)]
class IOEvent64(ctypes.Structure):
_fields_ = PADDEDptr64(ctypes.c_void_p, "data", "__pad1") + \
PADDEDptr64(ctypes.POINTER(IOCB64), "obj", "__pad2") + \
PADDEDul64("res", "__pad3") + \
PADDEDul64("res2", "__pad4")
我有一个叫做 AIOCommands 的包装类:
class AIOCommands:
def __init__(self, aioCommandList):
self.__commandList = aioCommandList
self.__iocbs = (IOCB64 * len(self.__commandList))()
for i in range(len(self.__commandList)):
self.__commandList[i].initialize(self.__iocbs[i])
def size(self):
return len(self.__iocbs)
def getIOCBArray(self):
return self.__iocbs
我已经定义了 io_submit 的参数和返回值:
class Executor:
def __init__(self, aioLibraryPath):
self.__aio = ctypes.CDLL(aioLibraryPath)
self.__aio.io_submit.argtypes = [self.aio_context_t, ctypes.c_long, ctypes.POINTER(ctypes.POINTER(IOCB64))]
self.__aio.io_submit.restype = ctypes.c_long
那么,Executor.io_submit 的具体实现应该是什么样的呢?我尝试了:
def io_submit(self, aioContext, aioCommands):
iocbPtr = ctypes.cast(aioCommands.getIOCBArray(), ctypes.POINTER(self.iocb_t))
return self.__aio.io_submit(aioContext, aioCommands.size(), ctypes.byref(iocbPtr))
但是每当 aioCommandList 的长度大于 1 时,我就会遇到段错误。当列表只包含 1 个命令时,代码就能正常工作。
这可能是我的结构定义出了问题吗?我尝试模仿 libaio.h 中的定义(假设只支持小端架构):
#if defined(__i386__) /* little endian, 32 bits */
#define PADDED(x, y) x; unsigned y
#define PADDEDptr(x, y) x; unsigned y
#define PADDEDul(x, y) unsigned long x; unsigned y
#elif defined(__ia64__) || defined(__x86_64__) || defined(__alpha__)
#define PADDED(x, y) x, y
#define PADDEDptr(x, y) x
#define PADDEDul(x, y) unsigned long x
#elif defined(__powerpc64__) /* big endian, 64 bits */
#define PADDED(x, y) unsigned y; x
#define PADDEDptr(x,y) x
#define PADDEDul(x, y) unsigned long x
#elif defined(__PPC__) /* big endian, 32 bits */
#define PADDED(x, y) unsigned y; x
#define PADDEDptr(x, y) unsigned y; x
#define PADDEDul(x, y) unsigned y; unsigned long x
#elif defined(__s390x__) /* big endian, 64 bits */
#define PADDED(x, y) unsigned y; x
#define PADDEDptr(x,y) x
#define PADDEDul(x, y) unsigned long x
#elif defined(__s390__) /* big endian, 32 bits */
#define PADDED(x, y) unsigned y; x
#define PADDEDptr(x, y) unsigned y; x
#define PADDEDul(x, y) unsigned y; unsigned long x
#else
#error endian?
#endif
struct io_iocb_poll {
PADDED(int events, __pad1);
}; /* result code is the set of result flags or -'ve errno */
struct io_iocb_sockaddr {
struct sockaddr *addr;
int len;
}; /* result code is the length of the sockaddr, or -'ve errno */
struct io_iocb_common {
PADDEDptr(void *buf, __pad1);
PADDEDul(nbytes, __pad2);
long long offset;
long long __pad3;
unsigned flags;
unsigned resfd;
}; /* result code is the amount read or -'ve errno */
struct io_iocb_vector {
const struct iovec *vec;
int nr;
long long offset;
}; /* result code is the amount read or -'ve errno */
struct iocb {
PADDEDptr(void *data, __pad1); /* Return in the io completion event */
PADDED(unsigned key, __pad2); /* For use in identifying io requests */
short aio_lio_opcode;
short aio_reqprio;
int aio_fildes;
union {
struct io_iocb_common c;
struct io_iocb_vector v;
struct io_iocb_poll poll;
struct io_iocb_sockaddr saddr;
} u;
};
如果有人能提供帮助,我将非常感激,因为我在这个问题上卡了好几个小时。
1 个回答
1
我理解的意思是,传给 io_submit() 的 iocbpp
参数是一个指向 struct iocb
结构体的指针数组。
这个理解在这里的一个Linux特定的例子中得到了进一步的确认:http://voinici.ceata.org/~sana/blog/?p=248,还有这里的EINVAL错误文档:http://linux.die.net/man/2/io_submit(数组下标优先于解引用)。
你传给 io_submit()
的其实是一个指向 struct iocb
数组的引用。当 io_submit
在遍历 iocbpp
数组时,它会解引用一些无效的内存地址,这样肯定会导致段错误(segfault)。第一个元素(索引0)会正常工作,因为没有内存偏移来访问它。
编辑 这里还有另一个例子:http://www.xmailserver.org/eventfd-aio-test.c