PHP 内部结构:TSRMLS_FETCH 是如何工作的?

Posted

技术标签:

【中文标题】PHP 内部结构:TSRMLS_FETCH 是如何工作的?【英文标题】:PHP Internals: How does TSRMLS_FETCH Work? 【发布时间】:2018-09-07 10:53:58 【问题描述】:

php Internals TSRMLS_FETCH 宏是如何工作的?

根据PHP Manual

在开发扩展时,构建包含“tsrm_ls 未定义”的错误或导致该结果的错误源于 TSRMLS 在当前范围内未定义的事实,要解决此问题,请使用适当的宏声明函数以接受 TSRMLS,如果相关函数的原型无法更改,您必须在函数体内调用 TSRMLS_FETCH。

我了解使用适当的宏声明函数接受 TSRMLS 意味着使用 TSRMLS_C, TSRMLS_D, TSRMLS_CC, and TSRMLS_DC 定义或调用具有额外参数/参数的函数。

但是,如果相关函数的原型不能更改,则必须在函数体中调用 TSRMLS_FETCH 让我有点困惑。如果我同时查看 php-src here 和 here TSRMLS_FETCH 似乎是一个空宏。

所以这给我留下了一个问题——TSRMLS_FETCH 是如何工作的?在编译时还有其他东西填充这个宏吗?

【问题讨论】:

【参考方案1】:

首先,我不会过多关注手册中关于 PHP 内部的内容。它非常过时,很有可能在不久的将来从手册中删除。目前有两个专门介绍 PHP 内部的网站:PHPInternalsBook.com 和 PHPInternals.net(我为后者编写内容)。还有几个不错的博客可供关注,包括 Nikita's 和 Julien's。

PHP 5.x 系列中的 TSRM 非常具有侵入性。当想要从函数中访问任何Zend globals 时,选择是从函数调用中获取 TLS 内存指针(例如 pthread_getspecific,这相对昂贵)或通过函数参数传播 TLS 内存指针(a混乱和容易出错的事情,但更快的方法)。您提到的TSRMLS_FETCH 宏用于前一种方法。

在 PHP 7.x 中,传播 TLS 内存指针(通过 TSRMLS_[D|C]C? 宏)已被完全删除(尽管它们的宏仍被定义为向后兼容 - 它们只是不会做任何事情)。现在访问 TSRM 的 TLS 的首选方式是通过其静态缓存。这基本上只是一个线程局部全局变量,用于保存当前的 TLS 内存指针。

以下是相关的宏:

#define TSRMLS_CACHE _tsrm_ls_cache // the TLS global variable
#define TSRMLS_CACHE_DEFINE() TSRM_TLS void *TSRMLS_CACHE = NULL; // define it
#define TSRMLS_CACHE_UPDATE() TSRMLS_CACHE = tsrm_get_ls_cache() // update it - i.e. calls pthread_getspecific()
#define TSRMLS_CACHE_RESET()  TSRMLS_CACHE = NULL // reset it

使用上述宏确实需要特别注意适当地更新静态缓存(通常在GINIT,有时在RINIT,扩展阶段)。然而,这是一种更简洁的方式来提供对 TLS 内存指针的访问,而无需通过函数参数传播它或总是获取它(通过pthread_getspecific 和类似方法)对性能造成影响。

补充阅读:

Native TLS(在 PHP 7.0 中引入此更改的 RFC) Threads and PHP(Julien 在 TSRM 上的博文)

【讨论】:

谢谢@tpunt!今年早些时候,我偶然发现了 PHPInternals.net,它是一股清新的空气。我也不知道 Nikita 或 Julien 的网站,所以他们会排在我阅读列表的首位。【参考方案2】:

看看older versions of that file:

#define TSRMLS_FETCH()            void ***tsrm_ls = (void ***) ts_resource_ex(0, NULL)
#define TSRMG(id, type, element)  (((type) (*((void ***) tsrm_ls))[TSRM_UNSHUFFLE_RSRC_ID(id)])->element)
#define TSRMLS_D  void ***tsrm_ls
#define TSRMLS_DC , TSRMLS_D
#define TSRMLS_C  tsrm_ls
#define TSRMLS_CC , TSRMLS_C

似乎 PHP 在某些时候删除了对这些宏的支持,但将它们保留为空,以避免将外部代码分成两个版本,一个用于新 PHP,另一个用于旧 PHP。

【讨论】:

啊哈!看起来使用/需要这些宏的最后一个 PHP 版本是 PHP 5.6 -- github.com/php/php-src/blob/PHP-5.6/TSRM/TSRM.h。谢谢,这就是我要找的答案!【参考方案3】:

这段代码

#if ZEND_DEBUG
...
#else
#define TSRMLS_FETCH()
...
#endif

正在做以下事情:

如果您未处于调试模式,请将每次调用更改为宏 TSRMLS_FETCH(),而无需任何内容。

在这个例子中:

#if 0
#define TSRMLS_FETCH() printf("Bla");
#else
#define TSRMLS_FETCH()
#endif

int main(void) 

    TSRMLS_FETCH()
    return 0;

cpp demo.c(预处理器输出)返回:

int main(void) 

    return 0;

【讨论】:

感谢您的回复,并为有关宏 #ifdef 处理的有用一般信息 +1。我很感激。但是——在这两种情况下,TSRMLS_FETCH 宏都解析为空文本,所以这并不能直接回答我的问题。 我明白了,也许这个宏是在另一个include 或旧版本中定义的,这个宏会阻止在 ZEND_DEBUG 模式下使用代码。 这是第二个——旧版本的 PHP =7.0 不需要。看起来内部人员对它们进行了定义,以便他们和拥有多版本 PHP 扩展的人不需要进行大的重构。【参考方案4】:

在 PHP 8 中(在我写这篇文章的时候在 Alhpa fase 中)很多这些宏都被删除了。见https://github.com/php/php-src/blob/master/UPGRADING.INTERNALS

c. The following things have been removed from TSRM:
    - TSRMLS_DC
    - TSRMLS_D
    - TSRMLS_CC
    - TSRMLS_C
    - TSRMLS_FETCH
    - TSRMLS_FETCH_FROM_CTX
    - TSRMLS_SET_CTX
    - tsrm_new_interpreter_context
    - tsrm_set_interpreter_context
    - tsrm_free_interpreter_context
    - support for GNUPTH, SGI ST, and BETHREADS

【讨论】:

以上是关于PHP 内部结构:TSRMLS_FETCH 是如何工作的?的主要内容,如果未能解决你的问题,请参考以下文章

PHP中如何使用foreach结构遍历数组?

PHP7变量的内部实现

PHP常量

为啥 PHP 不对字符串使用内部智能字符串?

php底层原理之垃圾回收机制

如何使用 PHP 扩展覆盖内部 Zend 函数?