forked from walu/phpbook
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path9.2.txt
More file actions
349 lines (319 loc) · 15.1 KB
/
9.2.txt
File metadata and controls
349 lines (319 loc) · 15.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
Persistent Resources
9.1
9.3
#
<p>通常情况下,像{资源}这类复合类型的数据都会占用大量的硬件资源,比如内存、CPU以及网络带宽。对于使用频率超级高的数据库链接,我们可以获取一个长链接,使其不会在脚本结束后自动销毁,一旦创建便可以在各个请求中直接使用,从而减少每次创建它的消耗。Mysql的长链接在PHP内核中其实就是一种Persistent Resources。</p>
<p>Memory Allocation</p>
<p>前面的章节里我们接触了emalloc()之类的以e开头的内存管理函数,通过它们申请的内存都会在被内核自动的进行垃圾回收的操作。而对于一个Persistent Resources来说,我们是绝对不希望它在脚本结束后被回收的。</p>
<p>
假设我们需要在我们的{资源}中同时保存文件名和文件句柄两个数据,现在我们就需要自己定义个结构了:</p>
<code c>
typedef struct _php_sample_descriptor_data
{
char *filename;
FILE *fp;
}php_sample_descriptor_data;
</code>
<p>当然,因为结构变了(之前是个FILE*),我们之前的代码也需要跟着改动。这里还没有涉及到Persistent Resources,仅仅是换了一种{资源}结构</p>
<code c>
static void php_sample_descriptor_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC)
{
php_sample_descriptor_data *fdata = (php_sample_descriptor_data*)rsrc->ptr;
fclose(fdata->fp);
efree(fdata->filename);
efree(fdata);
}
PHP_FUNCTION(sample_fopen)
{
php_sample_descriptor_data *fdata;
FILE *fp;
char *filename, *mode;
int filename_len, mode_len;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss",&filename, &filename_len,&mode, &mode_len) == FAILURE)
{
RETURN_NULL();
}
if (!filename_len || !mode_len) {
php_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;
}
fdata = emalloc(sizeof(php_sample_descriptor_data));
fdata->fp = fp;
fdata->filename = estrndup(filename, filename_len);
ZEND_REGISTER_RESOURCE(return_value, fdata,le_sample_descriptor);
}
PHP_FUNCTION(sample_fwrite)
{
php_sample_descriptor_data *fdata;
zval *file_resource;
char *data;
int data_len;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs",&file_resource, &data, &data_len) == FAILURE )
{
RETURN_NULL();
}
ZEND_FETCH_RESOURCE(fdata, php_sample_descriptor_data*,&file_resource, -1,PHP_SAMPLE_DESCRIPTOR_RES_NAME, le_sample_descriptor);
RETURN_LONG(fwrite(data, 1, data_len, fdata->fp));
}
</code>
<div class="tip-common">我们这里没有重写sample_fclose()函数,你可以尝试着自己实现它。</div>
<p>现在编译运行,所以的代码的结果都非常正确,我们还可以在内核中获取每个{资源}对应的文件名称了。</p>
<code c>
PHP_FUNCTION(sample_fname)
{
php_sample_descriptor_data *fdata;
zval *file_resource;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r",&file_resource) == FAILURE )
{
RETURN_NULL();
}
ZEND_FETCH_RESOURCE(fdata, php_sample_descriptor_data*,&file_resource, -1,PHP_SAMPLE_DESCRIPTOR_RES_NAME, le_sample_descriptor);
RETURN_STRING(fdata->filename, 1);
}
</code>
<p>现在,Persistent Resources来了!</p>
<h3>Delayed Destruction</h3>
<p>在前面我们删除一个{资源}的时候,其实是去EG(regular_list)中将其删掉,EG(regular_list)存储着所有的只用在当前请求的{资源}。</p>
<p>
Persistent resources,存储在另一个HashTable中:EG(persistent_list)。其与EG(regular_list)有个明显的区别,那就是它每个值的索引都是字符串类型的,而且它的每个值也不会在每次请求结束后被释放掉,只能我们手动通过zend_hash_del()来删除,或者在进程结束后类似与MSHUTDOWN阶段将EG(persistent_list)整体清除,最常见的情景便是操作系统关闭了Web Server。</p>
<p>EG(persistent_list)对其元素也有自己的dtor回调函数,和EG(regular_list)一样,它将根据其值的类型去调用不同的回调函数,我们这一次注册回调函数的时,需要用到zend_register_list_destructors_ex()函数的第二个参数,第一个则被赋成NULL。</p>
<p>在底层的实现中,persistent和regular{资源}是分别在不同的地方存储的,也分别拥有各自不同的释放函数。但在我们为脚本提供的函数中,却希望能够封装这种差异,从而使我们的用户使用起来更加方便快捷。</p>
<code c>
static int le_sample_descriptor_persist;
static void php_sample_descriptor_dtor_persistent(zend_rsrc_list_entry *rsrc TSRMLS_DC)
{
php_sample_descriptor_data *fdata = (php_sample_descriptor_data*)rsrc->ptr;
fclose(fdata->fp);
pefree(fdata->filename, 1);
pefree(fdata, 1);
}
PHP_MINIT_FUNCTION(sample)
{
le_sample_descriptor = zend_register_list_destructors_ex(php_sample_descriptor_dtor, NULL,PHP_SAMPLE_DESCRIPTOR_RES_NAME, module_number);
le_sample_descriptor_persist =zend_register_list_destructors_ex(NULL, php_sample_descriptor_dtor_persistent,PHP_SAMPLE_DESCRIPTOR_RES_NAME, module_number);
return SUCCESS;
}
</code>
<p>我们并没有为这两种{资源}起不同的名字,以防使用户产生疑惑。</p>
<p>现在我们的PHP扩展中引进了一种新的{资源},所以我们需要改写一下我们上面的函数,<b>尽量使</b>用户使用时感觉不到这种差异。</p>
<code c>
//sample_fopen()
PHP_FUNCTION(sample_fopen)
{
php_sample_descriptor_data *fdata;
FILE *fp;
char *filename, *mode;
int filename_len, mode_len;
zend_bool persist = 0;
//类比一下mysql_connect函数的最后一个参数。
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,"ss|b",&filename, &filename_len, &mode, &mode_len,&persist) == FAILURE)
{
RETURN_NULL();
}
if (!filename_len || !mode_len)
{
php_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;
}
if (!persist)
{
fdata = emalloc(sizeof(php_sample_descriptor_data));
fdata->filename = estrndup(filename, filename_len);
fdata->fp = fp;
ZEND_REGISTER_RESOURCE(return_value, fdata,le_sample_descriptor);
}
else
{
list_entry le;
char *hash_key;
int hash_key_len;
fdata =pemalloc(sizeof(php_sample_descriptor_data),1);
fdata->filename = pemalloc(filename_len + 1, 1);
memcpy(data->filename, filename, filename_len + 1);
fdata->fp = fp;
//在EG(regular_list中存一份)
ZEND_REGISTER_RESOURCE(return_value, fdata,le_sample_descriptor_persist);
//在EG(persistent_list)中再存一份
le.type = le_sample_descriptor_persist;
le.ptr = fdata;
hash_key_len = spprintf(&hash_key, 0,"sample_descriptor:%s:%s", filename, mode);
zend_hash_update(&EG(persistent_list),hash_key, hash_key_len + 1,(void*)&le, sizeof(list_entry), NULL);
efree(hash_key);
}
}
</code>
<p>在Persistent Resources时,因为我们在EG(regular_list)中也保存了一份,所以脚本中我们资源类型的变量在实现中仍然是保存着一个resource ID,我们可以用它来进行之前章节所做的工作。</p>
<p>将其添加到EG(persistent_list)中时,我们进行的操作流程几乎和ZEND_REGISTER_RESOURCE()宏函数一样,唯一的不同便是索引由之前的数字类型换成了字符串类型。</p>
<p>当一个保存在EG(regular_list)中的Persistent Resources被脚本释放时,内核会在EG(regular_list)寻找它对应的dtor函数,但它找到的是NULL,因为我们在使用zend_register_list_destructors_ex()函数声明这种资源类型时,第一个参数的值为NULL。所以此时这个{资源}不会被任何dtor函数调用,可以继续存在于内存中,任脚本流逝,请求更迭。</p>
<p>当web server的进程执行完毕后,内核会扫描EG(persistent_list)的dtor,并调用我们已经定义好的释放函数。在我们定义的释放函数中,一定要记得使用pfree函数来释放内存,而不是efree。</p>
<h3>Reuse</h3>
<p>创建Persistent Resources的目的是为了使用它,而不是让它来浪费内存的,我们再次重写一下sample_open()函数,这一次我们将检测需要创建的资源是否已经在persistent_list中存在了。</p>
<code c>
PHP_FUNCTION(sample_fopen)
{
php_sample_descriptor_data *fdata;
FILE *fp;
char *filename, *mode, *hash_key;
int filename_len, mode_len, hash_key_len;
zend_bool persist = 0;
list_entry *existing_file;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,"ss|b",&filename, &filename_len, &mode, &mode_len,&persist) == FAILURE)
{
RETURN_NULL();
}
if (!filename_len || !mode_len)
{
php_error_docref(NULL TSRMLS_CC, E_WARNING,"Invalid filename or mode length");
RETURN_FALSE;
}
//看看是否已经存在,如果已经存在就直接使用,不再创建
hash_key_len = spprintf(&hash_key, 0,"sample_descriptor:%s:%s", filename, mode);
if (zend_hash_find(&EG(persistent_list), hash_key,hash_key_len + 1, (void **)&existing_file) == SUCCESS)
{
//存在一个,直接使用!
ZEND_REGISTER_RESOURCE(return_value,existing_file->ptr, le_sample_descriptor_persist);
efree(hash_key);
return;
}
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;
}
if (!persist)
{
fdata = emalloc(sizeof(php_sample_descriptor_data));
fdata->filename = estrndup(filename, filename_len);
fdata->fp = fp;
ZEND_REGISTER_RESOURCE(return_value, fdata,le_sample_descriptor);
}
else
{
list_entry le;
fdata =pemalloc(sizeof(php_sample_descriptor_data),1);
fdata->filename = pemalloc(filename_len + 1, 1);
memcpy(data->filename, filename, filename_len + 1);
fdata->fp = fp;
ZEND_REGISTER_RESOURCE(return_value, fdata,le_sample_descriptor_persist);
/* Store a copy in the persistent_list */
le.type = le_sample_descriptor_persist;
le.ptr = fdata;
//hash_key在上面已经被创建了
zend_hash_update(&EG(persistent_list),hash_key, hash_key_len + 1,(void*)&le, sizeof(list_entry), NULL);
}
efree(hash_key);
}
</code>
<p>
因为所有的PHP扩展都共用同一个HashTable来保存Persistent Resources,所以我们在为{资源}的索引起名时,一定要唯一,同时必须简单,方面我们在其它的函数中构造出来。</p>
<h3>Liveness Checking and Early Departure</h3>
<p>
一旦我们打开一个本地文件,便可以一直占有它的操作句柄,保证随时可以打开它。但是对于一些存在于远程计算机上的资源,比如mysql链接、http链接,虽然我们仍然握着与服务器的链接,但是这个链接在服务器端可能已经被关闭了,在本地我们就无法再用它来做一些有价值的工作了。</p>
<p>
所以,当我们使用{资源},尤其是Persistent Resources时,一定要保证获取出来的{资源}仍然是有效的、可以使用的。如果它失效了,我们必须将其从persistent list中移除。先面就是一个检测socket有效性的例子:
</p>
<code c>
if (zend_hash_find(&EG(persistent_list), hash_key,hash_key_len + 1, (void**)&socket) == SUCCESS)
{
if (php_sample_socket_is_alive(socket->ptr))
{
ZEND_REGISTER_RESOURCE(return_value,socket->ptr, le_sample_socket);
return;
}
zend_hash_del(&EG(persistent_list),hash_key, hash_key_len + 1);
}
</code>
<p>
如你所见,{资源}失效后,我们只要把它从HashTable中删除就行了,这一步操作同样会激活我们设置的回调函数。On completion of this code block, the function will be in the same state it would have been if no resource had been found in the persistent list.
</p>
<h3>Agnostic Retrieval</h3>
<p>
现在我们已经可以创建资源类型并生成新的资源,还能将Persistent Resources与平常{资源}使用的差异性封装起来。但是如果用户对一个Persistent Resources调用sample_fwrite()时候并不会正常工作,先想一下内核是如何通过一个数字所以在regular_list中获取最终资源的。
</p>
<code c>
ZEND_FETCH_RESOURCE(
fdata,
php_sample_descriptor_data*,
&file_resource,
-1,
PHP_SAMPLE_DESCRIPTOR_RES_NAME,
le_sample_descriptor
);
</code>
<p>
le_sample_descriptor可以保证你获取到的资源确实是这种类型的,绝不会出现你想要一个文件句柄,却返回给你一个mysql链接的情况。这种验证是必须的,但有时你又想绕过这种验证,因为我们放在persistenst_list中的{资源}是le_sample_descruotor_persist类型的,所以当我们把它复制到regular_list中时,它也是le_sample_descructor_persist的,所以如果我们想获取它,貌似只有两种方法,要么修改类型,要么再写一个新的sample_write_persistent函数的实现。或者极端一些,在sample_write函数里进行复杂的判断。但是如果sample_write()函数能同时接收它们两种类型的{资源}多好啊....
</p>
<p>
事情没有这么复杂,我们确实可以在sample_write()函数里获取{资源}时候同时指定两种类型。那就是使用ZEND_FETCH_RESOURCE2()宏函数,它与ZEND_FETCH_RESOURCE()宏函数的唯一区别就是它可以接收两种类型参数。</p>
<code c>
ZEND_FETCH_RESOURCE2(
fdata,
php_sample_descriptor_data*,
&file_resource,
-1,
PHP_SAMPLE_DESCRIPTOR_RES_NAME,
le_sample_descriptor,
le_sample_descriptor_persist
);
</code>
<p>
现在,只要resource ID对应的最终资源类型是persistent或者non-persistent的一种便可以正常通过验证了。</p>
<p>
什么,你想设置三种甚至更多的类型?!!那你只能直接使用zend_fetch_resource()函数了。</p>
<code c>
//一种类型的
fp = (FILE*) zend_fetch_resource(
&file_descriptor TSRMLS_CC,
-1,
PHP_SAMPLE_DESCRIPTOR_RES_NAME,
NULL,
1,
le_sample_descriptor
);
ZEND_VERIFY_RESOURCE(fp);
</code>
<p>
想看看ZEND_FETCH_RESOURCE2()宏函数的实现么?</p>
<code c>
//两种类型的
fp = (FILE*) zend_fetch_resource(
&file_descriptor TSRMLS_CC,
-1,
PHP_SAMPLE_DESCRIPTOR_RES_NAME,
NULL,
2,
le_sample_descriptor,
le_sample_descriptor_persist
);
ZEND_VERIFY_RESOURCE(fp);
</code>
<p>
再给力一些,三种类型的:
</p>
<code c>
fp = (FILE*) zend_fetch_resource(
&file_descriptor TSRMLS_CC,
-1,
PHP_SAMPLE_DESCRIPTOR_RES_NAME,
NULL,
3,
le_sample_descriptor,
le_sample_descriptor_persist,
le_sample_othertype
);
ZEND_VERIFY_RESOURCE(fp);
</code>
<p>话都说到这份上了,你肯定知道四种、五种、更多种类型的应该怎么调用了。</p>