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