安爸-超级家庭

PyImport_AppendInittab函数解析

安爸 发布于

源码位置:cpython\Python\import.c

一.PyImport_AppendInittab

这个函数提供了一个简便方法,用于在Python初始化前向内置模块表中添加单个模块条目。此函数通常被嵌入式应用程序用来在调用Py_Initialize()之前注册自定义的C扩展模块,使其成为Python解释器内置的一部分,无需通过普通的导入机制即可使用。

`/ Shorthand to add a single entry given a name and a function /

int
PyImport_AppendInittab(constchar name, PyObject (*initfunc)(void))
{
struct _inittabnewtab[2];

if (INITTAB != NULL) {
        Py_FatalError("PyImport_AppendInittab() may not be called after Py_Initialize()");
    }

memset(newtab, '\0', sizeof newtab);

    newtab[0].name = name;
    newtab[0].initfunc = initfunc;

return PyImport_ExtendInittab(newtab);
}
`

1.创建临时表结构

  • 创建一个包含两个元素的_inittab结构体数组newtab
  • 第一个元素用于存储新模块信息
  • 第二个元素作为表的终止标记(全NULL)

2.检查Python初始化状态

  • 如果INITTAB不为NULL,表示Python已经初始化
  • 此时不允许添加新的内置模块,会触发致命错误
  • 这是一个安全检查,确保模块在Python启动前注册

3.初始化表条目

  • 使用memset将整个newtab数组清零
  • 设置第一个条目的模块名称和初始化函数
  • 第二个条目保持为NULL,作为表的终止标记

4.添加到全局表

  • 调用PyImport_ExtendInittab(newtab)将新模块添加到全局表中
  • 返回添加操作的结果(0表示成功,-1表示失败)

二.PyImport_ExtendInittab

这个函数允许嵌入式应用程序向 Python 内置模块表添加自定义条目。

`/ API for embedding applications that want to add their own entries
   to the table of built-in modules.  This should normally be called
   
before* Py_Initialize().  When the table resize fails, -1 is
   returned and the existing table is unchanged.

   After a similar function by Just van Rossum. */

int
PyImport_ExtendInittab(struct _inittab newtab)
{
struct _inittab 
p;
size_t i, n;
int res = 0;

if (INITTAB != NULL) {
        Py_FatalError("PyImport_ExtendInittab() may not be called after Py_Initialize()");
    }

/ Count the number of entries in both tables /
for (n = 0; newtab[n].name != NULL; n++)
        ;
if (n == 0)
return0; / Nothing to do /
for (i = 0; PyImport_Inittab[i].name != NULL; i++)
        ;

/ Force default raw memory allocator to get a known allocator to be able
       to release the memory in _PyImport_Fini2()
/
/ Allocate new memory for the combined table /
    p = NULL;
if (i + n <= SIZE_MAX / sizeof(struct _inittab) - 1) {
size_t size = sizeof(struct _inittab) * (i + n + 1);
        p = _PyMem_DefaultRawRealloc(inittab_copy, size);
    }
if (p == NULL) {
        res = -1;
goto done;
    }

/ Copy the tables into the new memory at the first call
       to PyImport_ExtendInittab().
/
if (inittab_copy != PyImport_Inittab) {
memcpy(p, PyImport_Inittab, (i+1)  sizeof(struct _inittab));
    }
memcpy(p + i, newtab, (n + 1)
 sizeof(struct _inittab));
    PyImport_Inittab = inittab_copy = p;
done:
return res;
}
`

该函数目的为让嵌入 Python 的应用程序可以注册自己的内置模块,必须在 Py_Initialize() 调用之前使用。它的工作流程如下:

1.初始检查

  • 如果 INITTAB 不为空,说明 Python 已初始化,此时调用会导致程序终止

