First describe the structure of the {resource} type in the kernel:
//Every resource is implemented through it.
typedef struct _zend_rsrc_list_entry
{
void *ptr;
int type;
int refcount;
}zend_rsrc_list_entry;
In the real world, we often need to operate some data that is difficult to express with scalar values, such as the handle of a file , and for C, it is just a pointer.
#include
int main(void)
{
FILE *fd;
fd = fopen("/home/jdoe/.plan", "r");
fclose(fd);
return 0;
}
The file descriptor of stdio in C language is a variable that matches each open file. It is actually a pointer of eleven FILE types, which will be used when the program interacts with the hardware. We can use the fopen function to open a file and obtain a handle. Then we only need to pass the handle to functions such as feof(), fread(), fwrite(), fclose(), and then we can perform subsequent operations on the file. Since this data cannot be directly represented by scalar data in C language, how can we encapsulate it to ensure that users can also use it in PHP language? This is the role of resource type variables in PHP! So it is also encapsulated through a zval structure. The implementation of the resource type is not complicated. Its value is actually just an integer. Based on this integer value, the kernel will go to a place similar to a resource pool to find the final required data.
Use of resource type variables
Resource type variables are also type-differentiated in implementation! In order to distinguish different types of resources, such as a file handle and a mysql link, we need to give them different classification names. First, we need to add this category to the program. This step can be done in MINIT:
#define PHP_SAMPLE_DESCRIPTOR_RES_NAME "Shanzhai file descriptor"
static int le_sample_descriptor;
ZEND_MINIT_FUNCTION(sample)
{
le_sample_descriptor = zend_register_list_destructors_ex(NULL, NULL, PHP_SAMPLE_DESCRIPTOR_RES_NAME,module_number);
return SUCCESS ;
}
//Additional information
#define register_list_destructors(ld, pld) zend_register_list_destructors((void (*)(void *))ld, (void (*)(void *))pld, module_number);
ZEND_API int zend_register_list_destructors(void (*ld)(void *), void (*pld)(void *), int module_number);
ZEND_API int zend_register_list_destructors_ex(rsrc_dtor_func_t ld, rsrc_dtor_func_t pld, char *type_name, int module_number);
Next, we To add the defined MINIT stage function to the extended module_entry, just replace the original "NULL, /* MINIT */" line:
ZEND_MINIT(sample), /* MINIT */
ZEND_MINIT_FUNCTION( ) macro is used to help us define functions in the MINIT phase, which we have already described in Chapter 1, but will be described in more detail in Chapters 12 and 3. What's important to know at this juncture is that the MINIT method is executed once when your extension is first loaded and before any requests have been received. Here you've used that opportunity to register destructor functionsthe NULL values, which you'll change soon enoughfor a resource type that will be henceforth known by a unique integer ID. When you see the zend_register_list_destructors_ex() function, you will definitely wonder if there is also a zend_register_list_destructors() function? Yes, there is indeed such a function whose parameters include less resource category names than the former. So what's the difference between the two?
eaco $re_1;
//resource(4) of type (copycat File handle)
echo $re_2;
//resource(4) of type (Unknown)
Create resources
We registered it in the kernel above A new resource type. The next step is to create resource variables of this type. Next, let us simply reimplement a fopen function, now called sample_open:
PHP_FUNCTION(sample_fopen)
{
FILE *fp;
char *filename, *mode;
int filename_len, mode_len;
if (zend_parse_parameters(ZEND_NUM_AR GS() TSRMLS_CC, "ss",&filename, &filename_len,&mode, &mode_len) == FAILURE)
error_docref(NULL TSRMLS_CC, E_WARNING,"Invalid filename or mode length");
RETURN_FALSE;fp = fopen(filename, mode);
if (!fp)
{
php_error_docref(NULL TSRMLS_CC, E_WARNING,"Unable to open %s using mode %s",filename, mode);
RETURN_FALSE;
}
// Add fp to the resource pool and mark it as le_sample_descriptor type.
ZEND_REGISTER_RESOURCE(return_value,fp,le_sample_descriptor);
}
If you have read the knowledge in the previous chapters, you should be able to guess what the last line of code does. It creates a new resource of type le_sample_descriptor. The value of this resource is fp. In addition, it adds this resource to a HashTable that stores resources, and assigns the corresponding digital Key of this resource to return_value.
Resources are not limited to file handles. We can apply for a piece of memory and use a pointer to it as a resource. So resources can correspond to any type of data.
Destroy resources
Everything in the world has joys and sorrows, life and death. It’s time for us to discuss how to destroy resources. The simplest one is to imitate fclose and write a sample_close() function, in which the release of a certain {resource: specifically refers to the value represented by PHP's resource type variable}.
But what happens if the user-side script releases a variable of a certain resource type through the unset() function? They don't know that its value ultimately corresponds to a FILE pointer, so they cannot use the fclose() function to release it. This FILE handle is likely to remain in the memory until the PHP program hangs up and is recycled by the OS. But in a normal Web environment, our servers will run for a long time. Is there no solution? Of course not, the answer lies in the NULL parameter, which is the first parameter and the second parameter of the zend_register_list_destructors_ex() function we called above to generate a new resource type. Both parameters each represent a callback parameter. The first callback function will be triggered when the resource variable of the corresponding type in the script is released, such as the scope ends or is unset().
The second callback function is used on a resource similar to a long link type, that is, this resource will always exist in memory after it is created and will not be released after the request ends. It will be called when the web server process terminates, which is equivalent to being called by the kernel during the MSHUTDOWN phase. Regarding persistent resources, we will discuss them in detail in the next section.
Let’s define the first callback function first.
static void php_sample_descriptor_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC)
{
FILE *fp = (FILE*)rsrc->ptr;
fclose(fp);
}
Then replace the zend_register_list_destructors_ex() function with it A parameter is NULL :
le_sample_descriptor = zend_register_list_destructors_ex(
php_sample_descriptor_dtor,
NULL,
PHP_SAMPLE_DESCRIPTOR_RES_NAME,
module_number); Now, if a resource variable of the above type is obtained in the script, when it is unset, or it is released by the kernel because the scope is executed When it is dropped, the underlying php_sample_descriptor_dtor will be called by the kernel to preprocess it. In this way, it seems that we don't need the sample_close() function at all!
$fp = sample_fopen("/home/jdoe/notes.txt", "r"); unset($fp);
?> After
unset($fp) is executed, the kernel will automatically call The php_sample_descriptor_dtor function is used to clean up some data corresponding to this variable. Of course, things are definitely not that simple. Let us keep this question in mind and continue reading.
Decoding Resources
We compare resource variables to bookmarks, but if there are only bookmarks, it will have absolutely no effect! We need to find the corresponding page through bookmarks. For resource variables, we must be able to find the corresponding final data through it!
ZEND_FUNCTION(sample_fwrite)
{
FILE *fp;
zval *file_resource;
char *data;
int data_len;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs" ,&file_resource, &data, &data_len) == FAILURE )
{ZEND_FETCH_RESource (FP, File*, & File_Resource, PHP_SAMPLE_DEScriptor_Res_name, Le_SAMPLE_DESCRIPTOR); Rn the number of bytes who is
* SuccessFully Written to the file */
Return_long (fwrite (data , 1, data_len, fp));
}
The r placeholder in the zend_parse_parameters() function represents the variable receiving the resource type, and its carrier is a zval*. Then let's take a look at the ZEND_FETCH_RESOURCE() macro function.
#define ZEND_FETCH_RESOURCE(rsrc, rsrc_type, passed_id,default_id, resource_type_name, resource_type)
rsrc = (rsrc_type) zend_fetch_resource(passed_id TSRMLS_CC,default_id, resource_type_name, NULL,1, resource_type);
ZEND_VERIFY_RES CE(rsrc);
//at In our example, it looks like this:
fp = (FILE*) zend_fetch_resource(&file_descriptor TSRMLS_CC, -1,PHP_SAMPLE_DESCRIPTOR_RES_NAME, NULL,1, le_sample_descriptor);
if (!fp)
{
RETURN_FALSE;
}
zend_fetch_resource( ) is a layer of encapsulation of zend_hash_find(). It uses a digital key to find the final required data in a HashTable that stores various {resources}. After finding it, we use the ZEND_VERIFY_RESOURCE() macro function to verify the data. From the above code, we can see that NULL and 0 cannot be used as a resource. In the above example, the zend_fetch_resource() function first obtains the resource type represented by le_sample_descriptor. If the resource does not exist or the received zval is not a resource type variable, it will return NULL and throw the corresponding error message. The final ZEND_VERIFY_RESOURCE() macro function will automatically return if an error is detected, allowing us to break away from error detection and focus more on the main logic of the program. Now that we have obtained the corresponding FILE*, let's use fwrite() to write some data into it! .
To avoid having zend_fetch_resource() generate an error on failure, simply pass NULL for the resource_type_name parameter. Without a meaningful error message to display, zend_fetch_resource() will fail silently instead.
We can also use another method to get our final desired data.
ZEND_FUNCTION(sample_fwrite)
{
FILE *fp;
zval *file_resource;
char *data;
int data_len, rsrc_type;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs",&file_resource, &data, &data_len) = = FAILURE ) {
use php_error_docref(NULL TSRMLS_CC, E_WARNING," Invalid resource provided");
RETURN_FALSE;
}
RETURN_LONG(fwrite(data, 1, data_len, fp));
}
You can choose which form to use according to your own habits, but it is recommended to use the ZEND_FETCH_RESOURCE() macro function .
Forcing Destruction
We still have an unresolved question above, that is, is unset($fp) similar to what we implemented above really omnipotent? Of course not, take a look at the following code:
$fp = sample_fopen("/home/jdoe/world_domination.log", "a");
$evil_log = $fp;
unset($fp);
?>
This time, $fp and $evil_log share a zval. Although $fp is released, its zval will not be released because $evil_log is still in use. In other words, the file handle represented by $evil_log is still writable! So in order to avoid this kind of error, we really need to close it manually! The sample_close() function must exist!
PHP_FUNCTION(sample_fclose)
{ FILE *fp;
zval *file_resource;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r",&file_resource) == FAILURE ) {
RE TURN_NULL();
}
/* While it's not necessary to actually fetch the
* FILE* resource, performing the fetch provides
* an opportunity to verify that we are closing
* the correct resource type. */
ZEND_FETCH_RESOURCE(fp, FILE*, &file_resource, -1,PHP_SAMPLE_DESCRIPTOR_RES_NAME, le_sample_descriptor);
/* Force the resource into self-destruct mode */
zend_hash_index_del(&EG(regular_list),Z_RESVAL_P(file_ resource));
RETURN_TRUE;
}
This deletion operation also shows again that resource data is stored in HashTable. Although we can operate this HashTable that stores resources through functions such as zend_hash_index_find() or zend_hash_next_index_insert(), this is never a good idea, because in subsequent versions, PHP may modify the implementation of this part. The above method will not work, so for better compatibility, please use standard macro functions or API functions. When we delete data in the HashTable of EG (regular_list), a dtor function is called back, which calls the corresponding dtor function implementation according to the category of the resource variable, which is the first parameter when we call the zend_register_list_destructors_ex() function.
In many places, we will see a zend_list_delete() macro function specially used for deletion, because it takes into account the reference count of the resource data itself.