diff options
author | Simon Guo <wei.guo.simon@gmail.com> | 2016-10-07 19:59:52 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2016-10-07 21:46:28 -0400 |
commit | 26b4224d99615a19c002508c6e80bd3d1d783b64 (patch) | |
tree | 2d8f893c4d47888dc09641a2d4b75318bd66f157 /tools/testing/selftests | |
parent | d5aed9c06712520a6e919dc5c0525e39d9795124 (diff) |
selftests: expanding more mlock selftest
This patch will randomly perform mlock/mlock2 on a given memory region,
and verify the RLIMIT_MEMLOCK limitation works properly.
Suggested-by: David Rientjes <rientjes@google.com>
Link: http://lkml.kernel.org/r/1473325970-11393-4-git-send-email-wei.guo.simon@gmail.com
Signed-off-by: Simon Guo <wei.guo.simon@gmail.com>
Cc: Shuah Khan <shuah@kernel.org>
Cc: Vlastimil Babka <vbabka@suse.cz>
Cc: "Kirill A. Shutemov" <kirill.shutemov@linux.intel.com>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Eric B Munson <emunson@akamai.com>
Cc: Simon Guo <wei.guo.simon@gmail.com>
Cc: Mel Gorman <mgorman@techsingularity.net>
Cc: Alexey Klimov <klimov.linux@gmail.com>
Cc: Andrea Arcangeli <aarcange@redhat.com>
Cc: Thierry Reding <treding@nvidia.com>
Cc: Mike Kravetz <mike.kravetz@oracle.com>
Cc: Geert Uytterhoeven <geert@linux-m68k.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'tools/testing/selftests')
-rw-r--r-- | tools/testing/selftests/vm/Makefile | 4 | ||||
-rw-r--r-- | tools/testing/selftests/vm/mlock-intersect-test.c | 76 | ||||
-rw-r--r-- | tools/testing/selftests/vm/mlock-random-test.c | 293 |
3 files changed, 295 insertions, 78 deletions
diff --git a/tools/testing/selftests/vm/Makefile b/tools/testing/selftests/vm/Makefile index a0412a80b679..bbab7f4664ac 100644 --- a/tools/testing/selftests/vm/Makefile +++ b/tools/testing/selftests/vm/Makefile | |||
@@ -10,7 +10,7 @@ BINARIES += on-fault-limit | |||
10 | BINARIES += thuge-gen | 10 | BINARIES += thuge-gen |
11 | BINARIES += transhuge-stress | 11 | BINARIES += transhuge-stress |
12 | BINARIES += userfaultfd | 12 | BINARIES += userfaultfd |
13 | BINARIES += mlock-intersect-test | 13 | BINARIES += mlock-random-test |
14 | 14 | ||
15 | all: $(BINARIES) | 15 | all: $(BINARIES) |
16 | %: %.c | 16 | %: %.c |
@@ -18,7 +18,7 @@ all: $(BINARIES) | |||
18 | userfaultfd: userfaultfd.c ../../../../usr/include/linux/kernel.h | 18 | userfaultfd: userfaultfd.c ../../../../usr/include/linux/kernel.h |
19 | $(CC) $(CFLAGS) -O2 -o $@ $< -lpthread | 19 | $(CC) $(CFLAGS) -O2 -o $@ $< -lpthread |
20 | 20 | ||
21 | mlock-intersect-test: mlock-intersect-test.c | 21 | mlock-random-test: mlock-random-test.c |
22 | $(CC) $(CFLAGS) -o $@ $< -lcap | 22 | $(CC) $(CFLAGS) -o $@ $< -lcap |
23 | 23 | ||
24 | ../../../../usr/include/linux/kernel.h: | 24 | ../../../../usr/include/linux/kernel.h: |
diff --git a/tools/testing/selftests/vm/mlock-intersect-test.c b/tools/testing/selftests/vm/mlock-intersect-test.c deleted file mode 100644 index f78e68a0967c..000000000000 --- a/tools/testing/selftests/vm/mlock-intersect-test.c +++ /dev/null | |||
@@ -1,76 +0,0 @@ | |||
1 | /* | ||
2 | * It tests the duplicate mlock result: | ||
3 | * - the ulimit of lock page is 64k | ||
4 | * - allocate address area 64k starting from p | ||
5 | * - mlock [p -- p + 30k] | ||
6 | * - Then mlock address [ p -- p + 40k ] | ||
7 | * | ||
8 | * It should succeed since totally we locked | ||
9 | * 40k < 64k limitation. | ||
10 | * | ||
11 | * It should not be run with CAP_IPC_LOCK. | ||
12 | */ | ||
13 | #include <stdlib.h> | ||
14 | #include <stdio.h> | ||
15 | #include <unistd.h> | ||
16 | #include <sys/resource.h> | ||
17 | #include <sys/capability.h> | ||
18 | #include <sys/mman.h> | ||
19 | #include "mlock2.h" | ||
20 | |||
21 | int main(int argc, char **argv) | ||
22 | { | ||
23 | struct rlimit new; | ||
24 | char *p = NULL; | ||
25 | cap_t cap = cap_init(); | ||
26 | int i; | ||
27 | |||
28 | /* drop capabilities including CAP_IPC_LOCK */ | ||
29 | if (cap_set_proc(cap)) | ||
30 | return -1; | ||
31 | |||
32 | /* set mlock limits to 64k */ | ||
33 | new.rlim_cur = 65536; | ||
34 | new.rlim_max = 65536; | ||
35 | setrlimit(RLIMIT_MEMLOCK, &new); | ||
36 | |||
37 | /* test VM_LOCK */ | ||
38 | p = malloc(1024 * 64); | ||
39 | if (mlock(p, 1024 * 30)) { | ||
40 | printf("mlock() 30k return failure.\n"); | ||
41 | return -1; | ||
42 | } | ||
43 | for (i = 0; i < 10; i++) { | ||
44 | if (mlock(p, 1024 * 40)) { | ||
45 | printf("mlock() #%d 40k returns failure.\n", i); | ||
46 | return -1; | ||
47 | } | ||
48 | } | ||
49 | for (i = 0; i < 10; i++) { | ||
50 | if (mlock2_(p, 1024 * 40, MLOCK_ONFAULT)) { | ||
51 | printf("mlock2_() #%d 40k returns failure.\n", i); | ||
52 | return -1; | ||
53 | } | ||
54 | } | ||
55 | free(p); | ||
56 | |||
57 | /* Test VM_LOCKONFAULT */ | ||
58 | p = malloc(1024 * 64); | ||
59 | if (mlock2_(p, 1024 * 30, MLOCK_ONFAULT)) { | ||
60 | printf("mlock2_() 30k return failure.\n"); | ||
61 | return -1; | ||
62 | } | ||
63 | for (i = 0; i < 10; i++) { | ||
64 | if (mlock2_(p, 1024 * 40, MLOCK_ONFAULT)) { | ||
65 | printf("mlock2_() #%d 40k returns failure.\n", i); | ||
66 | return -1; | ||
67 | } | ||
68 | } | ||
69 | for (i = 0; i < 10; i++) { | ||
70 | if (mlock(p, 1024 * 40)) { | ||
71 | printf("mlock() #%d 40k returns failure.\n", i); | ||
72 | return -1; | ||
73 | } | ||
74 | } | ||
75 | return 0; | ||
76 | } | ||
diff --git a/tools/testing/selftests/vm/mlock-random-test.c b/tools/testing/selftests/vm/mlock-random-test.c new file mode 100644 index 000000000000..83de4f58d262 --- /dev/null +++ b/tools/testing/selftests/vm/mlock-random-test.c | |||
@@ -0,0 +1,293 @@ | |||
1 | /* | ||
2 | * It tests the mlock/mlock2() when they are invoked | ||
3 | * on randomly memory region. | ||
4 | */ | ||
5 | #include <unistd.h> | ||
6 | #include <sys/resource.h> | ||
7 | #include <sys/capability.h> | ||
8 | #include <sys/mman.h> | ||
9 | #include <fcntl.h> | ||
10 | #include <string.h> | ||
11 | #include <sys/ipc.h> | ||
12 | #include <sys/shm.h> | ||
13 | #include <time.h> | ||
14 | #include "mlock2.h" | ||
15 | |||
16 | #define CHUNK_UNIT (128 * 1024) | ||
17 | #define MLOCK_RLIMIT_SIZE (CHUNK_UNIT * 2) | ||
18 | #define MLOCK_WITHIN_LIMIT_SIZE CHUNK_UNIT | ||
19 | #define MLOCK_OUTOF_LIMIT_SIZE (CHUNK_UNIT * 3) | ||
20 | |||
21 | #define TEST_LOOP 100 | ||
22 | #define PAGE_ALIGN(size, ps) (((size) + ((ps) - 1)) & ~((ps) - 1)) | ||
23 | |||
24 | int set_cap_limits(rlim_t max) | ||
25 | { | ||
26 | struct rlimit new; | ||
27 | cap_t cap = cap_init(); | ||
28 | |||
29 | new.rlim_cur = max; | ||
30 | new.rlim_max = max; | ||
31 | if (setrlimit(RLIMIT_MEMLOCK, &new)) { | ||
32 | perror("setrlimit() returns error\n"); | ||
33 | return -1; | ||
34 | } | ||
35 | |||
36 | /* drop capabilities including CAP_IPC_LOCK */ | ||
37 | if (cap_set_proc(cap)) { | ||
38 | perror("cap_set_proc() returns error\n"); | ||
39 | return -2; | ||
40 | } | ||
41 | |||
42 | return 0; | ||
43 | } | ||
44 | |||
45 | int get_proc_locked_vm_size(void) | ||
46 | { | ||
47 | FILE *f; | ||
48 | int ret = -1; | ||
49 | char line[1024] = {0}; | ||
50 | unsigned long lock_size = 0; | ||
51 | |||
52 | f = fopen("/proc/self/status", "r"); | ||
53 | if (!f) { | ||
54 | perror("fopen"); | ||
55 | return -1; | ||
56 | } | ||
57 | |||
58 | while (fgets(line, 1024, f)) { | ||
59 | if (strstr(line, "VmLck")) { | ||
60 | ret = sscanf(line, "VmLck:\t%8lu kB", &lock_size); | ||
61 | if (ret <= 0) { | ||
62 | printf("sscanf() on VmLck error: %s: %d\n", | ||
63 | line, ret); | ||
64 | fclose(f); | ||
65 | return -1; | ||
66 | } | ||
67 | fclose(f); | ||
68 | return (int)(lock_size << 10); | ||
69 | } | ||
70 | } | ||
71 | |||
72 | perror("cann't parse VmLck in /proc/self/status\n"); | ||
73 | fclose(f); | ||
74 | return -1; | ||
75 | } | ||
76 | |||
77 | /* | ||
78 | * Get the MMUPageSize of the memory region including input | ||
79 | * address from proc file. | ||
80 | * | ||
81 | * return value: on error case, 0 will be returned. | ||
82 | * Otherwise the page size(in bytes) is returned. | ||
83 | */ | ||
84 | int get_proc_page_size(unsigned long addr) | ||
85 | { | ||
86 | FILE *smaps; | ||
87 | char *line; | ||
88 | unsigned long mmupage_size = 0; | ||
89 | size_t size; | ||
90 | |||
91 | smaps = seek_to_smaps_entry(addr); | ||
92 | if (!smaps) { | ||
93 | printf("Unable to parse /proc/self/smaps\n"); | ||
94 | return 0; | ||
95 | } | ||
96 | |||
97 | while (getline(&line, &size, smaps) > 0) { | ||
98 | if (!strstr(line, "MMUPageSize")) { | ||
99 | free(line); | ||
100 | line = NULL; | ||
101 | size = 0; | ||
102 | continue; | ||
103 | } | ||
104 | |||
105 | /* found the MMUPageSize of this section */ | ||
106 | if (sscanf(line, "MMUPageSize: %8lu kB", | ||
107 | &mmupage_size) < 1) { | ||
108 | printf("Unable to parse smaps entry for Size:%s\n", | ||
109 | line); | ||
110 | break; | ||
111 | } | ||
112 | |||
113 | } | ||
114 | free(line); | ||
115 | if (smaps) | ||
116 | fclose(smaps); | ||
117 | return mmupage_size << 10; | ||
118 | } | ||
119 | |||
120 | /* | ||
121 | * Test mlock/mlock2() on provided memory chunk. | ||
122 | * It expects the mlock/mlock2() to be successful (within rlimit) | ||
123 | * | ||
124 | * With allocated memory chunk [p, p + alloc_size), this | ||
125 | * test will choose start/len randomly to perform mlock/mlock2 | ||
126 | * [start, start + len] memory range. The range is within range | ||
127 | * of the allocated chunk. | ||
128 | * | ||
129 | * The memory region size alloc_size is within the rlimit. | ||
130 | * So we always expect a success of mlock/mlock2. | ||
131 | * | ||
132 | * VmLck is assumed to be 0 before this test. | ||
133 | * | ||
134 | * return value: 0 - success | ||
135 | * else: failure | ||
136 | */ | ||
137 | int test_mlock_within_limit(char *p, int alloc_size) | ||
138 | { | ||
139 | int i; | ||
140 | int ret = 0; | ||
141 | int locked_vm_size = 0; | ||
142 | struct rlimit cur; | ||
143 | int page_size = 0; | ||
144 | |||
145 | getrlimit(RLIMIT_MEMLOCK, &cur); | ||
146 | if (cur.rlim_cur < alloc_size) { | ||
147 | printf("alloc_size[%d] < %u rlimit,lead to mlock failure\n", | ||
148 | alloc_size, (unsigned int)cur.rlim_cur); | ||
149 | return -1; | ||
150 | } | ||
151 | |||
152 | srand(time(NULL)); | ||
153 | for (i = 0; i < TEST_LOOP; i++) { | ||
154 | /* | ||
155 | * - choose mlock/mlock2 randomly | ||
156 | * - choose lock_size randomly but lock_size < alloc_size | ||
157 | * - choose start_offset randomly but p+start_offset+lock_size | ||
158 | * < p+alloc_size | ||
159 | */ | ||
160 | int is_mlock = !!(rand() % 2); | ||
161 | int lock_size = rand() % alloc_size; | ||
162 | int start_offset = rand() % (alloc_size - lock_size); | ||
163 | |||
164 | if (is_mlock) | ||
165 | ret = mlock(p + start_offset, lock_size); | ||
166 | else | ||
167 | ret = mlock2_(p + start_offset, lock_size, | ||
168 | MLOCK_ONFAULT); | ||
169 | |||
170 | if (ret) { | ||
171 | printf("%s() failure at |%p(%d)| mlock:|%p(%d)|\n", | ||
172 | is_mlock ? "mlock" : "mlock2", | ||
173 | p, alloc_size, | ||
174 | p + start_offset, lock_size); | ||
175 | return ret; | ||
176 | } | ||
177 | } | ||
178 | |||
179 | /* | ||
180 | * Check VmLck left by the tests. | ||
181 | */ | ||
182 | locked_vm_size = get_proc_locked_vm_size(); | ||
183 | page_size = get_proc_page_size((unsigned long)p); | ||
184 | if (page_size == 0) { | ||
185 | printf("cannot get proc MMUPageSize\n"); | ||
186 | return -1; | ||
187 | } | ||
188 | |||
189 | if (locked_vm_size > PAGE_ALIGN(alloc_size, page_size) + page_size) { | ||
190 | printf("test_mlock_within_limit() left VmLck:%d on %d chunk\n", | ||
191 | locked_vm_size, alloc_size); | ||
192 | return -1; | ||
193 | } | ||
194 | |||
195 | return 0; | ||
196 | } | ||
197 | |||
198 | |||
199 | /* | ||
200 | * We expect the mlock/mlock2() to be fail (outof limitation) | ||
201 | * | ||
202 | * With allocated memory chunk [p, p + alloc_size), this | ||
203 | * test will randomly choose start/len and perform mlock/mlock2 | ||
204 | * on [start, start+len] range. | ||
205 | * | ||
206 | * The memory region size alloc_size is above the rlimit. | ||
207 | * And the len to be locked is higher than rlimit. | ||
208 | * So we always expect a failure of mlock/mlock2. | ||
209 | * No locked page number should be increased as a side effect. | ||
210 | * | ||
211 | * return value: 0 - success | ||
212 | * else: failure | ||
213 | */ | ||
214 | int test_mlock_outof_limit(char *p, int alloc_size) | ||
215 | { | ||
216 | int i; | ||
217 | int ret = 0; | ||
218 | int locked_vm_size = 0, old_locked_vm_size = 0; | ||
219 | struct rlimit cur; | ||
220 | |||
221 | getrlimit(RLIMIT_MEMLOCK, &cur); | ||
222 | if (cur.rlim_cur >= alloc_size) { | ||
223 | printf("alloc_size[%d] >%u rlimit, violates test condition\n", | ||
224 | alloc_size, (unsigned int)cur.rlim_cur); | ||
225 | return -1; | ||
226 | } | ||
227 | |||
228 | old_locked_vm_size = get_proc_locked_vm_size(); | ||
229 | srand(time(NULL)); | ||
230 | for (i = 0; i < TEST_LOOP; i++) { | ||
231 | int is_mlock = !!(rand() % 2); | ||
232 | int lock_size = (rand() % (alloc_size - cur.rlim_cur)) | ||
233 | + cur.rlim_cur; | ||
234 | int start_offset = rand() % (alloc_size - lock_size); | ||
235 | |||
236 | if (is_mlock) | ||
237 | ret = mlock(p + start_offset, lock_size); | ||
238 | else | ||
239 | ret = mlock2_(p + start_offset, lock_size, | ||
240 | MLOCK_ONFAULT); | ||
241 | if (ret == 0) { | ||
242 | printf("%s() succeeds? on %p(%d) mlock%p(%d)\n", | ||
243 | is_mlock ? "mlock" : "mlock2", | ||
244 | p, alloc_size, | ||
245 | p + start_offset, lock_size); | ||
246 | return -1; | ||
247 | } | ||
248 | } | ||
249 | |||
250 | locked_vm_size = get_proc_locked_vm_size(); | ||
251 | if (locked_vm_size != old_locked_vm_size) { | ||
252 | printf("tests leads to new mlocked page: old[%d], new[%d]\n", | ||
253 | old_locked_vm_size, | ||
254 | locked_vm_size); | ||
255 | return -1; | ||
256 | } | ||
257 | |||
258 | return 0; | ||
259 | } | ||
260 | |||
261 | int main(int argc, char **argv) | ||
262 | { | ||
263 | char *p = NULL; | ||
264 | int ret = 0; | ||
265 | |||
266 | if (set_cap_limits(MLOCK_RLIMIT_SIZE)) | ||
267 | return -1; | ||
268 | |||
269 | p = malloc(MLOCK_WITHIN_LIMIT_SIZE); | ||
270 | if (p == NULL) { | ||
271 | perror("malloc() failure\n"); | ||
272 | return -1; | ||
273 | } | ||
274 | ret = test_mlock_within_limit(p, MLOCK_WITHIN_LIMIT_SIZE); | ||
275 | if (ret) | ||
276 | return ret; | ||
277 | munlock(p, MLOCK_WITHIN_LIMIT_SIZE); | ||
278 | free(p); | ||
279 | |||
280 | |||
281 | p = malloc(MLOCK_OUTOF_LIMIT_SIZE); | ||
282 | if (p == NULL) { | ||
283 | perror("malloc() failure\n"); | ||
284 | return -1; | ||
285 | } | ||
286 | ret = test_mlock_outof_limit(p, MLOCK_OUTOF_LIMIT_SIZE); | ||
287 | if (ret) | ||
288 | return ret; | ||
289 | munlock(p, MLOCK_OUTOF_LIMIT_SIZE); | ||
290 | free(p); | ||
291 | |||
292 | return 0; | ||
293 | } | ||