2.计算表项数量

  • 统计新表 newtab 中的条目数量(n
  • 如果 n 为 0,直接返回 0(无需处理)
  • 统计现有表 PyImport_Inittab 中的条目数量(i

3.内存分配

  • 检查整数溢出风险
  • 计算所需内存:sizeof(struct _inittab) * (i + n + 1)
  • 使用 _PyMem_DefaultRawRealloc 分配内存
  • 分配失败时设置返回值 -1

4.合并表内容

  • 首次调用时复制 PyImport_Inittab 到新内存
  • newtab 内容复制到新内存的后半部分
  • 更新全局指针 PyImport_Inittabinittab_copy

三._PyMem_DefaultRawRealloc

源码位置:cpython\Objects\obmalloc.c

_PyMem_DefaultRawRealloc函数是Python内存管理系统中的一个关键组件,用于重新分配内存块。这个函数提供了一个默认的内存重分配实现,它是不受PyMem_SetAllocator()影响的标准内存分配器。

`void 
_PyMem_DefaultRawRealloc(void 
ptr, size_t size)
{

ifdef Py_DEBUG

return _PyMem_DebugRawRealloc(&_PyRuntime.allocators.debug.raw, ptr, size);

else

return _PyMem_RawRealloc(NULL, ptr, size);

endif

}
`

ptr表示指向要重新分配的现有内存块的指针,size表示请求的新内存大小(以字节为单位)。函数根据Python构建模式选择不同的内存分配策略:

1.调试模式下Py_DEBUG

  • 调用_PyMem_DebugRawRealloc函数
  • 传递&_PyRuntime.allocators.debug.raw作为上下文
  • 启用额外的内存边界检查、填充和追踪功能

2.非调试模式下

  • 调用简单的_PyMem_RawRealloc函数
  • 传递NULL作为上下文参数
  • 本质上是对标准C库realloc()的轻量级包装

3.使用场景

这个函数主要在Python内部使用,作为原始内存分配器的默认实现,确保即使自定义分配器被设置,某些核心操作仍能正常工作。

四._PyMem_DebugRawRealloc

源码位置:cpython\Objects\obmalloc.c

这个函数是Python内存调试系统的一部分,用于重新分配内存块,同时保持完整的调试信息。这个函数通过在内存块周围添加额外的标记,帮助开发者发现诸如缓冲区溢出、使用已释放内存等问题。

`void 
_PyMem_DebugRawRealloc(void 
ctx, void *p, size_t nbytes)
{
if (p == NULL) {
return _PyMem_DebugRawAlloc(0, ctx, nbytes);
    }

debug_alloc_api_t api = (debug_alloc_api_t )ctx;
uint8_t head;        / base address of malloc'ed pad block /
uint8_t 
data;        / pointer to data bytes /
uint8_t r;
uint8_t 
tail;        / data + nbytes == pointer to tail pad bytes /
size_t total;         / 2 SST + nbytes + 2 SST /
size_t original_nbytes;

define ERASED_SIZE 64

    _PyMem_DebugCheckAddress(func, api->api_id, p);

    data = (uint8_t )p;
    head = data - 2
SST;
    original_nbytes = read_size_t(head);
if (nbytes > (size_t)PY_SSIZE_T_MAX - PYMEM_DEBUG_EXTRA_BYTES) {
/ integer overflow: can't represent total as a Py_ssize_t /
returnNULL;
    }
    total = nbytes + PYMEM_DEBUG_EXTRA_BYTES;

    tail = data + original_nbytes;

ifdef PYMEM_DEBUG_SERIALNO

size_t block_serialno = read_size_t(tail + SST);

endif

ifndef Py_GIL_DISABLED

/ Mark the header, the trailer, ERASED_SIZE bytes at the begin and
       ERASED_SIZE bytes at the end as dead and save the copy of erased bytes.
     
/
uint8_t save[2ERASED_SIZE];  / A copy of erased bytes. /
if (original_nbytes <= sizeof(save)) {
memcpy(save, data, original_nbytes);
memset(data - 2 
SST, PYMEM_DEADBYTE,
               original_nbytes + PYMEM_DEBUG_EXTRA_BYTES);
    }
else {
memcpy(save, data, ERASED_SIZE);
memset(head, PYMEM_DEADBYTE, ERASED_SIZE + 2  SST);
memcpy(&save[ERASED_SIZE], tail - ERASED_SIZE, ERASED_SIZE);
memset(tail - ERASED_SIZE, PYMEM_DEADBYTE,
               ERASED_SIZE + PYMEM_DEBUG_EXTRA_BYTES - 2 
SST);
    }

endif

/ Resize and add decorations. /
    r = (uint8_t )api->alloc.realloc(api->alloc.ctx, head, total);
if (r == NULL) {
/
if realloc() failed: rewrite header and footer which have
           just been erased */
        nbytes = original_nbytes;
    }
else {
        head = r;

ifdef PYMEM_DEBUG_SERIALNO

        bumpserialno();
        block_serialno = serialno;

endif

    }
    data = head + 2*SST;

write_size_t(head, nbytes);
    head[SST] = (uint8_t)api->api_id;
memset(head + SST + 1, PYMEM_FORBIDDENBYTE, SST-1);

    tail = data + nbytes;
memset(tail, PYMEM_FORBIDDENBYTE, SST);

ifdef PYMEM_DEBUG_SERIALNO

write_size_t(tail + SST, block_serialno);

endif

ifndef Py_GIL_DISABLED

/ Restore saved bytes. /
if (original_nbytes <= sizeof(save)) {
memcpy(data, save, Py_MIN(nbytes, original_nbytes));
    }
else {
size_t i = original_nbytes - ERASED_SIZE;
memcpy(data, save, Py_MIN(nbytes, ERASED_SIZE));
if (nbytes > i) {
memcpy(data + i, &save[ERASED_SIZE],
                   Py_MIN(nbytes - i, ERASED_SIZE));
        }
    }

endif

if (r == NULL) {
returnNULL;
    }

if (nbytes > original_nbytes) {
/ growing: mark new extra memory clean /
memset(data + original_nbytes, PYMEM_CLEANBYTE,
               nbytes - original_nbytes);
    }

return data;
}
`

1.初始检查

  • 如果输入指针 p 是 NULL,直接调用 _PyMem_DebugRawAlloc 分配新内存并返回
  • 否则从上下文 ctx 中获取调试API信息

2.内存布局解析

  • 数据指针 data 就是传入的 p
  • 头部指针 head 位于数据前 2*SST 字节处
  • 从头部读取原始分配的字节数 original_nbytes
  • 检查新的字节数是否会导致整数溢出

3.保存原始数据 (在非GIL禁用模式下)

  • 定义局部缓冲区 save[2*ERASED_SIZE] 保存原始数据
  • 根据原始大小采取不同的策略:
    • 如果原始数据较小,全部保存
    • 否则只保存开头和结尾的 ERASED_SIZE 字节
  • 同时将头部、尾部和部分数据区域标记为”死亡”状态(PYMEM_DEADBYTE

4.重新分配内存

  • 调用底层的 realloc 重新分配内存
  • 如果失败(返回NULL),保持使用原始大小
  • 成功则更新 head 指针和序列号(如果启用了序列号功能)

5.重建内存块结构

  • 重新写入大小信息、API ID和边界标记
  • 在数据区域前后添加用于检测缓冲区溢出的”禁止字节”

6.恢复原始数据

  • 将之前保存的数据复制回新分配的内存
  • 如果是扩大内存,将新增部分标记为”干净”状态(PYMEM_CLEANBYTE

7.返回

  • 如果重新分配失败,返回NULL
  • 否则返回指向数据区域的指针(不是原始分配的内存块起始位置)

五._PyMem_RawRealloc

源码位置:cpython\Objects\obmalloc.c

这段代码是Python内存管理系统中的_PyMem_RawRealloc函数实现,它是对C标准库realloc函数的封装。

void * _PyMem_RawRealloc(void *Py_UNUSED(ctx), void *ptr, size_t size) { if (size == 0)         size = 1; return realloc(ptr, size); }

1.函数签名

void *_PyMem_RawRealloc(void *Py_UNUSED(ctx), void *ptr, size_t size)

  • 返回类型:void * – 指向重新分配后内存区域的指针
  • 参数:
    • void *Py_UNUSED(ctx) – 上下文参数,使用Py_UNUSED宏标记为未使用
    • void *ptr – 待重新分配的内存块指针
    • size_t size – 请求的新内存大小(字节数)

2.函数功能

  • 重新分配指定内存块的大小
  • 如果size为0,将其设置为1(第83-84行)
  • 调用标准C库的realloc函数执行实际的内存重分配

3.特殊处理

  • size为0时设置为1的处理是为了解决不同系统上realloc(ptr, 0)行为不一致的问题
  • 某些系统可能将realloc(ptr, 0)视为free(ptr)并返回NULL
  • 通过确保至少分配1字节,Python保证了跨平台的一致行为

这是Python内存分配系统中的一个底层函数,它属于”raw”内存分配器系列,直接与系统内存分配交互,不使用Python的对象分配机制。

参考文献

[0] PyImport_AppendInittab函数解析:https://z0yrmerhgi8.feishu.cn/wiki/DlbEwVBnXil6erk8VDVcuFQfnmT

[1] Python初始化配置:https://docs.python.org/zh-cn/3.14/c-api/init\_config.html#


知识星球服务内容:Dify源码剖析及答疑,Dify对话系统源码,NLP电子书籍报告下载,公众号所有付费资料。加微信buxingtianxia21进NLP工程化资料群

(文:NLP工程化)

PyImport_AppendInittab函数解析最先出现在每时AI


扫描二维码,在手机上阅读