PostgreSQL Source Code  git master
win32_shmem.c
Go to the documentation of this file.
1 /*-------------------------------------------------------------------------
2  *
3  * win32_shmem.c
4  * Implement shared memory using win32 facilities
5  *
6  * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
7  *
8  * IDENTIFICATION
9  * src/backend/port/win32_shmem.c
10  *
11  *-------------------------------------------------------------------------
12  */
13 #include "postgres.h"
14 
15 #include "miscadmin.h"
16 #include "storage/dsm.h"
17 #include "storage/ipc.h"
18 #include "storage/pg_shmem.h"
19 
20 HANDLE UsedShmemSegID = INVALID_HANDLE_VALUE;
21 void *UsedShmemSegAddr = NULL;
23 
24 static bool EnableLockPagesPrivilege(int elevel);
25 static void pgwin32_SharedMemoryDelete(int status, Datum shmId);
26 
27 /*
28  * Generate shared memory segment name. Expand the data directory, to generate
29  * an identifier unique for this data directory. Then replace all backslashes
30  * with forward slashes, since backslashes aren't permitted in global object names.
31  *
32  * Store the shared memory segment in the Global\ namespace (requires NT2 TSE or
33  * 2000, but that's all we support for other reasons as well), to make sure you can't
34  * open two postmasters in different sessions against the same data directory.
35  *
36  * XXX: What happens with junctions? It's only someone breaking things on purpose,
37  * and this is still better than before, but we might want to do something about
38  * that sometime in the future.
39  */
40 static char *
42 {
43  char *retptr;
44  DWORD bufsize;
45  DWORD r;
46  char *cp;
47 
48  bufsize = GetFullPathName(DataDir, 0, NULL, NULL);
49  if (bufsize == 0)
50  elog(FATAL, "could not get size for full pathname of datadir %s: error code %lu",
51  DataDir, GetLastError());
52 
53  retptr = malloc(bufsize + 18); /* 18 for Global\PostgreSQL: */
54  if (retptr == NULL)
55  elog(FATAL, "could not allocate memory for shared memory name");
56 
57  strcpy(retptr, "Global\\PostgreSQL:");
58  r = GetFullPathName(DataDir, bufsize, retptr + 18, NULL);
59  if (r == 0 || r > bufsize)
60  elog(FATAL, "could not generate full pathname for datadir %s: error code %lu",
61  DataDir, GetLastError());
62 
63  /*
64  * XXX: Intentionally overwriting the Global\ part here. This was not the
65  * original approach, but putting it in the actual Global\ namespace
66  * causes permission errors in a lot of cases, so we leave it in the
67  * default namespace for now.
68  */
69  for (cp = retptr; *cp; cp++)
70  if (*cp == '\\')
71  *cp = '/';
72 
73  return retptr;
74 }
75 
76 
77 /*
78  * PGSharedMemoryIsInUse
79  *
80  * Is a previously-existing shmem segment still existing and in use?
81  *
82  * The point of this exercise is to detect the case where a prior postmaster
83  * crashed, but it left child backends that are still running. Therefore
84  * we only care about shmem segments that are associated with the intended
85  * DataDir. This is an important consideration since accidental matches of
86  * shmem segment IDs are reasonably common.
87  */
88 bool
89 PGSharedMemoryIsInUse(unsigned long id1, unsigned long id2)
90 {
91  char *szShareMem;
92  HANDLE hmap;
93 
94  szShareMem = GetSharedMemName();
95 
96  hmap = OpenFileMapping(FILE_MAP_READ, FALSE, szShareMem);
97 
98  free(szShareMem);
99 
100  if (hmap == NULL)
101  return false;
102 
103  CloseHandle(hmap);
104  return true;
105 }
106 
107 /*
108  * EnableLockPagesPrivilege
109  *
110  * Try to acquire SeLockMemoryPrivilege so we can use large pages.
111  */
112 static bool
114 {
115  HANDLE hToken;
116  TOKEN_PRIVILEGES tp;
117  LUID luid;
118 
119  if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
120  {
121  ereport(elevel,
122  (errmsg("could not enable Lock Pages in Memory user right: error code %lu", GetLastError()),
123  errdetail("Failed system call was %s.", "OpenProcessToken")));
124  return FALSE;
125  }
126 
127  if (!LookupPrivilegeValue(NULL, SE_LOCK_MEMORY_NAME, &luid))
128  {
129  ereport(elevel,
130  (errmsg("could not enable Lock Pages in Memory user right: error code %lu", GetLastError()),
131  errdetail("Failed system call was %s.", "LookupPrivilegeValue")));
132  CloseHandle(hToken);
133  return FALSE;
134  }
135  tp.PrivilegeCount = 1;
136  tp.Privileges[0].Luid = luid;
137  tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
138 
139  if (!AdjustTokenPrivileges(hToken, FALSE, &tp, 0, NULL, NULL))
140  {
141  ereport(elevel,
142  (errmsg("could not enable Lock Pages in Memory user right: error code %lu", GetLastError()),
143  errdetail("Failed system call was %s.", "AdjustTokenPrivileges")));
144  CloseHandle(hToken);
145  return FALSE;
146  }
147 
148  if (GetLastError() != ERROR_SUCCESS)
149  {
150  if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
151  ereport(elevel,
152  (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
153  errmsg("could not enable Lock Pages in Memory user right"),
154  errhint("Assign Lock Pages in Memory user right to the Windows user account which runs PostgreSQL.")));
155  else
156  ereport(elevel,
157  (errmsg("could not enable Lock Pages in Memory user right: error code %lu", GetLastError()),
158  errdetail("Failed system call was %s.", "AdjustTokenPrivileges")));
159  CloseHandle(hToken);
160  return FALSE;
161  }
162 
163  CloseHandle(hToken);
164 
165  return TRUE;
166 }
167 
168 /*
169  * PGSharedMemoryCreate
170  *
171  * Create a shared memory segment of the given size and initialize its
172  * standard header.
173  *
174  * makePrivate means to always create a new segment, rather than attach to
175  * or recycle any existing segment. On win32, we always create a new segment,
176  * since there is no need for recycling (segments go away automatically
177  * when the last backend exits)
178  */
180 PGSharedMemoryCreate(Size size, bool makePrivate, int port,
181  PGShmemHeader **shim)
182 {
183  void *memAddress;
184  PGShmemHeader *hdr;
185  HANDLE hmap,
186  hmap2;
187  char *szShareMem;
188  int i;
189  DWORD size_high;
190  DWORD size_low;
191  SIZE_T largePageSize = 0;
192  Size orig_size = size;
193  DWORD flProtect = PAGE_READWRITE;
194 
195  /* Room for a header? */
196  Assert(size > MAXALIGN(sizeof(PGShmemHeader)));
197 
198  szShareMem = GetSharedMemName();
199 
200  UsedShmemSegAddr = NULL;
201 
203  {
204  /* Does the processor support large pages? */
205  largePageSize = GetLargePageMinimum();
206  if (largePageSize == 0)
207  {
209  (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
210  errmsg("the processor does not support large pages")));
211  ereport(DEBUG1,
212  (errmsg("disabling huge pages")));
213  }
215  {
216  ereport(DEBUG1,
217  (errmsg("disabling huge pages")));
218  }
219  else
220  {
221  /* Huge pages available and privilege enabled, so turn on */
222  flProtect = PAGE_READWRITE | SEC_COMMIT | SEC_LARGE_PAGES;
223 
224  /* Round size up as appropriate. */
225  if (size % largePageSize != 0)
226  size += largePageSize - (size % largePageSize);
227  }
228  }
229 
230 retry:
231 #ifdef _WIN64
232  size_high = size >> 32;
233 #else
234  size_high = 0;
235 #endif
236  size_low = (DWORD) size;
237 
238  /*
239  * When recycling a shared memory segment, it may take a short while
240  * before it gets dropped from the global namespace. So re-try after
241  * sleeping for a second, and continue retrying 10 times. (both the 1
242  * second time and the 10 retries are completely arbitrary)
243  */
244  for (i = 0; i < 10; i++)
245  {
246  /*
247  * In case CreateFileMapping() doesn't set the error code to 0 on
248  * success
249  */
250  SetLastError(0);
251 
252  hmap = CreateFileMapping(INVALID_HANDLE_VALUE, /* Use the pagefile */
253  NULL, /* Default security attrs */
254  flProtect,
255  size_high, /* Size Upper 32 Bits */
256  size_low, /* Size Lower 32 bits */
257  szShareMem);
258 
259  if (!hmap)
260  {
261  if (GetLastError() == ERROR_NO_SYSTEM_RESOURCES &&
263  (flProtect & SEC_LARGE_PAGES) != 0)
264  {
265  elog(DEBUG1, "CreateFileMapping(%zu) with SEC_LARGE_PAGES failed, "
266  "huge pages disabled",
267  size);
268 
269  /*
270  * Use the original size, not the rounded-up value, when falling back
271  * to non-huge pages.
272  */
273  size = orig_size;
274  flProtect = PAGE_READWRITE;
275  goto retry;
276  }
277  else
278  ereport(FATAL,
279  (errmsg("could not create shared memory segment: error code %lu", GetLastError()),
280  errdetail("Failed system call was CreateFileMapping(size=%zu, name=%s).",
281  size, szShareMem)));
282  }
283 
284  /*
285  * If the segment already existed, CreateFileMapping() will return a
286  * handle to the existing one and set ERROR_ALREADY_EXISTS.
287  */
288  if (GetLastError() == ERROR_ALREADY_EXISTS)
289  {
290  CloseHandle(hmap); /* Close the handle, since we got a valid one
291  * to the previous segment. */
292  hmap = NULL;
293  Sleep(1000);
294  continue;
295  }
296  break;
297  }
298 
299  /*
300  * If the last call in the loop still returned ERROR_ALREADY_EXISTS, this
301  * shared memory segment exists and we assume it belongs to somebody else.
302  */
303  if (!hmap)
304  ereport(FATAL,
305  (errmsg("pre-existing shared memory block is still in use"),
306  errhint("Check if there are any old server processes still running, and terminate them.")));
307 
308  free(szShareMem);
309 
310  /*
311  * Make the handle inheritable
312  */
313  if (!DuplicateHandle(GetCurrentProcess(), hmap, GetCurrentProcess(), &hmap2, 0, TRUE, DUPLICATE_SAME_ACCESS))
314  ereport(FATAL,
315  (errmsg("could not create shared memory segment: error code %lu", GetLastError()),
316  errdetail("Failed system call was DuplicateHandle.")));
317 
318  /*
319  * Close the old, non-inheritable handle. If this fails we don't really
320  * care.
321  */
322  if (!CloseHandle(hmap))
323  elog(LOG, "could not close handle to shared memory: error code %lu", GetLastError());
324 
325 
326  /*
327  * Get a pointer to the new shared memory segment. Map the whole segment
328  * at once, and let the system decide on the initial address.
329  */
330  memAddress = MapViewOfFileEx(hmap2, FILE_MAP_WRITE | FILE_MAP_READ, 0, 0, 0, NULL);
331  if (!memAddress)
332  ereport(FATAL,
333  (errmsg("could not create shared memory segment: error code %lu", GetLastError()),
334  errdetail("Failed system call was MapViewOfFileEx.")));
335 
336 
337 
338  /*
339  * OK, we created a new segment. Mark it as created by this process. The
340  * order of assignments here is critical so that another Postgres process
341  * can't see the header as valid but belonging to an invalid PID!
342  */
343  hdr = (PGShmemHeader *) memAddress;
344  hdr->creatorPID = getpid();
345  hdr->magic = PGShmemMagic;
346 
347  /*
348  * Initialize space allocation status for segment.
349  */
350  hdr->totalsize = size;
351  hdr->freeoffset = MAXALIGN(sizeof(PGShmemHeader));
352  hdr->dsm_control = 0;
353 
354  /* Save info for possible future use */
355  UsedShmemSegAddr = memAddress;
356  UsedShmemSegSize = size;
357  UsedShmemSegID = hmap2;
358 
359  /* Register on-exit routine to delete the new segment */
361 
362  *shim = hdr;
363  return hdr;
364 }
365 
366 /*
367  * PGSharedMemoryReAttach
368  *
369  * This is called during startup of a postmaster child process to re-attach to
370  * an already existing shared memory segment, using the handle inherited from
371  * the postmaster.
372  *
373  * UsedShmemSegID and UsedShmemSegAddr are implicit parameters to this
374  * routine. The caller must have already restored them to the postmaster's
375  * values.
376  */
377 void
379 {
380  PGShmemHeader *hdr;
381  void *origUsedShmemSegAddr = UsedShmemSegAddr;
382 
383  Assert(UsedShmemSegAddr != NULL);
385 
386  /*
387  * Release memory region reservation that was made by the postmaster
388  */
389  if (VirtualFree(UsedShmemSegAddr, 0, MEM_RELEASE) == 0)
390  elog(FATAL, "failed to release reserved memory region (addr=%p): error code %lu",
391  UsedShmemSegAddr, GetLastError());
392 
393  hdr = (PGShmemHeader *) MapViewOfFileEx(UsedShmemSegID, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0, UsedShmemSegAddr);
394  if (!hdr)
395  elog(FATAL, "could not reattach to shared memory (key=%p, addr=%p): error code %lu",
396  UsedShmemSegID, UsedShmemSegAddr, GetLastError());
397  if (hdr != origUsedShmemSegAddr)
398  elog(FATAL, "reattaching to shared memory returned unexpected address (got %p, expected %p)",
399  hdr, origUsedShmemSegAddr);
400  if (hdr->magic != PGShmemMagic)
401  elog(FATAL, "reattaching to shared memory returned non-PostgreSQL memory");
402  dsm_set_control_handle(hdr->dsm_control);
403 
404  UsedShmemSegAddr = hdr; /* probably redundant */
405 }
406 
407 /*
408  * PGSharedMemoryNoReAttach
409  *
410  * This is called during startup of a postmaster child process when we choose
411  * *not* to re-attach to the existing shared memory segment. We must clean up
412  * to leave things in the appropriate state.
413  *
414  * The child process startup logic might or might not call PGSharedMemoryDetach
415  * after this; make sure that it will be a no-op if called.
416  *
417  * UsedShmemSegID and UsedShmemSegAddr are implicit parameters to this
418  * routine. The caller must have already restored them to the postmaster's
419  * values.
420  */
421 void
423 {
424  Assert(UsedShmemSegAddr != NULL);
426 
427  /*
428  * Under Windows we will not have mapped the segment, so we don't need to
429  * un-map it. Just reset UsedShmemSegAddr to show we're not attached.
430  */
431  UsedShmemSegAddr = NULL;
432 
433  /*
434  * We *must* close the inherited shmem segment handle, else Windows will
435  * consider the existence of this process to mean it can't release the
436  * shmem segment yet. We can now use PGSharedMemoryDetach to do that.
437  */
439 }
440 
441 /*
442  * PGSharedMemoryDetach
443  *
444  * Detach from the shared memory segment, if still attached. This is not
445  * intended to be called explicitly by the process that originally created the
446  * segment (it will have an on_shmem_exit callback registered to do that).
447  * Rather, this is for subprocesses that have inherited an attachment and want
448  * to get rid of it.
449  *
450  * UsedShmemSegID and UsedShmemSegAddr are implicit parameters to this
451  * routine.
452  */
453 void
455 {
456  /* Unmap the view, if it's mapped */
457  if (UsedShmemSegAddr != NULL)
458  {
459  if (!UnmapViewOfFile(UsedShmemSegAddr))
460  elog(LOG, "could not unmap view of shared memory: error code %lu",
461  GetLastError());
462 
463  UsedShmemSegAddr = NULL;
464  }
465 
466  /* And close the shmem handle, if we have one */
467  if (UsedShmemSegID != INVALID_HANDLE_VALUE)
468  {
469  if (!CloseHandle(UsedShmemSegID))
470  elog(LOG, "could not close handle to shared memory: error code %lu",
471  GetLastError());
472 
473  UsedShmemSegID = INVALID_HANDLE_VALUE;
474  }
475 }
476 
477 
478 /*
479  * pgwin32_SharedMemoryDelete
480  *
481  * Detach from and delete the shared memory segment
482  * (called as an on_shmem_exit callback, hence funny argument list)
483  */
484 static void
486 {
489 }
490 
491 /*
492  * pgwin32_ReserveSharedMemoryRegion(hChild)
493  *
494  * Reserve the memory region that will be used for shared memory in a child
495  * process. It is called before the child process starts, to make sure the
496  * memory is available.
497  *
498  * Once the child starts, DLLs loading in different order or threads getting
499  * scheduled differently may allocate memory which can conflict with the
500  * address space we need for our shared memory. By reserving the shared
501  * memory region before the child starts, and freeing it only just before we
502  * attempt to get access to the shared memory forces these allocations to
503  * be given different address ranges that don't conflict.
504  *
505  * NOTE! This function executes in the postmaster, and should for this
506  * reason not use elog(FATAL) since that would take down the postmaster.
507  */
508 int
510 {
511  void *address;
512 
513  Assert(UsedShmemSegAddr != NULL);
514  Assert(UsedShmemSegSize != 0);
515 
516  address = VirtualAllocEx(hChild, UsedShmemSegAddr, UsedShmemSegSize,
517  MEM_RESERVE, PAGE_READWRITE);
518  if (address == NULL)
519  {
520  /* Don't use FATAL since we're running in the postmaster */
521  elog(LOG, "could not reserve shared memory region (addr=%p) for child %p: error code %lu",
522  UsedShmemSegAddr, hChild, GetLastError());
523  return false;
524  }
525  if (address != UsedShmemSegAddr)
526  {
527  /*
528  * Should never happen - in theory if allocation granularity causes
529  * strange effects it could, so check just in case.
530  *
531  * Don't use FATAL since we're running in the postmaster.
532  */
533  elog(LOG, "reserved shared memory region got incorrect address %p, expected %p",
534  address, UsedShmemSegAddr);
535  VirtualFreeEx(hChild, address, 0, MEM_RELEASE);
536  return false;
537  }
538 
539  return true;
540 }
#define TRUE
Definition: ecpglib.h:35
pid_t creatorPID
Definition: pg_shmem.h:33
void PGSharedMemoryDetach(void)
Definition: win32_shmem.c:454
#define DEBUG1
Definition: elog.h:25
int errhint(const char *fmt,...)
Definition: elog.c:987
int pgwin32_ReserveSharedMemoryRegion(HANDLE hChild)
Definition: win32_shmem.c:509
dsm_handle dsm_control
Definition: pg_shmem.h:36
#define PointerGetDatum(X)
Definition: postgres.h:539
#define FALSE
Definition: ecpglib.h:39
static bool EnableLockPagesPrivilege(int elevel)
Definition: win32_shmem.c:113
void PGSharedMemoryNoReAttach(void)
Definition: win32_shmem.c:422
int errcode(int sqlerrcode)
Definition: elog.c:575
#define LOG
Definition: elog.h:26
PGShmemHeader * PGSharedMemoryCreate(Size size, bool makePrivate, int port, PGShmemHeader **shim)
Definition: win32_shmem.c:180
#define malloc(a)
Definition: header.h:50
#define FATAL
Definition: elog.h:52
void on_shmem_exit(pg_on_exit_callback function, Datum arg)
Definition: ipc.c:355
bool IsUnderPostmaster
Definition: globals.c:101
HANDLE UsedShmemSegID
Definition: win32_shmem.c:20
int errdetail(const char *fmt,...)
Definition: elog.c:873
void PGSharedMemoryReAttach(void)
Definition: win32_shmem.c:378
static void pgwin32_SharedMemoryDelete(int status, Datum shmId)
Definition: win32_shmem.c:485
#define ereport(elevel, rest)
Definition: elog.h:122
static int port
Definition: pg_regress.c:90
int32 magic
Definition: pg_shmem.h:31
static Size UsedShmemSegSize
Definition: win32_shmem.c:22
static int elevel
Definition: vacuumlazy.c:136
Size totalsize
Definition: pg_shmem.h:34
uintptr_t Datum
Definition: postgres.h:365
static char * GetSharedMemName(void)
Definition: win32_shmem.c:41
#define free(a)
Definition: header.h:65
#define Assert(condition)
Definition: c.h:688
#define PGShmemMagic
Definition: pg_shmem.h:32
Size freeoffset
Definition: pg_shmem.h:35
size_t Size
Definition: c.h:422
#define MAXALIGN(LEN)
Definition: c.h:641
int huge_pages
Definition: guc.c:488
#define DatumGetPointer(X)
Definition: postgres.h:532
bool PGSharedMemoryIsInUse(unsigned long id1, unsigned long id2)
Definition: win32_shmem.c:89
int errmsg(const char *fmt,...)
Definition: elog.c:797
int i
char * DataDir
Definition: globals.c:60
#define elog
Definition: elog.h:219
static void static void status(const char *fmt,...) pg_attribute_printf(1
Definition: pg_regress.c:225
void * UsedShmemSegAddr
Definition: win32_shmem.c:21