(承接上篇)
六. C/C++ Extending标准库
场景。无可用Python模块,将C API整合到Python类型系统中,造福众人。
实现。
1. 假设已经写好C扩展(后面介绍实现),Python调用C扩展再次实现一遍
同样的需求。
-
import datetime
-
import inotify
-
import sys
-
-
MASK_EVENTS = [(k,v) for k,v in sorted(list(inotify.__dict__.items()))
-
if k.startswith('IN_') and \
-
k not in ('IN_CLOEXEC', 'IN_NONBLOCK') and \
-
v == (v & ~(v-1))]
-
-
def print_event(filename, mask):
-
now = datetime.datetime.now()
-
print('{:02}:{:02}:{:02},{:03} file({file}) {mask}'.format(
-
now.hour, now.minute, now.second, now.microsecond//1000,
-
file=filename,
-
mask=' '.join(k for k,v in MASK_EVENTS if mask & v)))
-
-
def main():
-
if len(sys.argv) != 2:
-
sys.exit('usage: {} path'.format(sys.argv[0]))
-
pathname = sys.argv[1]
-
-
notify = inotify.inotify()
-
wd = notify.add_watch(pathname, inotify.IN_ALL_EVENTS)
-
while True:
-
evs = notify.read_events(1024)
-
for wd,mask,cookie,name in evs:
-
print_event(pathname if name is None else name, mask)
-
notify.rm_watch(wd)
-
notify.close()
-
-
if __name__ == '__main__':
-
main()
从编码风格来看,很难看出C style了,尤其是和C密切相关的结构体对齐和宏定义。
行2:inotify就是用C扩展的Python Module,所有C和OS的细节全部封装在内。
行5~8:此处仅为说明C扩展后的Module就像一个真正纯Python定义的Module,这个风格与Python的可读性原则不符,请勿模仿。if语句目的是提取模块中的mask常量定义,其一名称以“IN_”开头(如IN_OPEN等),其二名称不是IN_CLOEXEC和IN_NONBLOCK,它们是inotify_init的flags,不同于inotify_add_watch的flags,必须排除,其三
为了打印输出简洁,必须是原子flag(如IN_MOVE_TO)而非复合flag(IN_MOVE == IN_MOVE_FROM | IN_MOVE_TO),,inotify定义的原子flag的数值均等于2 ** n(n 为非负整数),其数值满足位运算v == (v & ~(v-1))。
行25~26:经过封装返回的事件是Python的内置类型,此处是[(wd, mask, cookie, name), (...)],一个由tuple构成的list,文件名name是str。
2. C扩展分析。通过Python C API将inotify C API集成到Python类型系统中,参考了CPython源码树中的Modules/selectmodule.c,乍看比较复杂,其实多数是程式化步骤。如果没有接触过C/C++ Extending,建议先读下官方文档的扩展入门部分,然后结合下文分析会更容易理解。
写C扩展时,第一个要弄清楚的就是
引用计数(reference count),下面列出有关tips:
-
reference count源于Python对象(对应C API的PyObject *)的内存管理,表示有多少个用户引用了该对象,当该值为0时,对象内存即被回收。
-
Python对象的内存由Python堆内存管理器(Python heap manager)统一协调,意味着为Python对象分配内存,不能用C的malloc/free系列函数,因为他们是C heap,不同于Python heap,即使为非Python对象提供内存也应该用Python heap提供的PyMem_Malloc/PyMem_Free系列函数,便于Python heap做全局内存诊断和优化。
-
reference count的增大和减少,必须由用户显式控制,Py_INCREF(x)和Py_DECREF(x)分别是增加和减少x的引用计数,Py_INCREF(x)意味着用户对该对象的所有权,在显式Py_DECREF(x)之前,用户必定可以访问x而保证其不被回收。
-
reference count的变化意味着用户对该对象所有权(Ownership)的变化。作为函数入参的对象,Ownership通常不会变化,但有两个例外,其一是需要将该对象保存时,会调Py_INCREF,比如PyList_Append(),其二是遇到特例函数PyTuple_SetItem()/PyList_SetItem(),它们会接管所有权,而不调用Py_INCREF。作为函数返回值的对象,Ownership通常会改变,但有例外,这四个函数PyTuple_GetItem()/PyList_GetItem()/PyDict_GetItem()/PyDict_GetItemString()就是特例,它们返回的对象Ownership无变化,调用方只有使用权,如果想声明所有权必须显式调用Py_INCREF。
C扩展的前半部分,包括inotify的成员函数,也是模块使用者最应该关心的部分。
点击(此处)折叠或打开(inotifymodule.c)
-
#include <Python.h>
-
#include <sys/inotify.h>
-
-
#define EVENT_SIZE_MAX (sizeof(struct inotify_event) + NAME_MAX + 1)
-
#define EVENT_SIZE_MIN (sizeof(struct inotify_event))
-
-
typedef struct {
-
PyObject_HEAD
-
int fd;
-
} pyinotify;
-
-
static PyObject *
-
pyinotify_err_closed(void)
-
{
-
PyErr_SetString(PyExc_ValueError,
-
"I/O operation on closed inotify object");
-
return NULL;
-
}
-
static int
-
pyinotify_internal_close(pyinotify *self)
-
{
-
int save_errno = 0;
-
if (self->fd >= 0) {
-
int fd = self->fd;
-
self->fd = -1;
-
Py_BEGIN_ALLOW_THREADS
-
if (close(fd) < 0)
-
save_errno = errno;
-
Py_END_ALLOW_THREADS
-
}
-
return save_errno;
-
}
-
-
static PyObject*
-
pyinotify_get_closed(pyinotify *self)
-
{
-
if (self->fd < 0)
-
Py_RETURN_TRUE;
-
else
-
Py_RETURN_FALSE;
-
}
-
-
PyDoc_STRVAR(pyinotify_add_watch_doc,
-
"add_watch(pathname, mask) -> wd.\n\
-
\n\
-
Add a watch to the inotify instance, and watch descriptor returned.");
-
-
static PyObject *
-
pyinotify_add_watch(pyinotify *self, PyObject *args)
-
{
-
PyObject *pathname;
-
uint32_t mask;
-
int wd;
-
-
if (!PyArg_ParseTuple(args, "O&I:add_watch",
-
PyUnicode_FSConverter, &pathname, &mask))
-
return NULL;
-
-
if (self->fd < 0)
-
return pyinotify_err_closed();
-
-
Py_BEGIN_ALLOW_THREADS
-
wd = inotify_add_watch(self->fd, PyBytes_AsString(pathname), mask);
-
Py_END_ALLOW_THREADS
-
Py_DECREF(pathname);
-
if (wd == -1) {
-
PyErr_SetFromErrno(PyExc_OSError);
-
return NULL;
-
}
-
-
return PyLong_FromLong(wd);
-
}
-
-
PyDoc_STRVAR(pyinotify_rm_watch_doc,
-
"rm_watch(wd) -> None.\n\
-
\n\
-
remove an existing watch descriptor from the inotify instance");
-
-
static PyObject *
-
pyinotify_rm_watch(pyinotify *self, PyObject *args)
-
{
-
int wd;
-
int result;
-
-
if (!PyArg_ParseTuple(args, "i:rm_watch", &wd))
-
return NULL;
-
-
if (self->fd < 0)
-
return pyinotify_err_closed();
-
-
Py_BEGIN_ALLOW_THREADS
-
result = inotify_rm_watch(self->fd, wd);
-
Py_END_ALLOW_THREADS
-
if (result == -1) {
-
PyErr_SetFromErrno(PyExc_OSError);
-
return NULL;
-
}
-
-
Py_RETURN_NONE;
-
}
-
-
PyDoc_STRVAR(pyinotify_read_doc,
-
"read(n) -> bytes\n\
-
\n\
-
Read events from the inotify instance.\n\
-
at most n bytes will be returned.");
-
-
static PyObject *
-
pyinotify_read(pyinotify *self, PyObject *args)
-
{
-
PyObject *evs;
-
Py_ssize_t result;
-
size_t nbyte;
-
char *buf;
-
-
if (!PyArg_ParseTuple(args, "I:read", &nbyte))
-
return NULL;
-
if (nbyte < EVENT_SIZE_MAX)
-
nbyte = EVENT_SIZE_MAX;
-
-
if (self->fd < 0)
-
return pyinotify_err_closed();
-
-
buf = (char *)PyMem_Malloc(nbyte);
-
if (buf == NULL) {
-
PyErr_NoMemory();
-
return NULL;
-
}
-
Py_BEGIN_ALLOW_THREADS
-
result = read(self->fd, buf, nbyte);
-
Py_END_ALLOW_THREADS
-
if (result < (Py_ssize_t)EVENT_SIZE_MIN) {
-
PyMem_Free(buf);
-
PyErr_SetFromErrno(PyExc_OSError);
-
return NULL;
-
}
-
evs = PyBytes_FromStringAndSize(buf, result);
-
PyMem_Free(buf);
-
-
return evs;
-
}
-
-
PyDoc_STRVAR(pyinotify_read_events_doc,
-
"read_events(n) -> [(wd, mask, cookie, name), (...)].\n\
-
\n\
-
Read events from the inotify instance.\n\
-
at most n bytes will be read, and then unpacked.");
-
-
static PyObject *
-
pyinotify_read_events(pyinotify *self, PyObject *args)
-
{
-
PyObject *elist = NULL;
-
PyObject *etuple = NULL;
-
struct inotify_event *eptr = NULL;
-
size_t nbyte = 0;
-
char *buf = NULL;
-
Py_ssize_t result = -1; /* bytes in #buf. */
-
Py_ssize_t cnt = 0; /* count the events in #buf. */
-
Py_ssize_t offset = 0; /* offset in #buf or #elist. */
-
-
if (!PyArg_ParseTuple(args, "I:read", &nbyte))
-
return NULL;
-
if (nbyte < EVENT_SIZE_MAX)
-
nbyte = EVENT_SIZE_MAX;
-
-
if (self->fd < 0)
-
return pyinotify_err_closed();
-
-
buf = (char *)PyMem_Malloc(nbyte);
-
if (buf == NULL) {
-
PyErr_NoMemory();
-
return NULL;
-
}
-
Py_BEGIN_ALLOW_THREADS
-
result = read(self->fd, buf, nbyte);
-
Py_END_ALLOW_THREADS
-
if (result < (Py_ssize_t)EVENT_SIZE_MIN) {
-
PyErr_SetFromErrno(PyExc_OSError);
-
goto error;
-
}
-
-
for (cnt = 0, offset = 0; offset < result; ++cnt) {
-
eptr = (struct inotify_event *)(buf + offset);
-
offset += sizeof(*eptr) + eptr->len;
-
}
-
elist = PyList_New(cnt);
-
if (elist == NULL) {
-
PyErr_NoMemory();
-
goto error;
-
}
-
-
eptr = (struct inotify_event *)buf;
-
for (offset = 0; offset < cnt; ++offset) {
-
if (eptr->len > 1) {
-
etuple = Py_BuildValue("iIIO&",
-
eptr->wd, eptr->mask, eptr->cookie,
-
PyUnicode_DecodeFSDefault, eptr->name);
-
} else {
-
etuple = Py_BuildValue("iIIy",
-
eptr->wd, eptr->mask, eptr->cookie, NULL);
-
}
-
if (etuple == NULL) {
-
Py_CLEAR(elist);
-
goto error;
-
}
-
if (PyList_SetItem(elist, offset, etuple) == -1) {
-
Py_CLEAR(etuple);
-
Py_CLEAR(elist);
-
goto error;
-
}
-
eptr = (struct inotify_event *)((char *)eptr + sizeof(*eptr) + eptr->len);
-
}
-
-
error:
-
PyMem_Free(buf);
-
return elist;
-
}
-
-
PyDoc_STRVAR(pyinotify_close_doc,
-
"close() -> None.\n\
-
\n\
-
close the inotify instance");
-
-
static PyObject *
-
pyinotify_close(pyinotify *self, PyObject *args)
-
{
-
errno = pyinotify_internal_close(self);
-
if (errno < 0) {
-
PyErr_SetFromErrno(PyExc_OSError);
-
return NULL;
-
}
-
Py_RETURN_NONE;
-
}
仔细浏览会发现各函数实现大同小异,这里只分析一个典型的函数pyinotify_read_events
行7~10:inotify对象的实际定义,唯一可见的成员就是inotify_init1返回的fd。
行143~147:pyinotify_read_events的文档,是函数实现的一部分,按照标准库的风格,硬编了几句英文。
行149~150:pyinotify_read_events的接口声明,该函数与Python代码直接交互,返回值必须是PyObject *,作为inotify对象的成员函数,第一个参数是对象本身PyObject *self,第二个参数是函数需要的所有参数PyObject *args
行161~162:解析pyinotify_read_events被调用时的入参,这是一个标准步骤,其他函数也是类似的。如果解析失败,PyArg_ParseTuple内部会设置异常类型,“return NULL”将告诉解释器终止当前代码运行,抛出异常。
行169~173:分配内存以存储内核将来返回的事件,最好利用Python heap提供的函数PyErr_NoMemory,当分配失败后,用户负责设置异常类型PyErr_NoMemory。
行174~176:读取内核事件,进行IO操作的前后,必须分别释放Py_BEGIN_ALLOW_THREADS和获取Py_END_ALLOW_THREADS全局解释锁(GIL,global interpreter lock),该原则适用所有C扩展。
行182~185:获取buf中事件的总数,struct inotify_event的真实内容是变长的,这里只能遍历一次buf才能获取事件总数。
行195~197:每个struct inotfiy_event构造一个tuple,read返回文件名是一个C字符串,要用PyUnicode_DecodeFSDefault解码为Python的str。
行206~209:每个struct inotfiy_event产生的etuple(对应Python tuple)都会保存到elist(对应Python list),注意此处PyList_SetItem会直接接管etuple对象,而不进行Py_INCREF(etuple),这是Python C API中为数不多的特例。如果执行失败,保证etuple, elist, buf的内存都要释放。
(更多内容参见下篇)
阅读(3012) | 评论(0) | 转发(1) |