diff options
Diffstat (limited to 'ext/opcache/zend_shared_alloc.c')
-rw-r--r-- | ext/opcache/zend_shared_alloc.c | 494 |
1 files changed, 494 insertions, 0 deletions
diff --git a/ext/opcache/zend_shared_alloc.c b/ext/opcache/zend_shared_alloc.c new file mode 100644 index 0000000000..cf4e0ff0f7 --- /dev/null +++ b/ext/opcache/zend_shared_alloc.c @@ -0,0 +1,494 @@ +/* + +----------------------------------------------------------------------+ + | Zend OPcache | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2013 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Andi Gutmans <andi@zend.com> | + | Zeev Suraski <zeev@zend.com> | + | Stanislav Malyshev <stas@zend.com> | + | Dmitry Stogov <dmitry@zend.com> | + +----------------------------------------------------------------------+ +*/ + +#include <errno.h> +#include "ZendAccelerator.h" +#include "zend_shared_alloc.h" +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif +#include <fcntl.h> +#ifndef ZEND_WIN32 +# include <sys/types.h> +# include <dirent.h> +# include <signal.h> +# include <sys/stat.h> +# include <stdio.h> +#endif + +#ifdef HAVE_MPROTECT +# include "sys/mman.h" +#endif + +#define TMP_DIR "/tmp" +#define SEM_FILENAME_PREFIX ".ZendSem." +#define S_H(s) g_shared_alloc_handler->s + +/* True globals */ +/* old/new mapping. We can use true global even for ZTS because its usage + is wrapped with exclusive lock anyway */ +static HashTable xlat_table; +static const zend_shared_memory_handlers *g_shared_alloc_handler = NULL; +static const char *g_shared_model; +/* pointer to globals allocated in SHM and shared across processes */ +zend_smm_shared_globals *smm_shared_globals; + +#ifndef ZEND_WIN32 +#ifdef ZTS +static MUTEX_T zts_lock; +#endif +int lock_file; +static char lockfile_name[sizeof(TMP_DIR) + sizeof(SEM_FILENAME_PREFIX) + 8]; +#endif + +static const zend_shared_memory_handler_entry handler_table[] = { +#ifdef USE_MMAP + { "mmap", &zend_alloc_mmap_handlers }, +#endif +#ifdef USE_SHM + { "shm", &zend_alloc_shm_handlers }, +#endif +#ifdef USE_SHM_OPEN + { "posix", &zend_alloc_posix_handlers }, +#endif +#ifdef ZEND_WIN32 + { "win32", &zend_alloc_win32_handlers }, +#endif + { NULL, NULL} +}; + +#ifndef ZEND_WIN32 +void zend_shared_alloc_create_lock(void) +{ + int val; + +#ifdef ZTS + zts_lock = tsrm_mutex_alloc(); +#endif + + sprintf(lockfile_name, "%s/%sXXXXXX", TMP_DIR, SEM_FILENAME_PREFIX); + lock_file = mkstemp(lockfile_name); + fchmod(lock_file, 0666); + + if (lock_file == -1) { + zend_accel_error(ACCEL_LOG_FATAL, "Unable to create lock file: %s (%d)", strerror(errno), errno); + } + val = fcntl(lock_file, F_GETFD, 0); + val |= FD_CLOEXEC; + fcntl(lock_file, F_SETFD, val); + + unlink(lockfile_name); +} +#endif + +static void no_memory_bailout(size_t allocate_size, char *error) +{ + 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 ); +} + +static void copy_shared_segments(void *to, void *from, int count, int size) +{ + zend_shared_segment **shared_segments_v = (zend_shared_segment **)to; + void *shared_segments_to_p = ((char *)to + count*(sizeof(void *))); + void *shared_segments_from_p = from; + int i; + + for (i = 0; i < count; i++) { + shared_segments_v[i] = shared_segments_to_p; + memcpy(shared_segments_to_p, shared_segments_from_p, size); + shared_segments_to_p = ((char *)shared_segments_to_p + size); + shared_segments_from_p = ((char *)shared_segments_from_p + size); + } +} + +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) +{ + int res; + g_shared_alloc_handler = he->handler; + g_shared_model = he->name; + ZSMMG(shared_segments) = NULL; + ZSMMG(shared_segments_count) = 0; + + res = S_H(create_segments)(requested_size, shared_segments_p, shared_segments_count, error_in); + + if (res) { + /* this model works! */ + return res; + } + if (*shared_segments_p) { + int i; + /* cleanup */ + for (i = 0; i < *shared_segments_count; i++) { + if ((*shared_segments_p)[i]->p && (*shared_segments_p)[i]->p != (void *)-1) { + S_H(detach_segment)((*shared_segments_p)[i]); + } + } + free(*shared_segments_p); + *shared_segments_p = NULL; + } + g_shared_alloc_handler = NULL; + return ALLOC_FAILURE; +} + +int zend_shared_alloc_startup(size_t requested_size) +{ + zend_shared_segment **tmp_shared_segments; + size_t shared_segments_array_size; + zend_smm_shared_globals tmp_shared_globals, *p_tmp_shared_globals; + char *error_in = NULL; + const zend_shared_memory_handler_entry *he; + int res = ALLOC_FAILURE; + + TSRMLS_FETCH(); + + /* shared_free must be valid before we call zend_shared_alloc() + * - make it temporarily point to a local variable + */ + smm_shared_globals = &tmp_shared_globals; + ZSMMG(shared_free) = requested_size; /* goes to tmp_shared_globals.shared_free */ + + zend_shared_alloc_create_lock(); + + if (ZCG(accel_directives).memory_model && ZCG(accel_directives).memory_model[0]) { + char *model = ZCG(accel_directives).memory_model; + /* "cgi" is really "shm"... */ + if (strncmp(ZCG(accel_directives).memory_model, "cgi", sizeof("cgi")) == 0) { + model = "shm"; + } + + for (he = handler_table; he->name; he++) { + if (strcmp(model, he->name) == 0) { + res = zend_shared_alloc_try(he, requested_size, &ZSMMG(shared_segments), &ZSMMG(shared_segments_count), &error_in); + if (res) { + /* this model works! */ + } + break; + } + } + } + + if (res == FAILED_REATTACHED) { + smm_shared_globals = NULL; + return res; + } + + if (!g_shared_alloc_handler) { + /* try memory handlers in order */ + for (he = handler_table; he->name; he++) { + res = zend_shared_alloc_try(he, requested_size, &ZSMMG(shared_segments), &ZSMMG(shared_segments_count), &error_in); + if (res) { + /* this model works! */ + break; + } + } + } + + if (!g_shared_alloc_handler) { + no_memory_bailout(requested_size, error_in); + return ALLOC_FAILURE; + } + + if (res == SUCCESSFULLY_REATTACHED) { + return res; + } + + shared_segments_array_size = ZSMMG(shared_segments_count) * S_H(segment_type_size)(); + + /* move shared_segments and shared_free to shared memory */ + ZCG(locked) = 1; /* no need to perform a real lock at this point */ + p_tmp_shared_globals = (zend_smm_shared_globals *) zend_shared_alloc(sizeof(zend_smm_shared_globals)); + if (!p_tmp_shared_globals) { + zend_accel_error(ACCEL_LOG_FATAL, "Insufficient shared memory!"); + return ALLOC_FAILURE;; + } + + tmp_shared_segments = zend_shared_alloc(shared_segments_array_size + ZSMMG(shared_segments_count) * sizeof(void *)); + if (!tmp_shared_segments) { + zend_accel_error(ACCEL_LOG_FATAL, "Insufficient shared memory!"); + return ALLOC_FAILURE;; + } + + copy_shared_segments(tmp_shared_segments, ZSMMG(shared_segments)[0], ZSMMG(shared_segments_count), S_H(segment_type_size)()); + + *p_tmp_shared_globals = tmp_shared_globals; + smm_shared_globals = p_tmp_shared_globals; + + free(ZSMMG(shared_segments)); + ZSMMG(shared_segments) = tmp_shared_segments; + + ZSMMG(shared_memory_state).positions = (int *)zend_shared_alloc(sizeof(int) * ZSMMG(shared_segments_count)); + if (!ZSMMG(shared_memory_state).positions) { + zend_accel_error(ACCEL_LOG_FATAL, "Insufficient shared memory!"); + return ALLOC_FAILURE;; + } + + ZCG(locked) = 0; + + return res; +} + +void zend_shared_alloc_shutdown(void) +{ + zend_shared_segment **tmp_shared_segments; + size_t shared_segments_array_size; + zend_smm_shared_globals tmp_shared_globals; + int i; + + tmp_shared_globals = *smm_shared_globals; + smm_shared_globals = &tmp_shared_globals; + shared_segments_array_size = ZSMMG(shared_segments_count) * (S_H(segment_type_size)() + sizeof(void *)); + tmp_shared_segments = emalloc(shared_segments_array_size); + copy_shared_segments(tmp_shared_segments, ZSMMG(shared_segments)[0], ZSMMG(shared_segments_count), S_H(segment_type_size)()); + ZSMMG(shared_segments) = tmp_shared_segments; + + for (i = 0; i < ZSMMG(shared_segments_count); i++) { + S_H(detach_segment)(ZSMMG(shared_segments)[i]); + } + efree(ZSMMG(shared_segments)); + ZSMMG(shared_segments) = NULL; + g_shared_alloc_handler = NULL; +#ifndef ZEND_WIN32 + close(lock_file); +#endif +} + +static size_t zend_shared_alloc_get_largest_free_block(void) +{ + int i; + size_t largest_block_size = 0; + + for (i = 0; i < ZSMMG(shared_segments_count); i++) { + size_t block_size = ZSMMG(shared_segments)[i]->size - ZSMMG(shared_segments)[i]->pos; + + if (block_size>largest_block_size) { + largest_block_size = block_size; + } + } + return largest_block_size; +} + +#define MIN_FREE_MEMORY 64*1024 + +#define SHARED_ALLOC_FAILED() do { \ + zend_accel_error(ACCEL_LOG_WARNING, "Not enough free shared space to allocate %ld bytes (%ld bytes free)", (long)size, (long)ZSMMG(shared_free)); \ + if (zend_shared_alloc_get_largest_free_block() < MIN_FREE_MEMORY) { \ + ZSMMG(memory_exhausted) = 1; \ + } \ + } while (0) + +void *zend_shared_alloc(size_t size) +{ + int i; + unsigned int block_size = ZEND_ALIGNED_SIZE(size); + TSRMLS_FETCH(); + +#if 1 + if (!ZCG(locked)) { + zend_accel_error(ACCEL_LOG_ERROR, "Shared memory lock not obtained"); + } +#endif + if (block_size > ZSMMG(shared_free)) { /* No hope to find a big-enough block */ + SHARED_ALLOC_FAILED(); + return NULL; + } + for (i = 0; i < ZSMMG(shared_segments_count); i++) { + if (ZSMMG(shared_segments)[i]->size - ZSMMG(shared_segments)[i]->pos >= block_size) { /* found a valid block */ + void *retval = (void *) (((char *) ZSMMG(shared_segments)[i]->p) + ZSMMG(shared_segments)[i]->pos); + + ZSMMG(shared_segments)[i]->pos += block_size; + ZSMMG(shared_free) -= block_size; + memset(retval, 0, block_size); + return retval; + } + } + SHARED_ALLOC_FAILED(); + return NULL; +} + +int zend_shared_memdup_size(void *source, size_t size) +{ + void **old_p; + + if (zend_hash_index_find(&xlat_table, (ulong)source, (void **)&old_p) == SUCCESS) { + /* we already duplicated this pointer */ + return 0; + } + zend_shared_alloc_register_xlat_entry(source, source); + return ZEND_ALIGNED_SIZE(size); +} + +void *_zend_shared_memdup(void *source, size_t size, zend_bool free_source TSRMLS_DC) +{ + void **old_p, *retval; + + if (zend_hash_index_find(&xlat_table, (ulong)source, (void **)&old_p) == SUCCESS) { + /* we already duplicated this pointer */ + return *old_p; + } + retval = ZCG(mem);; + ZCG(mem) = (void*)(((char*)ZCG(mem)) + ZEND_ALIGNED_SIZE(size)); + memcpy(retval, source, size); + if (free_source) { + interned_efree((char*)source); + } + zend_shared_alloc_register_xlat_entry(source, retval); + return retval; +} + +void zend_shared_alloc_safe_unlock(TSRMLS_D) +{ + if (ZCG(locked)) { + zend_shared_alloc_unlock(TSRMLS_C); + } +} + +#ifndef ZEND_WIN32 +/* name l_type l_whence l_start l_len */ +static FLOCK_STRUCTURE(mem_write_lock, F_WRLCK, SEEK_SET, 0, 1); +static FLOCK_STRUCTURE(mem_write_unlock, F_UNLCK, SEEK_SET, 0, 1); +#endif + +void zend_shared_alloc_lock(TSRMLS_D) +{ +#ifndef ZEND_WIN32 + +#ifdef ZTS + tsrm_mutex_lock(zts_lock); +#endif + +#if 0 + /* this will happen once per process, and will un-globalize mem_write_lock */ + if (mem_write_lock.l_pid == -1) { + mem_write_lock.l_pid = getpid(); + } +#endif + + while (1) { + if (fcntl(lock_file, F_SETLKW, &mem_write_lock) == -1) { + if (errno == EINTR) { + continue; + } + zend_accel_error(ACCEL_LOG_ERROR, "Cannot create lock - %s (%d)", strerror(errno), errno); + } + break; + } +#else + zend_shared_alloc_lock_win32(); +#endif + + ZCG(locked) = 1; + + /* Prepare translation table + * + * Make it persistent so that it uses malloc() and allocated blocks + * won't be taken from space which is freed by efree in memdup. + * Otherwise it leads to false matches in memdup check. + */ + zend_hash_init(&xlat_table, 100, NULL, NULL, 1); +} + +void zend_shared_alloc_unlock(TSRMLS_D) +{ + /* Destroy translation table */ + zend_hash_destroy(&xlat_table); + + ZCG(locked) = 0; + +#ifndef ZEND_WIN32 + if (fcntl(lock_file, F_SETLK, &mem_write_unlock) == -1) { + zend_accel_error(ACCEL_LOG_ERROR, "Cannot remove lock - %s (%d)", strerror(errno), errno); + } +#ifdef ZTS + tsrm_mutex_unlock(zts_lock); +#endif +#else + zend_shared_alloc_unlock_win32(); +#endif +} + +void zend_shared_alloc_clear_xlat_table(void) +{ + zend_hash_clean(&xlat_table); +} + +void zend_shared_alloc_register_xlat_entry(const void *old, const void *new) +{ + zend_hash_index_update(&xlat_table, (ulong)old, (void*)&new, sizeof(void *), NULL); +} + +void *zend_shared_alloc_get_xlat_entry(const void *old) +{ + void **retval; + + if (zend_hash_index_find(&xlat_table, (ulong)old, (void **)&retval) == FAILURE) { + return NULL; + } + return *retval; +} + +size_t zend_shared_alloc_get_free_memory(void) +{ + return ZSMMG(shared_free); +} + +void zend_shared_alloc_save_state(void) +{ + int i; + + for (i = 0; i < ZSMMG(shared_segments_count); i++) { + ZSMMG(shared_memory_state).positions[i] = ZSMMG(shared_segments)[i]->pos; + } + ZSMMG(shared_memory_state).shared_free = ZSMMG(shared_free); +} + +void zend_shared_alloc_restore_state(void) +{ + int i; + + for (i = 0; i < ZSMMG(shared_segments_count); i++) { + ZSMMG(shared_segments)[i]->pos = ZSMMG(shared_memory_state).positions[i]; + } + ZSMMG(shared_free) = ZSMMG(shared_memory_state).shared_free; + ZSMMG(memory_exhausted) = 0; + ZSMMG(wasted_shared_memory) = 0; +} + +const char *zend_accel_get_shared_model(void) +{ + return g_shared_model; +} + +void zend_accel_shared_protect(int mode TSRMLS_DC) +{ +#ifdef HAVE_MPROTECT + int i; + + if (mode) { + mode = PROT_READ; + } else { + mode = PROT_READ|PROT_WRITE; + } + + for (i = 0; i < ZSMMG(shared_segments_count); i++) { + mprotect(ZSMMG(shared_segments)[i]->p, ZSMMG(shared_segments)[i]->size, mode); + } +#endif +} |