- URL: https://www.laruence.com/en/2008/08/03/201.html
- Please include attribution when republishing.
If you've ever written a PHP extension or studied the PHP source code, you've seen this thing all over the place. Yet there's rarely any material explaining what it actually is.
The most common answer to "what is this thing?" is "you don't need to care what it is — just use it 'here' and 'there', and if the compiler tells you tsrm_ls is missing, just add it." This answer, though somewhat perfunctory, actually makes some sense, because the Zend Engine has made this macro so complex, and for a developer just starting out with PHP extensions, knowing what it is doesn't help much.
But I'm the kind of person who likes to get to the bottom of things. So if you happen to have some free time right now and the patience to understand what this thing is, please read on.
Glossary:
TSRM
Thread Safe Resource Manager. This is an often-overlooked, rarely-discussed "layer" that lives in the /TSRM directory of the PHP source. Under normal circumstances this layer is only enabled when explicitly needed (e.g. Apache2 + worker MPM, a thread-based MPM). For Apache on Win32, which is multi-threaded, this layer is always enabled.
ZTS
Zend Thread Safety. When TSRM is enabled, this macro named ZTS gets defined.
tsrm_ls
TSRM Local Storage. This is the variable name actually used in extensions and Zend to refer to the TSRM storage.
TSRMLS_??
This is a family of (4) macros that implement TSRM depending on whether the ZTS macro is defined. The 4 macros are:
#define TSRMLS_C tsrm_ls #define TSRMLS_D void *** tsrm_ls #define TSRMLS_CC ,tsrm_ls #define TSRMLS_DS ,void ***tsrm_ls // note the leading comma
As we all know, in C or PHP programming there are two ways to access the same variable across multiple functions. One is to pass it as a parameter, e.g.:
#include <stdio.h>
void output_func(char *message)
{
printf("%s\n", message);
}
int main(int argc, char *argv[])
{
output_func(argv[0]);
return 0;
}
The other way is to store the variable in a scope one level above the function (of course, for PHP you have to explicitly declare the Global variable — this has to do with PHP's scope implementation via the active symbol table, which is out of scope here and will be covered in a future article), e.g.:
#include <stdio.h>
char *message;
void output_func(void)
{
printf("%s\n", message);
}
int main(int argv, char *argv[])
{
message = argv[0];
output_func();
return 0;
}
For PHP using the second approach: under a typical single-threaded model such as PHP CLI, Apache1, or Apache2 + prefork MPM (also a multi-process model), it can be used safely without error. The global variable is created at MINIT/RINIT time, accessible throughout the process lifetime / request handling, and released at MSHUTDOWN/RSHUTDOWN time.
But under a multi-threaded model this approach is no longer safe — e.g. Apache2 + worker MPM and IIS. In these cases all threads share the same process address space, meaning multiple threads share one global variable, which creates a race. To put it the way a C programmer would: the global variable is not thread-safe here.
To solve this problem while staying compatible with single-threaded mode, Zend uses a mechanism called "Non_global Globals". The main idea is that, for a multi-threaded model, each time a new thread is created, a separate block of memory is allocated that holds a copy of the global variable. These blocks are strung together in a Vector, managed uniformly by Zend. To illustrate this, let's look at the following example:
typedef struct _zend_myextension_globals {
int foo;
char *bar;
} zend_myextension_globals;
#ifdef ZTS // if TSRM is enabled
int myextension_globals_id;
#else
zend_myextension_globals myextension_globals;
#endif
/* called when a thread is created */
static void php_myextension_globals_ctor(zend_myextension_globals *myext_globals TSRMLS_DC)
{
myext_globals->foo = 0;
myext_globals->bar = NULL;
}
/* called when a thread ends */
static void php_myextension_globals_dtor(zend_myextension_globals *myext_globals TSRMLS_DC)
{
if (myext_globals->bar) {
efree(myext_globals->bar);
}
}
PHP_MINIT_FUNCTION(myextension)
{
#ifdef ZTS
ts_allocate_id(&myextension_globals_id, sizeof(zend_myextension_globals),
php_myextension_globals_ctor, php_myextension_globals_dtor);
#else
php_myextension_globals_ctor(&myextension_globals TSRMLS_CC);
#endif
return SUCCESS;
}
PHP_MSHUTDOWN_FUNCTION(myextension)
{
#ifndef ZTS
php_myextension_globals_dtor(&myextension_globals TSRMLS_CC);
#endif
return SUCCESS;
}
This example first declares a global variable "zend_myextension_globals" to the TSRM layer:
ts_allocate_id(&myextension_globals_id, sizeof(zend_myextension_globals),
php_myextension_globals_ctor, php_myextension_globals_dtor);
It specifies the size of the global variable to request, the constructor and the destructor, and saves the offset (Index) of this generated global variable in the Vector into myextension_globals_id. For the case where TSRM is not enabled, the global variable is simply created.
If you ask me "why is there still a TSRMLS_CC when TSRM isn't enabled?", that means you haven't been confused by me yet 😉 — well, when ZTS is not set (TSRM not enabled), TSRMLS_CC is replaced by the compiler with nothing, because:
#ifdef ZTS
#define TRSMLS_CC ,tsrm_ls
#else
#define TSRMLS_CC
#endif
The reason for still specifying TSRMLS_CC when TSRM isn't enabled is purely to keep the code consistent.
Now that the global variable is set up, the next question is: how do we access it? Look at the following code:
#ifdef ZTS
# define MYEXTENSION_G(v) \
(((zend_myextension_globals*)(*((void ***)tsrm_ls))[(myextension_globals_id)-1])->v)
#else
# define MYEXTENSION_G(v) (myextension_globals.v)
#endif
Heh, got it? When ZTS is not set, the macro MYEXTENSION_G(V) simply reduces to the global variable myextension_globals.v; when TSRM is enabled, MYEXTENSION_G(V) is transformed into looking up the global variable to access within the Vector, based on my_extension_globals_id.
Now, as long as you use MYEXTENSION_G to access your global variables in your code, and add TSRMLS_CC to the parameter list of any function that uses the global variable, you'll have thread safety and code consistency under both single-threaded and multi-threaded models. 🙂
Be First to Comment