]> granicus.if.org Git - postgresql/commitdiff
Support huge pages on Windows
authorMagnus Hagander <magnus@hagander.net>
Sun, 21 Jan 2018 14:40:46 +0000 (15:40 +0100)
committerMagnus Hagander <magnus@hagander.net>
Sun, 21 Jan 2018 14:40:46 +0000 (15:40 +0100)
Add support for huge pages (called large pages on Windows) to the
Windows build.

This (probably) breaks compatibility with Windows versions prior to
Windows 2003 or Windows Vista.

Authors: Takayuki Tsunakawa and Thomas Munro
Reviewed by: Magnus Hagander, Amit Kapila

doc/src/sgml/config.sgml
src/backend/port/win32_shmem.c
src/backend/utils/misc/guc.c
src/bin/pg_ctl/pg_ctl.c

index 37a61a13c899bdfd7320f5ed66544e78925a2110..cc156c6385e48760204a9fc5ba1d461b83ac13ab 100644 (file)
@@ -1369,14 +1369,26 @@ include_dir 'conf.d'
        </para>
 
        <para>
-        At present, this feature is supported only on Linux. The setting is
-        ignored on other systems when set to <literal>try</literal>.
+        At present, this feature is supported only on Linux and Windows. The
+        setting is ignored on other systems when set to <literal>try</literal>.
        </para>
 
        <para>
         The use of huge pages results in smaller page tables and less CPU time
-        spent on memory management, increasing performance. For more details,
-        see <xref linkend="linux-huge-pages"/>.
+        spent on memory management, increasing performance. For more details about
+        using huge pages on Linux, see <xref linkend="linux-huge-pages"/>.
+       </para>
+
+       <para>
+        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 <productname>PostgreSQL</productname>.
+        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, the command prompt must be run as an administrator
+        User Access Control (UAC) must be disabled. When the UAC is enabled, the normal
+        command prompt revokes the user right Lock Pages in Memory when started.
        </para>
 
        <para>
index 4991ed46f1246bbc7324aa8b33ddc7b878fbc342..fa80cebfbd869b8cdf6da32d49667bbb332a5fd0 100644 (file)
@@ -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, "
+                                        "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
index 72f6be329ef4ca65a9829c37b709b42ef902de2a..d03ba234b5d9ed6f662575ac37ce75719704732c 100644 (file)
@@ -3913,7 +3913,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 or Windows."),
                        NULL
                },
                &huge_pages,
index 62c72c3fcfae871c1327d39c07259831207d9bff..9bc830b08524e373692ffe71b9ef94733e6d0d26 100644 (file)
@@ -144,6 +144,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);
@@ -1623,11 +1624,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.
@@ -1650,6 +1646,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;
@@ -1708,14 +1705,21 @@ CreateRestrictedProcess(char *cmd, PROCESS_INFORMATION *processInfo, bool as_ser
                return 0;
        }
 
+       /* Get list of privileges to remove */
+       delPrivs = GetPrivilegesToDelete(origToken);
+       if (delPrivs == NULL)
+               /* Error message already printed */
+               return 0;
+
        b = _CreateRestrictedToken(origToken,
-                                                          DISABLE_MAX_PRIVILEGE,
+                                                          0,
                                                           sizeof(dropSids) / sizeof(dropSids[0]),
                                                           dropSids,
-                                                          0, NULL,
+                                                          delPrivs->PrivilegeCount, delPrivs->Privileges,
                                                           0, NULL,
                                                           &restrictedToken);
 
+       free(delPrivs);
        FreeSid(dropSids[1].Sid);
        FreeSid(dropSids[0].Sid);
        CloseHandle(origToken);
@@ -1832,6 +1836,65 @@ CreateRestrictedProcess(char *cmd, PROCESS_INFORMATION *processInfo, bool as_ser
         */
        return r;
 }
+
+/*
+ * Get a list of privileges to delete from the access token. We delete all privileges
+ * except SeLockMemoryPrivilege which is needed to use large pages, and
+ * SeChangeNotifyPrivilege which is enabled by default in DISABLE_MAX_PRIVILEGE.
+ */
+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