xref: /PHP-5.5/ext/opcache/zend_shared_alloc.c (revision 0a8f28b4)
1 /*
2    +----------------------------------------------------------------------+
3    | Zend OPcache                                                         |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 1998-2015 The PHP Group                                |
6    +----------------------------------------------------------------------+
7    | This source file is subject to version 3.01 of the PHP license,      |
8    | that is bundled with this package in the file LICENSE, and is        |
9    | available through the world-wide-web at the following url:           |
10    | http://www.php.net/license/3_01.txt                                  |
11    | If you did not receive a copy of the PHP license and are unable to   |
12    | obtain it through the world-wide-web, please send a note to          |
13    | license@php.net so we can mail you a copy immediately.               |
14    +----------------------------------------------------------------------+
15    | Authors: Andi Gutmans <andi@zend.com>                                |
16    |          Zeev Suraski <zeev@zend.com>                                |
17    |          Stanislav Malyshev <stas@zend.com>                          |
18    |          Dmitry Stogov <dmitry@zend.com>                             |
19    +----------------------------------------------------------------------+
20 */
21 
22 #include <errno.h>
23 #include "ZendAccelerator.h"
24 #include "zend_shared_alloc.h"
25 #ifdef HAVE_UNISTD_H
26 # include <unistd.h>
27 #endif
28 #include <fcntl.h>
29 #ifndef ZEND_WIN32
30 # include <sys/types.h>
31 # include <dirent.h>
32 # include <signal.h>
33 # include <sys/stat.h>
34 # include <stdio.h>
35 #endif
36 
37 #ifdef HAVE_MPROTECT
38 # include "sys/mman.h"
39 #endif
40 
41 #define TMP_DIR "/tmp"
42 #define SEM_FILENAME_PREFIX ".ZendSem."
43 #define S_H(s) g_shared_alloc_handler->s
44 
45 /* True globals */
46 /* old/new mapping. We can use true global even for ZTS because its usage
47    is wrapped with exclusive lock anyway */
48 static HashTable xlat_table;
49 static const zend_shared_memory_handlers *g_shared_alloc_handler = NULL;
50 static const char *g_shared_model;
51 /* pointer to globals allocated in SHM and shared across processes */
52 zend_smm_shared_globals *smm_shared_globals;
53 
54 #ifndef ZEND_WIN32
55 #ifdef ZTS
56 static MUTEX_T zts_lock;
57 #endif
58 int lock_file;
59 static char lockfile_name[sizeof(TMP_DIR) + sizeof(SEM_FILENAME_PREFIX) + 8];
60 #endif
61 
62 static const zend_shared_memory_handler_entry handler_table[] = {
63 #ifdef USE_MMAP
64 	{ "mmap", &zend_alloc_mmap_handlers },
65 #endif
66 #ifdef USE_SHM
67 	{ "shm", &zend_alloc_shm_handlers },
68 #endif
69 #ifdef USE_SHM_OPEN
70 	{ "posix", &zend_alloc_posix_handlers },
71 #endif
72 #ifdef ZEND_WIN32
73 	{ "win32", &zend_alloc_win32_handlers },
74 #endif
75 	{ NULL, NULL}
76 };
77 
78 #ifndef ZEND_WIN32
zend_shared_alloc_create_lock(void)79 void zend_shared_alloc_create_lock(void)
80 {
81 	int val;
82 
83 #ifdef ZTS
84     zts_lock = tsrm_mutex_alloc();
85 #endif
86 
87 	sprintf(lockfile_name, "%s/%sXXXXXX", TMP_DIR, SEM_FILENAME_PREFIX);
88 	lock_file = mkstemp(lockfile_name);
89 	fchmod(lock_file, 0666);
90 
91 	if (lock_file == -1) {
92 		zend_accel_error(ACCEL_LOG_FATAL, "Unable to create lock file: %s (%d)", strerror(errno), errno);
93 	}
94 	val = fcntl(lock_file, F_GETFD, 0);
95 	val |= FD_CLOEXEC;
96 	fcntl(lock_file, F_SETFD, val);
97 
98 	unlink(lockfile_name);
99 }
100 #endif
101 
no_memory_bailout(size_t allocate_size,char * error)102 static void no_memory_bailout(size_t allocate_size, char *error)
103 {
104 	zend_accel_error(ACCEL_LOG_FATAL, "Unable to allocate shared memory segment of %ld bytes: %s: %s (%d)", allocate_size, error?error:"unknown", strerror(errno), errno );
105 }
106 
copy_shared_segments(void * to,void * from,int count,int size)107 static void copy_shared_segments(void *to, void *from, int count, int size)
108 {
109 	zend_shared_segment **shared_segments_v = (zend_shared_segment **)to;
110 	void *shared_segments_to_p = ((char *)to + count*(sizeof(void *)));
111 	void *shared_segments_from_p = from;
112 	int i;
113 
114 	for (i = 0; i < count; i++) {
115 		shared_segments_v[i] = 	shared_segments_to_p;
116 		memcpy(shared_segments_to_p, shared_segments_from_p, size);
117 		shared_segments_to_p = ((char *)shared_segments_to_p + size);
118 		shared_segments_from_p = ((char *)shared_segments_from_p + size);
119 	}
120 }
121 
zend_shared_alloc_try(const zend_shared_memory_handler_entry * he,size_t requested_size,zend_shared_segment *** shared_segments_p,int * shared_segments_count,char ** error_in)122 static int zend_shared_alloc_try(const zend_shared_memory_handler_entry *he, size_t requested_size, zend_shared_segment ***shared_segments_p, int *shared_segments_count, char **error_in)
123 {
124 	int res;
125 	g_shared_alloc_handler = he->handler;
126 	g_shared_model = he->name;
127 	ZSMMG(shared_segments) = NULL;
128 	ZSMMG(shared_segments_count) = 0;
129 
130 	res = S_H(create_segments)(requested_size, shared_segments_p, shared_segments_count, error_in);
131 
132 	if (res) {
133 		/* this model works! */
134 		return res;
135 	}
136 	if (*shared_segments_p) {
137 		int i;
138 		/* cleanup */
139 		for (i = 0; i < *shared_segments_count; i++) {
140 			if ((*shared_segments_p)[i]->p && (*shared_segments_p)[i]->p != (void *)-1) {
141 				S_H(detach_segment)((*shared_segments_p)[i]);
142 			}
143 		}
144 		free(*shared_segments_p);
145 		*shared_segments_p = NULL;
146 	}
147 	g_shared_alloc_handler = NULL;
148 	return ALLOC_FAILURE;
149 }
150 
zend_shared_alloc_startup(size_t requested_size)151 int zend_shared_alloc_startup(size_t requested_size)
152 {
153 	zend_shared_segment **tmp_shared_segments;
154 	size_t shared_segments_array_size;
155 	zend_smm_shared_globals tmp_shared_globals, *p_tmp_shared_globals;
156 	char *error_in = NULL;
157 	const zend_shared_memory_handler_entry *he;
158 	int res = ALLOC_FAILURE;
159 
160 	TSRMLS_FETCH();
161 
162 	/* shared_free must be valid before we call zend_shared_alloc()
163 	 * - make it temporarily point to a local variable
164 	 */
165 	smm_shared_globals = &tmp_shared_globals;
166 	ZSMMG(shared_free) = requested_size; /* goes to tmp_shared_globals.shared_free */
167 
168 	zend_shared_alloc_create_lock();
169 
170 	if (ZCG(accel_directives).memory_model && ZCG(accel_directives).memory_model[0]) {
171 		char *model = ZCG(accel_directives).memory_model;
172 		/* "cgi" is really "shm"... */
173 		if (strncmp(ZCG(accel_directives).memory_model, "cgi", sizeof("cgi")) == 0) {
174 			model = "shm";
175 		}
176 
177 		for (he = handler_table; he->name; he++) {
178 			if (strcmp(model, he->name) == 0) {
179 				res = zend_shared_alloc_try(he, requested_size, &ZSMMG(shared_segments), &ZSMMG(shared_segments_count), &error_in);
180 				if (res) {
181 					/* this model works! */
182 				}
183 				break;
184 			}
185 		}
186 	}
187 
188 	if (res == FAILED_REATTACHED) {
189 		smm_shared_globals = NULL;
190 		return res;
191 	}
192 
193 	if (!g_shared_alloc_handler) {
194 		/* try memory handlers in order */
195 		for (he = handler_table; he->name; he++) {
196 			res = zend_shared_alloc_try(he, requested_size, &ZSMMG(shared_segments), &ZSMMG(shared_segments_count), &error_in);
197 			if (res) {
198 				/* this model works! */
199 				break;
200 			}
201 		}
202 	}
203 
204 	if (!g_shared_alloc_handler) {
205 		no_memory_bailout(requested_size, error_in);
206 		return ALLOC_FAILURE;
207 	}
208 
209 	if (res == SUCCESSFULLY_REATTACHED) {
210 		return res;
211 	}
212 
213 	shared_segments_array_size = ZSMMG(shared_segments_count) * S_H(segment_type_size)();
214 
215 	/* move shared_segments and shared_free to shared memory */
216 	ZCG(locked) = 1; /* no need to perform a real lock at this point */
217 	p_tmp_shared_globals = (zend_smm_shared_globals *) zend_shared_alloc(sizeof(zend_smm_shared_globals));
218 	if (!p_tmp_shared_globals) {
219 		zend_accel_error(ACCEL_LOG_FATAL, "Insufficient shared memory!");
220 		return ALLOC_FAILURE;;
221 	}
222 
223 	tmp_shared_segments = zend_shared_alloc(shared_segments_array_size + ZSMMG(shared_segments_count) * sizeof(void *));
224 	if (!tmp_shared_segments) {
225 		zend_accel_error(ACCEL_LOG_FATAL, "Insufficient shared memory!");
226 		return ALLOC_FAILURE;;
227 	}
228 
229 	copy_shared_segments(tmp_shared_segments, ZSMMG(shared_segments)[0], ZSMMG(shared_segments_count), S_H(segment_type_size)());
230 
231 	*p_tmp_shared_globals = tmp_shared_globals;
232 	smm_shared_globals = p_tmp_shared_globals;
233 
234 	free(ZSMMG(shared_segments));
235 	ZSMMG(shared_segments) = tmp_shared_segments;
236 
237 	ZSMMG(shared_memory_state).positions = (int *)zend_shared_alloc(sizeof(int) * ZSMMG(shared_segments_count));
238 	if (!ZSMMG(shared_memory_state).positions) {
239 		zend_accel_error(ACCEL_LOG_FATAL, "Insufficient shared memory!");
240 		return ALLOC_FAILURE;;
241 	}
242 
243 	ZCG(locked) = 0;
244 
245 	return res;
246 }
247 
zend_shared_alloc_shutdown(void)248 void zend_shared_alloc_shutdown(void)
249 {
250 	zend_shared_segment **tmp_shared_segments;
251 	size_t shared_segments_array_size;
252 	zend_smm_shared_globals tmp_shared_globals;
253 	int i;
254 
255 	tmp_shared_globals = *smm_shared_globals;
256 	smm_shared_globals = &tmp_shared_globals;
257 	shared_segments_array_size = ZSMMG(shared_segments_count) * (S_H(segment_type_size)() + sizeof(void *));
258 	tmp_shared_segments = emalloc(shared_segments_array_size);
259 	copy_shared_segments(tmp_shared_segments, ZSMMG(shared_segments)[0], ZSMMG(shared_segments_count), S_H(segment_type_size)());
260 	ZSMMG(shared_segments) = tmp_shared_segments;
261 
262 	for (i = 0; i < ZSMMG(shared_segments_count); i++) {
263 		S_H(detach_segment)(ZSMMG(shared_segments)[i]);
264 	}
265 	efree(ZSMMG(shared_segments));
266 	ZSMMG(shared_segments) = NULL;
267 	g_shared_alloc_handler = NULL;
268 #ifndef ZEND_WIN32
269 	close(lock_file);
270 #endif
271 }
272 
zend_shared_alloc_get_largest_free_block(void)273 static size_t zend_shared_alloc_get_largest_free_block(void)
274 {
275 	int i;
276 	size_t largest_block_size = 0;
277 
278 	for (i = 0; i < ZSMMG(shared_segments_count); i++) {
279 		size_t block_size = ZSMMG(shared_segments)[i]->size - ZSMMG(shared_segments)[i]->pos;
280 
281 		if (block_size>largest_block_size) {
282 			largest_block_size = block_size;
283 		}
284 	}
285 	return largest_block_size;
286 }
287 
288 #define MIN_FREE_MEMORY 64*1024
289 
290 #define SHARED_ALLOC_FAILED() do {		\
291 		zend_accel_error(ACCEL_LOG_WARNING, "Not enough free shared space to allocate %ld bytes (%ld bytes free)", (long)size, (long)ZSMMG(shared_free)); \
292 		if (zend_shared_alloc_get_largest_free_block() < MIN_FREE_MEMORY) { \
293 			ZSMMG(memory_exhausted) = 1; \
294 		} \
295 	} while (0)
296 
zend_shared_alloc(size_t size)297 void *zend_shared_alloc(size_t size)
298 {
299 	int i;
300 	unsigned int block_size = ZEND_ALIGNED_SIZE(size);
301 	TSRMLS_FETCH();
302 
303 #if 1
304 	if (!ZCG(locked)) {
305 		zend_accel_error(ACCEL_LOG_ERROR, "Shared memory lock not obtained");
306 	}
307 #endif
308 	if (block_size > ZSMMG(shared_free)) { /* No hope to find a big-enough block */
309 		SHARED_ALLOC_FAILED();
310 		return NULL;
311 	}
312 	for (i = 0; i < ZSMMG(shared_segments_count); i++) {
313 		if (ZSMMG(shared_segments)[i]->size - ZSMMG(shared_segments)[i]->pos >= block_size) { /* found a valid block */
314 			void *retval = (void *) (((char *) ZSMMG(shared_segments)[i]->p) + ZSMMG(shared_segments)[i]->pos);
315 
316 			ZSMMG(shared_segments)[i]->pos += block_size;
317 			ZSMMG(shared_free) -= block_size;
318 			memset(retval, 0, block_size);
319 			return retval;
320 		}
321 	}
322 	SHARED_ALLOC_FAILED();
323 	return NULL;
324 }
325 
zend_shared_memdup_size(void * source,size_t size)326 int zend_shared_memdup_size(void *source, size_t size)
327 {
328 	void **old_p;
329 
330 	if (zend_hash_index_find(&xlat_table, (ulong)source, (void **)&old_p) == SUCCESS) {
331 		/* we already duplicated this pointer */
332 		return 0;
333 	}
334 	zend_shared_alloc_register_xlat_entry(source, source);
335 	return ZEND_ALIGNED_SIZE(size);
336 }
337 
_zend_shared_memdup(void * source,size_t size,zend_bool free_source TSRMLS_DC)338 void *_zend_shared_memdup(void *source, size_t size, zend_bool free_source TSRMLS_DC)
339 {
340 	void **old_p, *retval;
341 
342 	if (zend_hash_index_find(&xlat_table, (ulong)source, (void **)&old_p) == SUCCESS) {
343 		/* we already duplicated this pointer */
344 		return *old_p;
345 	}
346 	retval = ZCG(mem);;
347 	ZCG(mem) = (void*)(((char*)ZCG(mem)) + ZEND_ALIGNED_SIZE(size));
348 	memcpy(retval, source, size);
349 	zend_shared_alloc_register_xlat_entry(source, retval);
350 	if (free_source) {
351 		interned_efree((char*)source);
352 	}
353 	return retval;
354 }
355 
zend_shared_alloc_safe_unlock(TSRMLS_D)356 void zend_shared_alloc_safe_unlock(TSRMLS_D)
357 {
358 	if (ZCG(locked)) {
359 		zend_shared_alloc_unlock(TSRMLS_C);
360 	}
361 }
362 
363 #ifndef ZEND_WIN32
364 /* name l_type l_whence l_start l_len */
365 static FLOCK_STRUCTURE(mem_write_lock, F_WRLCK, SEEK_SET, 0, 1);
366 static FLOCK_STRUCTURE(mem_write_unlock, F_UNLCK, SEEK_SET, 0, 1);
367 #endif
368 
zend_shared_alloc_lock(TSRMLS_D)369 void zend_shared_alloc_lock(TSRMLS_D)
370 {
371 #ifndef ZEND_WIN32
372 
373 #ifdef ZTS
374 	tsrm_mutex_lock(zts_lock);
375 #endif
376 
377 #if 0
378 	/* this will happen once per process, and will un-globalize mem_write_lock */
379 	if (mem_write_lock.l_pid == -1) {
380 		mem_write_lock.l_pid = getpid();
381 	}
382 #endif
383 
384 	while (1) {
385 		if (fcntl(lock_file, F_SETLKW, &mem_write_lock) == -1) {
386 			if (errno == EINTR) {
387 				continue;
388 			}
389 			zend_accel_error(ACCEL_LOG_ERROR, "Cannot create lock - %s (%d)", strerror(errno), errno);
390 		}
391 		break;
392 	}
393 #else
394 	zend_shared_alloc_lock_win32();
395 #endif
396 
397 	ZCG(locked) = 1;
398 
399 	/* Prepare translation table
400 	 *
401 	 * Make it persistent so that it uses malloc() and allocated blocks
402 	 * won't be taken from space which is freed by efree in memdup.
403 	 * Otherwise it leads to false matches in memdup check.
404 	 */
405 	zend_hash_init(&xlat_table, 100, NULL, NULL, 1);
406 }
407 
zend_shared_alloc_unlock(TSRMLS_D)408 void zend_shared_alloc_unlock(TSRMLS_D)
409 {
410 	/* Destroy translation table */
411 	zend_hash_destroy(&xlat_table);
412 
413 	ZCG(locked) = 0;
414 
415 #ifndef ZEND_WIN32
416 	if (fcntl(lock_file, F_SETLK, &mem_write_unlock) == -1) {
417 		zend_accel_error(ACCEL_LOG_ERROR, "Cannot remove lock - %s (%d)", strerror(errno), errno);
418 	}
419 #ifdef ZTS
420 	tsrm_mutex_unlock(zts_lock);
421 #endif
422 #else
423 	zend_shared_alloc_unlock_win32();
424 #endif
425 }
426 
zend_shared_alloc_clear_xlat_table(void)427 void zend_shared_alloc_clear_xlat_table(void)
428 {
429 	zend_hash_clean(&xlat_table);
430 }
431 
zend_shared_alloc_register_xlat_entry(const void * old,const void * new)432 void zend_shared_alloc_register_xlat_entry(const void *old, const void *new)
433 {
434 	zend_hash_index_update(&xlat_table, (ulong)old, (void*)&new, sizeof(void *), NULL);
435 }
436 
zend_shared_alloc_get_xlat_entry(const void * old)437 void *zend_shared_alloc_get_xlat_entry(const void *old)
438 {
439 	void **retval;
440 
441 	if (zend_hash_index_find(&xlat_table, (ulong)old, (void **)&retval) == FAILURE) {
442 		return NULL;
443 	}
444 	return *retval;
445 }
446 
zend_shared_alloc_get_free_memory(void)447 size_t zend_shared_alloc_get_free_memory(void)
448 {
449 	return ZSMMG(shared_free);
450 }
451 
zend_shared_alloc_save_state(void)452 void zend_shared_alloc_save_state(void)
453 {
454 	int i;
455 
456 	for (i = 0; i < ZSMMG(shared_segments_count); i++) {
457 		ZSMMG(shared_memory_state).positions[i] = ZSMMG(shared_segments)[i]->pos;
458 	}
459 	ZSMMG(shared_memory_state).shared_free = ZSMMG(shared_free);
460 }
461 
zend_shared_alloc_restore_state(void)462 void zend_shared_alloc_restore_state(void)
463 {
464 	int i;
465 
466 	for (i = 0; i < ZSMMG(shared_segments_count); i++) {
467 		ZSMMG(shared_segments)[i]->pos = ZSMMG(shared_memory_state).positions[i];
468 	}
469 	ZSMMG(shared_free) = ZSMMG(shared_memory_state).shared_free;
470 	ZSMMG(memory_exhausted) = 0;
471 	ZSMMG(wasted_shared_memory) = 0;
472 }
473 
zend_accel_get_shared_model(void)474 const char *zend_accel_get_shared_model(void)
475 {
476 	return g_shared_model;
477 }
478 
zend_accel_shared_protect(int mode TSRMLS_DC)479 void zend_accel_shared_protect(int mode TSRMLS_DC)
480 {
481 #ifdef HAVE_MPROTECT
482 	int i;
483 
484 	if (mode) {
485 		mode = PROT_READ;
486 	} else {
487 		mode = PROT_READ|PROT_WRITE;
488 	}
489 
490 	for (i = 0; i < ZSMMG(shared_segments_count); i++) {
491 		mprotect(ZSMMG(shared_segments)[i]->p, ZSMMG(shared_segments)[i]->size, mode);
492 	}
493 #endif
494 }
495