ble 中,当一个新的资源被分配时,就会创建一个 tsrm_resource_type。 所有 tsrm_resource_type 以数组的方式组成 tsrm_resource_table,其下标就是这个资源的 ID。 其实我们可以将 tsrm_resource_table 看做一个 HASH 表,key 是资源 ID,value 是 tsrm_resource_type 结构(任何一个数组都可以看作一个 HASH 表,如果数组的key 值有意义的话)。
在分配了资源 ID 后,PHP 内核会接着遍历所有线程为每一个线程的 tsrm_tls_entry 分配这个线程全局变量需要的内存空间。 这里每个线程全局变量的大小在各自的调用处指定(也就是全局变量结构体的大小)。最后对地址存放的全局变量进行初始化。为此我画了一张图予以说明
上图中还有一个困惑的地方,tsrm_tls_table
的元素是如何添加的,链表是如何实现的。我们把这个问题先留着,后面会讨论。
每一次的 ts_allocate_id 调用,PHP 内核都会遍历所有线程并为每一个线程分配相应资源, 如果这个操作是在PHP生命周期的请求处理阶段进行,岂不是会重复调用?
PHP 考虑了这种情况,ts_allocate_id 的调用在模块初始化时就调用了。
TSRM 启动后,在模块初始化过程中会遍历每个扩展的模块初始化方法, 扩展的全局变量在扩展的实现代码开头声明,在 MINIT 方法中初始化。 其在初始化时会知会 TSRM 申请的全局变量以及大小,这里所谓的知会操作其实就是前面所说的 ts_allocate_id 函数。 TSRM 在内存池中分配并注册,然后将资源ID返回给扩展。
全局变量的使用
以标准的数组扩展为例,首先会声明当前扩展的全局变量。
ZEND_DECLARE_MODULE_GLOBALS(array)
然后在模块初始化时会调用全局变量初始化宏初始化 array,比如分配内存空间操作。
static void php_array_init_globals(zend_array_globals *array_globals)
{
memset(array_globals, 0, sizeof(zend_array_globals));
}
/* code... */
PHP_MINIT_FUNCTION(array) /* {{{ */
{
ZEND_INIT_MODULE_GLOBALS(array, php_array_init_globals, NULL);
/* code... */
}
这里的声明和初始化操作都是区分ZTS和非ZTS。
#ifdef ZTS
#define ZEND_DECLARE_MODULE_GLOBALS(module_name) \
ts_rsrc_id module_name##_globals_id;
#define ZEND_INIT_MODULE_GLOBALS(module_name, globals_ctor, globals_dtor) \
ts_allocate_id(&module_name##_globals_id, sizeof(zend_##module_name##_globals), (ts_allocate_ctor) globals_ctor, (ts_allocate_dtor) globals_dtor);
#else
#define ZEND_DECLARE_MODULE_GLOBALS(module_name) \
zend_##module_name##_globals module_name##_globals;
#define ZEND_INIT_MODULE_GLOBALS(module_name, globals_ctor, globals_dtor) \
globals_ctor(&module_name##_globals);
#endif
对于非ZTS的情况,直接声明变量,初始化变量;对于ZTS情况,PHP内核会添加TSRM,不再是声明全局变量,而是用ts_rsrc_id代替,初始化时也不再是初始化变量,而是调用ts_allocate_id函数在多线程环境中给当前这个模块申请一个全局变量并返回资源ID。其中,资源ID变量名由模块名加global_id组成。
如果要调用当前扩展的全局变量,则使用:ARRAYG(v),这个宏的定义:
#ifdef ZTS
#define ARRAYG(v) TSRMG(array_globals_id, zend_array_globals *, v)
#else
#define ARRAYG(v) (array_globals.v)
#endif
如果是非ZTS则直接调用全局变量的属性字段,如果是ZTS,则需要通过TSRMG获取变量。
TSRMG的定义:
#define TSRMG(id, type, element) (((type) (*((void ***) tsrm_ls))[TSRM_UNSHUFFLE_RSRC_ID(id)])->element)
去掉这一堆括号,TSRMG宏的意思就是从tsrm_ls中按资源ID获取全局变量,并返回对应变量的属性字段。
那么现在的问题是这个 tsrm_ls
从哪里来的?
tsrm_ls 的初始化
tsrm_ls
通过 ts_resource(0)
初始化。展开实际最后调用的是 ts_resource_ex(0,NULL)
。下面将 ts_resource_ex
一些宏展开,线程以 pthread
为例。
#define THREAD_HASH_OF(thr,ts) (unsigned long)thr%(unsigned long)ts
static MUTEX_T tsmm_mutex;
void *ts_resource_ex(ts_rsrc_id id, THREAD_T *th_id)
{
THREAD_T thread_id;
int hash_value;
tsrm_tls_entry *thread_resources;
// tsrm_tls_table 在 tsrm_startup 已初始化完毕
if(tsrm_tls_table) {
// 初始化时 th_id = NULL;
if (!th_id) {
//第一次为空 还未执行过 pthread_setspecific 所以 thread_resources 指针为空
thread_resources = pthread_getspecific(tls_key);
if(thread_resources){
TSRM_SAFE_RETURN_RSRC(thread_resources->storage, id, thread_resources->count);
}
thread_id = pthread_self();
} else {
thread_id = *th_id;
}
}
// 上锁
pthread_mutex_