Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * pg_numa.c
4 : : * Basic NUMA portability routines
5 : : *
6 : : *
7 : : * Copyright (c) 2025, PostgreSQL Global Development Group
8 : : *
9 : : *
10 : : * IDENTIFICATION
11 : : * src/port/pg_numa.c
12 : : *
13 : : *-------------------------------------------------------------------------
14 : : */
15 : :
16 : : #include "c.h"
17 : : #include <unistd.h>
18 : :
19 : : #include "miscadmin.h"
20 : : #include "port/pg_numa.h"
21 : :
22 : : /*
23 : : * At this point we provide support only for Linux thanks to libnuma, but in
24 : : * future support for other platforms e.g. Win32 or FreeBSD might be possible
25 : : * too. For Win32 NUMA APIs see
26 : : * https://learn.microsoft.com/en-us/windows/win32/procthread/numa-support
27 : : */
28 : : #ifdef USE_LIBNUMA
29 : :
30 : : #include <numa.h>
31 : : #include <numaif.h>
32 : :
33 : : /*
34 : : * numa_move_pages() chunk size, has to be <= 16 to work around a kernel bug
35 : : * in do_pages_stat() (chunked by DO_PAGES_STAT_CHUNK_NR). By using the same
36 : : * chunk size, we make it work even on unfixed kernels.
37 : : *
38 : : * 64-bit system are not affected by the bug, and so use much larger chunks.
39 : : */
40 : : #if SIZEOF_SIZE_T == 4
41 : : #define NUMA_QUERY_CHUNK_SIZE 16
42 : : #else
43 : : #define NUMA_QUERY_CHUNK_SIZE 1024
44 : : #endif
45 : :
46 : : /* libnuma requires initialization as per numa(3) on Linux */
47 : : int
253 tomas.vondra@postgre 48 :CBC 9 : pg_numa_init(void)
49 : : {
50 : : int r;
51 : :
52 : : /*
53 : : * XXX libnuma versions before 2.0.19 don't handle EPERM by disabling
54 : : * NUMA, which then leads to unexpected failures later. This affects
55 : : * containers that disable get_mempolicy by a seccomp profile.
56 : : */
26 57 [ - + - - ]: 9 : if (get_mempolicy(NULL, NULL, 0, 0, 0) < 0 && (errno == EPERM))
26 tomas.vondra@postgre 58 :UBC 0 : r = -1;
59 : : else
26 tomas.vondra@postgre 60 :CBC 9 : r = numa_available();
61 : :
253 62 : 9 : return r;
63 : : }
64 : :
65 : : /*
66 : : * We use move_pages(2) syscall here - instead of get_mempolicy(2) - as the
67 : : * first one allows us to batch and query about many memory pages in one single
68 : : * giant system call that is way faster.
69 : : *
70 : : * We call numa_move_pages() for smaller chunks of the whole array. The first
71 : : * reason is to work around a kernel bug, but also to allow interrupting the
72 : : * query between the calls (for many pointers processing the whole array can
73 : : * take a lot of time).
74 : : */
75 : : int
76 : 226 : pg_numa_query_pages(int pid, unsigned long count, void **pages, int *status)
77 : : {
168 78 : 226 : unsigned long next = 0;
79 : 226 : int ret = 0;
80 : :
81 : : /*
82 : : * Chunk pointers passed to numa_move_pages to NUMA_QUERY_CHUNK_SIZE
83 : : * items, to work around a kernel bug in do_pages_stat().
84 : : */
85 [ + + ]: 579 : while (next < count)
86 : : {
87 : 353 : unsigned long count_chunk = Min(count - next,
88 : : NUMA_QUERY_CHUNK_SIZE);
89 : :
90 [ - + ]: 353 : CHECK_FOR_INTERRUPTS();
91 : :
92 : : /*
93 : : * Bail out if any of the chunks errors out (ret<0). We ignore (ret>0)
94 : : * which is used to return number of nonmigrated pages, but we're not
95 : : * migrating any pages here.
96 : : */
97 : 353 : ret = numa_move_pages(pid, count_chunk, &pages[next], NULL, &status[next], 0);
98 [ - + ]: 353 : if (ret < 0)
99 : : {
100 : : /* plain error, return as is */
168 tomas.vondra@postgre 101 :UBC 0 : return ret;
102 : : }
103 : :
168 tomas.vondra@postgre 104 :CBC 353 : next += count_chunk;
105 : : }
106 : :
107 : : /* should have consumed the input array exactly */
108 [ - + ]: 226 : Assert(next == count);
109 : :
110 : 226 : return 0;
111 : : }
112 : :
113 : : int
253 114 : 3 : pg_numa_get_max_node(void)
115 : : {
116 : 3 : return numa_max_node();
117 : : }
118 : :
119 : : #else
120 : :
121 : : /* Empty wrappers */
122 : : int
123 : : pg_numa_init(void)
124 : : {
125 : : /* We state that NUMA is not available */
126 : : return -1;
127 : : }
128 : :
129 : : int
130 : : pg_numa_query_pages(int pid, unsigned long count, void **pages, int *status)
131 : : {
132 : : return 0;
133 : : }
134 : :
135 : : int
136 : : pg_numa_get_max_node(void)
137 : : {
138 : : return 0;
139 : : }
140 : :
141 : : #endif
|