mirror of
https://github.com/php/php-src.git
synced 2024-12-03 23:05:57 +08:00
350 lines
11 KiB
C
350 lines
11 KiB
C
/*
|
|
+----------------------------------------------------------------------+
|
|
| Zend OPcache |
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 1998-2015 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 "ZendAccelerator.h"
|
|
#include "zend_shared_alloc.h"
|
|
#include "zend_accelerator_util_funcs.h"
|
|
#include <winbase.h>
|
|
#include <process.h>
|
|
#include <LMCONS.H>
|
|
|
|
#define ACCEL_FILEMAP_NAME "ZendOPcache.SharedMemoryArea"
|
|
#define ACCEL_MUTEX_NAME "ZendOPcache.SharedMemoryMutex"
|
|
#define ACCEL_FILEMAP_BASE_DEFAULT 0x01000000
|
|
#define ACCEL_FILEMAP_BASE "ZendOPcache.MemoryBase"
|
|
#define ACCEL_EVENT_SOURCE "Zend OPcache"
|
|
|
|
static HANDLE memfile = NULL, memory_mutex = NULL;
|
|
static void *mapping_base;
|
|
|
|
#define MAX_MAP_RETRIES 25
|
|
|
|
static void zend_win_error_message(int type, char *msg, int err)
|
|
{
|
|
LPVOID lpMsgBuf;
|
|
HANDLE h;
|
|
char *ev_msgs[2];
|
|
|
|
FormatMessage(
|
|
FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
FORMAT_MESSAGE_FROM_SYSTEM |
|
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
NULL,
|
|
err,
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
|
|
(LPTSTR) &lpMsgBuf,
|
|
0,
|
|
NULL
|
|
);
|
|
|
|
h = RegisterEventSource(NULL, TEXT(ACCEL_EVENT_SOURCE));
|
|
ev_msgs[0] = msg;
|
|
ev_msgs[1] = lpMsgBuf;
|
|
ReportEvent(h, // event log handle
|
|
EVENTLOG_ERROR_TYPE, // event type
|
|
0, // category zero
|
|
err, // event identifier
|
|
NULL, // no user security identifier
|
|
2, // one substitution string
|
|
0, // no data
|
|
ev_msgs, // pointer to string array
|
|
NULL); // pointer to data
|
|
DeregisterEventSource(h);
|
|
|
|
LocalFree( lpMsgBuf );
|
|
|
|
zend_accel_error(type, msg);
|
|
}
|
|
|
|
static char *create_name_with_username(char *name)
|
|
{
|
|
static char newname[MAXPATHLEN + UNLEN + 4];
|
|
char uname[UNLEN + 1];
|
|
DWORD unsize = UNLEN;
|
|
|
|
GetUserName(uname, &unsize);
|
|
snprintf(newname, sizeof(newname) - 1, "%s@%s", name, uname);
|
|
return newname;
|
|
}
|
|
|
|
static char *get_mmap_base_file(void)
|
|
{
|
|
static char windir[MAXPATHLEN+UNLEN + 3 + sizeof("\\\\@")];
|
|
char uname[UNLEN + 1];
|
|
DWORD unsize = UNLEN;
|
|
int l;
|
|
|
|
GetTempPath(MAXPATHLEN, windir);
|
|
GetUserName(uname, &unsize);
|
|
l = strlen(windir);
|
|
snprintf(windir + l, sizeof(windir) - l - 1, "\\%s@%s", ACCEL_FILEMAP_BASE, uname);
|
|
return windir;
|
|
}
|
|
|
|
void zend_shared_alloc_create_lock(void)
|
|
{
|
|
memory_mutex = CreateMutex(NULL, FALSE, create_name_with_username(ACCEL_MUTEX_NAME));
|
|
if (!memory_mutex) {
|
|
zend_accel_error(ACCEL_LOG_FATAL, "Cannot create mutex");
|
|
return;
|
|
}
|
|
ReleaseMutex(memory_mutex);
|
|
}
|
|
|
|
void zend_shared_alloc_lock_win32(void)
|
|
{
|
|
DWORD waitRes = WaitForSingleObject(memory_mutex, INFINITE);
|
|
|
|
if (waitRes == WAIT_FAILED) {
|
|
zend_accel_error(ACCEL_LOG_ERROR, "Cannot lock mutex");
|
|
}
|
|
}
|
|
|
|
void zend_shared_alloc_unlock_win32(void)
|
|
{
|
|
ReleaseMutex(memory_mutex);
|
|
}
|
|
|
|
static int zend_shared_alloc_reattach(size_t requested_size, char **error_in)
|
|
{
|
|
int err;
|
|
void *wanted_mapping_base;
|
|
char *mmap_base_file = get_mmap_base_file();
|
|
FILE *fp = fopen(mmap_base_file, "r");
|
|
MEMORY_BASIC_INFORMATION info;
|
|
|
|
err = GetLastError();
|
|
if (!fp) {
|
|
zend_win_error_message(ACCEL_LOG_WARNING, mmap_base_file, err);
|
|
zend_win_error_message(ACCEL_LOG_FATAL, "Unable to open base address file", err);
|
|
*error_in="fopen";
|
|
return ALLOC_FAILURE;
|
|
}
|
|
if (!fscanf(fp, "%p", &wanted_mapping_base)) {
|
|
err = GetLastError();
|
|
zend_win_error_message(ACCEL_LOG_FATAL, "Unable to read base address", err);
|
|
*error_in="read mapping base";
|
|
fclose(fp);
|
|
return ALLOC_FAILURE;
|
|
}
|
|
fclose(fp);
|
|
|
|
/* Check if the requested address space is free */
|
|
if (VirtualQuery(wanted_mapping_base, &info, sizeof(info)) == 0 ||
|
|
info.State != MEM_FREE ||
|
|
info.RegionSize < requested_size) {
|
|
err = ERROR_INVALID_ADDRESS;
|
|
zend_win_error_message(ACCEL_LOG_FATAL, "Unable to reattach to base address", err);
|
|
return ALLOC_FAILURE;
|
|
}
|
|
|
|
mapping_base = MapViewOfFileEx(memfile, FILE_MAP_ALL_ACCESS, 0, 0, 0, wanted_mapping_base);
|
|
err = GetLastError();
|
|
|
|
if (mapping_base == NULL) {
|
|
if (err == ERROR_INVALID_ADDRESS) {
|
|
zend_win_error_message(ACCEL_LOG_FATAL, "Unable to reattach to base address", err);
|
|
return ALLOC_FAILURE;
|
|
}
|
|
return ALLOC_FAIL_MAPPING;
|
|
}
|
|
smm_shared_globals = (zend_smm_shared_globals *) mapping_base;
|
|
|
|
return SUCCESSFULLY_REATTACHED;
|
|
}
|
|
|
|
static int create_segments(size_t requested_size, zend_shared_segment ***shared_segments_p, int *shared_segments_count, char **error_in)
|
|
{
|
|
int err, ret;
|
|
zend_shared_segment *shared_segment;
|
|
int map_retries = 0;
|
|
void *default_mapping_base_set[] = { 0, 0 };
|
|
/* TODO:
|
|
improve fixed addresses on x64. It still makes no sense to do it as Windows addresses are virtual per se and can or should be randomized anyway
|
|
through Address Space Layout Radomization (ASLR). We can still let the OS do its job and be sure that each process gets the same address if
|
|
desired. Not done yet, @zend refused but did not remember the exact reason, pls add info here if one of you know why :)
|
|
*/
|
|
#if defined(_WIN64)
|
|
void *vista_mapping_base_set[] = { (void *) 0x0000100000000000, (void *) 0x0000200000000000, (void *) 0x0000300000000000, (void *) 0x0000700000000000, 0 };
|
|
#else
|
|
void *vista_mapping_base_set[] = { (void *) 0x20000000, (void *) 0x21000000, (void *) 0x30000000, (void *) 0x31000000, (void *) 0x50000000, 0 };
|
|
#endif
|
|
void **wanted_mapping_base = default_mapping_base_set;
|
|
TSRMLS_FETCH();
|
|
|
|
zend_shared_alloc_lock_win32();
|
|
/* Mapping retries: When Apache2 restarts, the parent process startup routine
|
|
can be called before the child process is killed. In this case, the map will fail
|
|
and we have to sleep some time (until the child releases the mapping object) and retry.*/
|
|
do {
|
|
memfile = OpenFileMapping(FILE_MAP_WRITE, 0, create_name_with_username(ACCEL_FILEMAP_NAME));
|
|
err = GetLastError();
|
|
if (memfile == NULL) {
|
|
break;
|
|
}
|
|
|
|
ret = zend_shared_alloc_reattach(requested_size, error_in);
|
|
err = GetLastError();
|
|
if (ret == ALLOC_FAIL_MAPPING) {
|
|
/* Mapping failed, wait for mapping object to get freed and retry */
|
|
CloseHandle(memfile);
|
|
memfile = NULL;
|
|
if (++map_retries >= MAX_MAP_RETRIES) {
|
|
break;
|
|
}
|
|
zend_shared_alloc_unlock_win32();
|
|
Sleep(1000 * (map_retries + 1));
|
|
zend_shared_alloc_lock_win32();
|
|
} else {
|
|
zend_shared_alloc_unlock_win32();
|
|
return ret;
|
|
}
|
|
} while (1);
|
|
|
|
if (map_retries == MAX_MAP_RETRIES) {
|
|
zend_shared_alloc_unlock_win32();
|
|
zend_win_error_message(ACCEL_LOG_FATAL, "Unable to open file mapping", err);
|
|
*error_in = "OpenFileMapping";
|
|
return ALLOC_FAILURE;
|
|
}
|
|
|
|
/* creating segment here */
|
|
*shared_segments_count = 1;
|
|
*shared_segments_p = (zend_shared_segment **) calloc(1, sizeof(zend_shared_segment)+sizeof(void *));
|
|
if (!*shared_segments_p) {
|
|
zend_shared_alloc_unlock_win32();
|
|
zend_win_error_message(ACCEL_LOG_FATAL, "calloc() failed", GetLastError());
|
|
*error_in = "calloc";
|
|
return ALLOC_FAILURE;
|
|
}
|
|
shared_segment = (zend_shared_segment *)((char *)(*shared_segments_p) + sizeof(void *));
|
|
(*shared_segments_p)[0] = shared_segment;
|
|
|
|
memfile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, requested_size,
|
|
create_name_with_username(ACCEL_FILEMAP_NAME));
|
|
err = GetLastError();
|
|
if (memfile == NULL) {
|
|
zend_shared_alloc_unlock_win32();
|
|
zend_win_error_message(ACCEL_LOG_FATAL, "Unable to create file mapping", err);
|
|
*error_in = "CreateFileMapping";
|
|
return ALLOC_FAILURE;
|
|
}
|
|
|
|
/* Starting from windows Vista, heap randomization occurs which might cause our mapping base to
|
|
be taken (fail to map). So under Vista, we try to map into a hard coded predefined addresses
|
|
in high memory. */
|
|
if (!ZCG(accel_directives).mmap_base || !*ZCG(accel_directives).mmap_base) {
|
|
do {
|
|
OSVERSIONINFOEX osvi;
|
|
SYSTEM_INFO si;
|
|
|
|
ZeroMemory(&si, sizeof(SYSTEM_INFO));
|
|
ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
|
|
|
|
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
|
|
|
|
if (! GetVersionEx ((OSVERSIONINFO *) &osvi)) {
|
|
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
|
|
if (!GetVersionEx((OSVERSIONINFO *)&osvi)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
GetSystemInfo(&si);
|
|
|
|
/* Are we running Vista ? */
|
|
if (osvi.dwPlatformId == VER_PLATFORM_WIN32_NT && osvi.dwMajorVersion >= 6) {
|
|
wanted_mapping_base = vista_mapping_base_set;
|
|
}
|
|
} while (0);
|
|
} else {
|
|
char *s = ZCG(accel_directives).mmap_base;
|
|
|
|
/* skip leading 0x, %p assumes hexdeciaml format anyway */
|
|
if (*s == '0' && *(s + 1) == 'x') {
|
|
s += 2;
|
|
}
|
|
if (sscanf(s, "%p", &default_mapping_base_set[0]) != 1) {
|
|
zend_shared_alloc_unlock_win32();
|
|
zend_win_error_message(ACCEL_LOG_FATAL, "Bad mapping address specified in opcache.mmap_base", err);
|
|
return ALLOC_FAILURE;
|
|
}
|
|
}
|
|
|
|
do {
|
|
shared_segment->p = mapping_base = MapViewOfFileEx(memfile, FILE_MAP_ALL_ACCESS, 0, 0, 0, *wanted_mapping_base);
|
|
if (*wanted_mapping_base == NULL) { /* Auto address (NULL) is the last option on the array */
|
|
break;
|
|
}
|
|
wanted_mapping_base++;
|
|
} while (!mapping_base);
|
|
|
|
err = GetLastError();
|
|
if (mapping_base == NULL) {
|
|
zend_shared_alloc_unlock_win32();
|
|
zend_win_error_message(ACCEL_LOG_FATAL, "Unable to create view for file mapping", err);
|
|
*error_in = "MapViewOfFile";
|
|
return ALLOC_FAILURE;
|
|
} else {
|
|
char *mmap_base_file = get_mmap_base_file();
|
|
FILE *fp = fopen(mmap_base_file, "w");
|
|
err = GetLastError();
|
|
if (!fp) {
|
|
zend_shared_alloc_unlock_win32();
|
|
zend_win_error_message(ACCEL_LOG_WARNING, mmap_base_file, err);
|
|
zend_win_error_message(ACCEL_LOG_FATAL, "Unable to write base address", err);
|
|
return ALLOC_FAILURE;
|
|
}
|
|
fprintf(fp, "%p\n", mapping_base);
|
|
fclose(fp);
|
|
}
|
|
|
|
shared_segment->pos = 0;
|
|
shared_segment->size = requested_size;
|
|
|
|
zend_shared_alloc_unlock_win32();
|
|
|
|
return ALLOC_SUCCESS;
|
|
}
|
|
|
|
static int detach_segment(zend_shared_segment *shared_segment)
|
|
{
|
|
zend_shared_alloc_lock_win32();
|
|
if (mapping_base) {
|
|
UnmapViewOfFile(mapping_base);
|
|
}
|
|
CloseHandle(memfile);
|
|
zend_shared_alloc_unlock_win32();
|
|
CloseHandle(memory_mutex);
|
|
return 0;
|
|
}
|
|
|
|
static size_t segment_type_size(void)
|
|
{
|
|
return sizeof(zend_shared_segment);
|
|
}
|
|
|
|
zend_shared_memory_handlers zend_alloc_win32_handlers = {
|
|
create_segments,
|
|
detach_segment,
|
|
segment_type_size
|
|
};
|