diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index ac339fb..18fb3a4 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -1350,17 +1350,29 @@ include_dir 'conf.d' - At present, this feature is supported only on Linux. The setting is - ignored on other systems when set to try. + At present, this feature is supported only on Linux and Windows. The + setting is ignored on other systems when set to try. The use of huge pages results in smaller page tables and less CPU time - spent on memory management, increasing performance. For more details, + spent on memory management, increasing performance. For more details on Linux, see . + Huge pages are known as large pages on Windows. To use them, you need to + assign the user right Lock Pages in Memory to the Windows user account + that runs PostgreSQL. + You can use Windows Group Policy tool (gpedit.msc) to assign the user right + Lock Pages in Memory. + To start the database server on the command prompt as a standalone process, + not as a Windows service, run the command prompt as an administrator or + disable the User Access Control (UAC). When the UAC is enabled, the normal + command prompt revokes the user right Lock Pages in Memory. + + + With huge_pages set to try, the server will try to use huge pages, but fall back to using normal allocation if that fails. With on, failure diff --git a/src/backend/port/win32_shmem.c b/src/backend/port/win32_shmem.c index 31489fc..793ee03 100644 --- a/src/backend/port/win32_shmem.c +++ b/src/backend/port/win32_shmem.c @@ -21,6 +21,7 @@ HANDLE UsedShmemSegID = INVALID_HANDLE_VALUE; void *UsedShmemSegAddr = NULL; static Size UsedShmemSegSize = 0; +static bool EnableLockPagesPrivilege(int elevel); static void pgwin32_SharedMemoryDelete(int status, Datum shmId); /* @@ -103,6 +104,66 @@ PGSharedMemoryIsInUse(unsigned long id1, unsigned long id2) return true; } +/* + * EnableLockPagesPrivilege + * + * Try to acquire SeLockMemoryPrivilege so we can use large pages. + */ +static bool +EnableLockPagesPrivilege(int elevel) +{ + HANDLE hToken; + TOKEN_PRIVILEGES tp; + LUID luid; + + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) + { + ereport(elevel, + (errmsg("could not enable Lock Pages in Memory user right: error code %lu", GetLastError()), + errdetail("Failed system call was %s.", "OpenProcessToken"))); + return FALSE; + } + + if (!LookupPrivilegeValue(NULL, SE_LOCK_MEMORY_NAME, &luid)) + { + ereport(elevel, + (errmsg("could not enable Lock Pages in Memory user right: error code %lu", GetLastError()), + errdetail("Failed system call was %s.", "LookupPrivilegeValue"))); + CloseHandle(hToken); + return FALSE; + } + tp.PrivilegeCount = 1; + tp.Privileges[0].Luid = luid; + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + if (!AdjustTokenPrivileges(hToken, FALSE, &tp, 0, NULL, NULL)) + { + ereport(elevel, + (errmsg("could not enable Lock Pages in Memory user right: error code %lu", GetLastError()), + errdetail("Failed system call was %s.", "AdjustTokenPrivileges"))); + CloseHandle(hToken); + return FALSE; + } + + if (GetLastError() != ERROR_SUCCESS) + { + if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) + ereport(elevel, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("could not enable Lock Pages in Memory user right"), + errhint("Assign Lock Pages in Memory user right to the Windows user account which runs PostgreSQL."))); + else + ereport(elevel, + (errmsg("could not enable Lock Pages in Memory user right: error code %lu", GetLastError()), + errdetail("Failed system call was %s.", "AdjustTokenPrivileges"))); + CloseHandle(hToken); + return FALSE; + } + + CloseHandle(hToken); + + return TRUE; +} /* * PGSharedMemoryCreate @@ -127,11 +188,9 @@ PGSharedMemoryCreate(Size size, bool makePrivate, int port, int i; DWORD size_high; DWORD size_low; - - if (huge_pages == HUGE_PAGES_ON) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("huge pages not supported on this platform"))); + SIZE_T largePageSize = 0; + Size orig_size = size; + DWORD flProtect = PAGE_READWRITE; /* Room for a header? */ Assert(size > MAXALIGN(sizeof(PGShmemHeader))); @@ -140,6 +199,35 @@ PGSharedMemoryCreate(Size size, bool makePrivate, int port, UsedShmemSegAddr = NULL; + if (huge_pages == HUGE_PAGES_ON || huge_pages == HUGE_PAGES_TRY) + { + /* Does the processor support large pages? */ + largePageSize = GetLargePageMinimum(); + if (largePageSize == 0) + { + ereport(huge_pages == HUGE_PAGES_ON ? FATAL : DEBUG1, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("the processor does not support large pages"))); + ereport(DEBUG1, + (errmsg("disabling huge pages"))); + } + else if (!EnableLockPagesPrivilege(huge_pages == HUGE_PAGES_ON ? FATAL : DEBUG1)) + { + ereport(DEBUG1, + (errmsg("disabling huge pages"))); + } + else + { + /* Huge pages available and privilege enabled, so turn on */ + flProtect = PAGE_READWRITE | SEC_COMMIT | SEC_LARGE_PAGES; + + /* Round size up as appropriate. */ + if (size % largePageSize != 0) + size += largePageSize - (size % largePageSize); + } + } + +retry: #ifdef _WIN64 size_high = size >> 32; #else @@ -163,16 +251,35 @@ PGSharedMemoryCreate(Size size, bool makePrivate, int port, hmap = CreateFileMapping(INVALID_HANDLE_VALUE, /* Use the pagefile */ NULL, /* Default security attrs */ - PAGE_READWRITE, /* Memory is Read/Write */ + flProtect, size_high, /* Size Upper 32 Bits */ size_low, /* Size Lower 32 bits */ szShareMem); if (!hmap) - ereport(FATAL, - (errmsg("could not create shared memory segment: error code %lu", GetLastError()), - errdetail("Failed system call was CreateFileMapping(size=%zu, name=%s).", - size, szShareMem))); + { + if (GetLastError() == ERROR_NO_SYSTEM_RESOURCES && + huge_pages == HUGE_PAGES_TRY && + (flProtect & SEC_LARGE_PAGES) != 0) + { + elog(DEBUG1, "CreateFileMapping(%zu) with SEC_LARGE_PAGES failed " + "due to insufficient large pages, huge pages disabled", + size); + + /* + * Use the original size, not the rounded-up value, when falling back + * to non-huge pages. + */ + size = orig_size; + flProtect = PAGE_READWRITE; + goto retry; + } + else + ereport(FATAL, + (errmsg("could not create shared memory segment: error code %lu", GetLastError()), + errdetail("Failed system call was CreateFileMapping(size=%zu, name=%s).", + size, szShareMem))); + } /* * If the segment already existed, CreateFileMapping() will return a diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 8b5f064..b871679 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -3857,7 +3857,7 @@ static struct config_enum ConfigureNamesEnum[] = { {"huge_pages", PGC_POSTMASTER, RESOURCES_MEM, - gettext_noop("Use of huge pages on Linux."), + gettext_noop("Use of huge pages on Linux/Windows."), NULL }, &huge_pages, diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c index c63819b..5c93d85 100644 --- a/src/bin/pg_ctl/pg_ctl.c +++ b/src/bin/pg_ctl/pg_ctl.c @@ -140,6 +140,7 @@ static void WINAPI pgwin32_ServiceHandler(DWORD); static void WINAPI pgwin32_ServiceMain(DWORD, LPTSTR *); static void pgwin32_doRunAsService(void); static int CreateRestrictedProcess(char *cmd, PROCESS_INFORMATION *processInfo, bool as_service); +static PTOKEN_PRIVILEGES GetPrivilegesToDelete(HANDLE hToken); #endif static pgpid_t get_pgpid(bool is_status_request); @@ -1708,11 +1709,6 @@ typedef BOOL (WINAPI * __SetInformationJobObject) (HANDLE, JOBOBJECTINFOCLASS, L typedef BOOL (WINAPI * __AssignProcessToJobObject) (HANDLE, HANDLE); typedef BOOL (WINAPI * __QueryInformationJobObject) (HANDLE, JOBOBJECTINFOCLASS, LPVOID, DWORD, LPDWORD); -/* Windows API define missing from some versions of MingW headers */ -#ifndef DISABLE_MAX_PRIVILEGE -#define DISABLE_MAX_PRIVILEGE 0x1 -#endif - /* * Create a restricted token, a job object sandbox, and execute the specified * process with it. @@ -1735,6 +1731,7 @@ CreateRestrictedProcess(char *cmd, PROCESS_INFORMATION *processInfo, bool as_ser HANDLE restrictedToken; SID_IDENTIFIER_AUTHORITY NtAuthority = {SECURITY_NT_AUTHORITY}; SID_AND_ATTRIBUTES dropSids[2]; + PTOKEN_PRIVILEGES delPrivs; /* Functions loaded dynamically */ __CreateRestrictedToken _CreateRestrictedToken = NULL; @@ -1793,11 +1790,17 @@ CreateRestrictedProcess(char *cmd, PROCESS_INFORMATION *processInfo, bool as_ser return 0; } + /* Get list of privileges to remove */ + delPrivs = GetPrivilegesToDelete(origToken); + if (delPrivs == NULL) + /* The message was output in the function */ + return 0; + b = _CreateRestrictedToken(origToken, - DISABLE_MAX_PRIVILEGE, + 0, sizeof(dropSids) / sizeof(dropSids[0]), dropSids, - 0, NULL, + delPrivs->PrivilegeCount, delPrivs->Privileges, 0, NULL, &restrictedToken); @@ -1917,6 +1920,67 @@ CreateRestrictedProcess(char *cmd, PROCESS_INFORMATION *processInfo, bool as_ser */ return r; } + +/* + * Get a list of privileges to delete from the access token. + * We used to delete all privileges (except SeChangeNotifyName) by passing + * DISABLE_MAX_PRIVILEGE when we invoke postgres.exe. However, we need to retain + * SeLockMemoryPrivilege for large pages. + * So, we delete particular privileges instead by passing. + */ +static PTOKEN_PRIVILEGES +GetPrivilegesToDelete(HANDLE hToken) +{ + int i, j; + DWORD length; + PTOKEN_PRIVILEGES tokenPrivs; + LUID luidLockPages; + LUID luidChangeNotify; + + if (!LookupPrivilegeValue(NULL, SE_LOCK_MEMORY_NAME, &luidLockPages) || + !LookupPrivilegeValue(NULL, SE_CHANGE_NOTIFY_NAME, &luidChangeNotify)) + { + write_stderr(_("%s: could not get LUIDs for privileges: error code %lu\n"), + progname, (unsigned long) GetLastError()); + return NULL; + } + + if (!GetTokenInformation(hToken, TokenPrivileges, NULL, 0, &length) && + GetLastError() != ERROR_INSUFFICIENT_BUFFER) + { + write_stderr(_("%s: could not get token information: error code %lu\n"), + progname, (unsigned long) GetLastError()); + return NULL; + } + + tokenPrivs = (PTOKEN_PRIVILEGES) malloc(length); + if (tokenPrivs == NULL) + { + write_stderr(_("%s: out of memory\n"), progname); + return NULL; + } + + if (!GetTokenInformation(hToken, TokenPrivileges, tokenPrivs, length, &length)) + { + write_stderr(_("%s: could not get token information: error code %lu\n"), + progname, (unsigned long) GetLastError()); + free(tokenPrivs); + return NULL; + } + + for (i = 0; i < tokenPrivs->PrivilegeCount; i++) + { + if (memcmp(&tokenPrivs->Privileges[i].Luid, &luidLockPages, sizeof(LUID)) == 0 || + memcmp(&tokenPrivs->Privileges[i].Luid, &luidChangeNotify, sizeof(LUID)) == 0) + { + for (j = i; j < tokenPrivs->PrivilegeCount - 1; j++) + tokenPrivs->Privileges[j] = tokenPrivs->Privileges[j + 1]; + tokenPrivs->PrivilegeCount--; + } + } + + return tokenPrivs; +} #endif /* WIN32 */ static void