Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.1.
  1#define _GNU_SOURCE
  2#include <sys/mman.h>
  3#include <stdint.h>
  4#include <unistd.h>
  5#include <string.h>
  6#include <sys/time.h>
  7#include <sys/resource.h>
  8#include <stdbool.h>
  9#include "mlock2.h"
 10
 11struct vm_boundaries {
 12	unsigned long start;
 13	unsigned long end;
 14};
 15
 16static int get_vm_area(unsigned long addr, struct vm_boundaries *area)
 17{
 18	FILE *file;
 19	int ret = 1;
 20	char line[1024] = {0};
 21	char *end_addr;
 22	char *stop;
 23	unsigned long start;
 24	unsigned long end;
 25
 26	if (!area)
 27		return ret;
 28
 29	file = fopen("/proc/self/maps", "r");
 30	if (!file) {
 31		perror("fopen");
 32		return ret;
 33	}
 34
 35	memset(area, 0, sizeof(struct vm_boundaries));
 36
 37	while(fgets(line, 1024, file)) {
 38		end_addr = strchr(line, '-');
 39		if (!end_addr) {
 40			printf("cannot parse /proc/self/maps\n");
 41			goto out;
 42		}
 43		*end_addr = '\0';
 44		end_addr++;
 45		stop = strchr(end_addr, ' ');
 46		if (!stop) {
 47			printf("cannot parse /proc/self/maps\n");
 48			goto out;
 49		}
 50		stop = '\0';
 51
 52		sscanf(line, "%lx", &start);
 53		sscanf(end_addr, "%lx", &end);
 54
 55		if (start <= addr && end > addr) {
 56			area->start = start;
 57			area->end = end;
 58			ret = 0;
 59			goto out;
 60		}
 61	}
 62out:
 63	fclose(file);
 64	return ret;
 65}
 66
 67static uint64_t get_pageflags(unsigned long addr)
 68{
 69	FILE *file;
 70	uint64_t pfn;
 71	unsigned long offset;
 72
 73	file = fopen("/proc/self/pagemap", "r");
 74	if (!file) {
 75		perror("fopen pagemap");
 76		_exit(1);
 77	}
 78
 79	offset = addr / getpagesize() * sizeof(pfn);
 80
 81	if (fseek(file, offset, SEEK_SET)) {
 82		perror("fseek pagemap");
 83		_exit(1);
 84	}
 85
 86	if (fread(&pfn, sizeof(pfn), 1, file) != 1) {
 87		perror("fread pagemap");
 88		_exit(1);
 89	}
 90
 91	fclose(file);
 92	return pfn;
 93}
 94
 95static uint64_t get_kpageflags(unsigned long pfn)
 96{
 97	uint64_t flags;
 98	FILE *file;
 99
100	file = fopen("/proc/kpageflags", "r");
101	if (!file) {
102		perror("fopen kpageflags");
103		_exit(1);
104	}
105
106	if (fseek(file, pfn * sizeof(flags), SEEK_SET)) {
107		perror("fseek kpageflags");
108		_exit(1);
109	}
110
111	if (fread(&flags, sizeof(flags), 1, file) != 1) {
112		perror("fread kpageflags");
113		_exit(1);
114	}
115
116	fclose(file);
117	return flags;
118}
119
120#define VMFLAGS "VmFlags:"
121
122static bool is_vmflag_set(unsigned long addr, const char *vmflag)
123{
124	char *line = NULL;
125	char *flags;
126	size_t size = 0;
127	bool ret = false;
128	FILE *smaps;
129
130	smaps = seek_to_smaps_entry(addr);
131	if (!smaps) {
132		printf("Unable to parse /proc/self/smaps\n");
133		goto out;
134	}
135
136	while (getline(&line, &size, smaps) > 0) {
137		if (!strstr(line, VMFLAGS)) {
138			free(line);
139			line = NULL;
140			size = 0;
141			continue;
142		}
143
144		flags = line + strlen(VMFLAGS);
145		ret = (strstr(flags, vmflag) != NULL);
146		goto out;
147	}
148
149out:
150	free(line);
151	fclose(smaps);
152	return ret;
153}
154
155#define SIZE "Size:"
156#define RSS  "Rss:"
157#define LOCKED "lo"
158
159static bool is_vma_lock_on_fault(unsigned long addr)
160{
161	bool ret = false;
162	bool locked;
163	FILE *smaps = NULL;
164	unsigned long vma_size, vma_rss;
165	char *line = NULL;
166	char *value;
167	size_t size = 0;
168
169	locked = is_vmflag_set(addr, LOCKED);
170	if (!locked)
171		goto out;
172
173	smaps = seek_to_smaps_entry(addr);
174	if (!smaps) {
175		printf("Unable to parse /proc/self/smaps\n");
176		goto out;
177	}
178
179	while (getline(&line, &size, smaps) > 0) {
180		if (!strstr(line, SIZE)) {
181			free(line);
182			line = NULL;
183			size = 0;
184			continue;
185		}
186
187		value = line + strlen(SIZE);
188		if (sscanf(value, "%lu kB", &vma_size) < 1) {
189			printf("Unable to parse smaps entry for Size\n");
190			goto out;
191		}
192		break;
193	}
194
195	while (getline(&line, &size, smaps) > 0) {
196		if (!strstr(line, RSS)) {
197			free(line);
198			line = NULL;
199			size = 0;
200			continue;
201		}
202
203		value = line + strlen(RSS);
204		if (sscanf(value, "%lu kB", &vma_rss) < 1) {
205			printf("Unable to parse smaps entry for Rss\n");
206			goto out;
207		}
208		break;
209	}
210
211	ret = locked && (vma_rss < vma_size);
212out:
213	free(line);
214	if (smaps)
215		fclose(smaps);
216	return ret;
217}
218
219#define PRESENT_BIT     0x8000000000000000ULL
220#define PFN_MASK        0x007FFFFFFFFFFFFFULL
221#define UNEVICTABLE_BIT (1UL << 18)
222
223static int lock_check(char *map)
224{
225	unsigned long page_size = getpagesize();
226	uint64_t page1_flags, page2_flags;
227
228	page1_flags = get_pageflags((unsigned long)map);
229	page2_flags = get_pageflags((unsigned long)map + page_size);
230
231	/* Both pages should be present */
232	if (((page1_flags & PRESENT_BIT) == 0) ||
233	    ((page2_flags & PRESENT_BIT) == 0)) {
234		printf("Failed to make both pages present\n");
235		return 1;
236	}
237
238	page1_flags = get_kpageflags(page1_flags & PFN_MASK);
239	page2_flags = get_kpageflags(page2_flags & PFN_MASK);
240
241	/* Both pages should be unevictable */
242	if (((page1_flags & UNEVICTABLE_BIT) == 0) ||
243	    ((page2_flags & UNEVICTABLE_BIT) == 0)) {
244		printf("Failed to make both pages unevictable\n");
245		return 1;
246	}
247
248	if (!is_vmflag_set((unsigned long)map, LOCKED)) {
249		printf("VMA flag %s is missing on page 1\n", LOCKED);
250		return 1;
251	}
252
253	if (!is_vmflag_set((unsigned long)map + page_size, LOCKED)) {
254		printf("VMA flag %s is missing on page 2\n", LOCKED);
255		return 1;
256	}
257
258	return 0;
259}
260
261static int unlock_lock_check(char *map)
262{
263	unsigned long page_size = getpagesize();
264	uint64_t page1_flags, page2_flags;
265
266	page1_flags = get_pageflags((unsigned long)map);
267	page2_flags = get_pageflags((unsigned long)map + page_size);
268	page1_flags = get_kpageflags(page1_flags & PFN_MASK);
269	page2_flags = get_kpageflags(page2_flags & PFN_MASK);
270
271	if ((page1_flags & UNEVICTABLE_BIT) || (page2_flags & UNEVICTABLE_BIT)) {
272		printf("A page is still marked unevictable after unlock\n");
273		return 1;
274	}
275
276	if (is_vmflag_set((unsigned long)map, LOCKED)) {
277		printf("VMA flag %s is present on page 1 after unlock\n", LOCKED);
278		return 1;
279	}
280
281	if (is_vmflag_set((unsigned long)map + page_size, LOCKED)) {
282		printf("VMA flag %s is present on page 2 after unlock\n", LOCKED);
283		return 1;
284	}
285
286	return 0;
287}
288
289static int test_mlock_lock()
290{
291	char *map;
292	int ret = 1;
293	unsigned long page_size = getpagesize();
294
295	map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
296		   MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
297	if (map == MAP_FAILED) {
298		perror("test_mlock_locked mmap");
299		goto out;
300	}
301
302	if (mlock2_(map, 2 * page_size, 0)) {
303		if (errno == ENOSYS) {
304			printf("Cannot call new mlock family, skipping test\n");
305			_exit(0);
306		}
307		perror("mlock2(0)");
308		goto unmap;
309	}
310
311	if (lock_check(map))
312		goto unmap;
313
314	/* Now unlock and recheck attributes */
315	if (munlock(map, 2 * page_size)) {
316		perror("munlock()");
317		goto unmap;
318	}
319
320	ret = unlock_lock_check(map);
321
322unmap:
323	munmap(map, 2 * page_size);
324out:
325	return ret;
326}
327
328static int onfault_check(char *map)
329{
330	unsigned long page_size = getpagesize();
331	uint64_t page1_flags, page2_flags;
332
333	page1_flags = get_pageflags((unsigned long)map);
334	page2_flags = get_pageflags((unsigned long)map + page_size);
335
336	/* Neither page should be present */
337	if ((page1_flags & PRESENT_BIT) || (page2_flags & PRESENT_BIT)) {
338		printf("Pages were made present by MLOCK_ONFAULT\n");
339		return 1;
340	}
341
342	*map = 'a';
343	page1_flags = get_pageflags((unsigned long)map);
344	page2_flags = get_pageflags((unsigned long)map + page_size);
345
346	/* Only page 1 should be present */
347	if ((page1_flags & PRESENT_BIT) == 0) {
348		printf("Page 1 is not present after fault\n");
349		return 1;
350	} else if (page2_flags & PRESENT_BIT) {
351		printf("Page 2 was made present\n");
352		return 1;
353	}
354
355	page1_flags = get_kpageflags(page1_flags & PFN_MASK);
356
357	/* Page 1 should be unevictable */
358	if ((page1_flags & UNEVICTABLE_BIT) == 0) {
359		printf("Failed to make faulted page unevictable\n");
360		return 1;
361	}
362
363	if (!is_vma_lock_on_fault((unsigned long)map)) {
364		printf("VMA is not marked for lock on fault\n");
365		return 1;
366	}
367
368	if (!is_vma_lock_on_fault((unsigned long)map + page_size)) {
369		printf("VMA is not marked for lock on fault\n");
370		return 1;
371	}
372
373	return 0;
374}
375
376static int unlock_onfault_check(char *map)
377{
378	unsigned long page_size = getpagesize();
379	uint64_t page1_flags;
380
381	page1_flags = get_pageflags((unsigned long)map);
382	page1_flags = get_kpageflags(page1_flags & PFN_MASK);
383
384	if (page1_flags & UNEVICTABLE_BIT) {
385		printf("Page 1 is still marked unevictable after unlock\n");
386		return 1;
387	}
388
389	if (is_vma_lock_on_fault((unsigned long)map) ||
390	    is_vma_lock_on_fault((unsigned long)map + page_size)) {
391		printf("VMA is still lock on fault after unlock\n");
392		return 1;
393	}
394
395	return 0;
396}
397
398static int test_mlock_onfault()
399{
400	char *map;
401	int ret = 1;
402	unsigned long page_size = getpagesize();
403
404	map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
405		   MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
406	if (map == MAP_FAILED) {
407		perror("test_mlock_locked mmap");
408		goto out;
409	}
410
411	if (mlock2_(map, 2 * page_size, MLOCK_ONFAULT)) {
412		if (errno == ENOSYS) {
413			printf("Cannot call new mlock family, skipping test\n");
414			_exit(0);
415		}
416		perror("mlock2(MLOCK_ONFAULT)");
417		goto unmap;
418	}
419
420	if (onfault_check(map))
421		goto unmap;
422
423	/* Now unlock and recheck attributes */
424	if (munlock(map, 2 * page_size)) {
425		if (errno == ENOSYS) {
426			printf("Cannot call new mlock family, skipping test\n");
427			_exit(0);
428		}
429		perror("munlock()");
430		goto unmap;
431	}
432
433	ret = unlock_onfault_check(map);
434unmap:
435	munmap(map, 2 * page_size);
436out:
437	return ret;
438}
439
440static int test_lock_onfault_of_present()
441{
442	char *map;
443	int ret = 1;
444	unsigned long page_size = getpagesize();
445	uint64_t page1_flags, page2_flags;
446
447	map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
448		   MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
449	if (map == MAP_FAILED) {
450		perror("test_mlock_locked mmap");
451		goto out;
452	}
453
454	*map = 'a';
455
456	if (mlock2_(map, 2 * page_size, MLOCK_ONFAULT)) {
457		if (errno == ENOSYS) {
458			printf("Cannot call new mlock family, skipping test\n");
459			_exit(0);
460		}
461		perror("mlock2(MLOCK_ONFAULT)");
462		goto unmap;
463	}
464
465	page1_flags = get_pageflags((unsigned long)map);
466	page2_flags = get_pageflags((unsigned long)map + page_size);
467	page1_flags = get_kpageflags(page1_flags & PFN_MASK);
468	page2_flags = get_kpageflags(page2_flags & PFN_MASK);
469
470	/* Page 1 should be unevictable */
471	if ((page1_flags & UNEVICTABLE_BIT) == 0) {
472		printf("Failed to make present page unevictable\n");
473		goto unmap;
474	}
475
476	if (!is_vma_lock_on_fault((unsigned long)map) ||
477	    !is_vma_lock_on_fault((unsigned long)map + page_size)) {
478		printf("VMA with present pages is not marked lock on fault\n");
479		goto unmap;
480	}
481	ret = 0;
482unmap:
483	munmap(map, 2 * page_size);
484out:
485	return ret;
486}
487
488static int test_munlockall()
489{
490	char *map;
491	int ret = 1;
492	unsigned long page_size = getpagesize();
493
494	map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
495		   MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
496
497	if (map == MAP_FAILED) {
498		perror("test_munlockall mmap");
499		goto out;
500	}
501
502	if (mlockall(MCL_CURRENT)) {
503		perror("mlockall(MCL_CURRENT)");
504		goto out;
505	}
506
507	if (lock_check(map))
508		goto unmap;
509
510	if (munlockall()) {
511		perror("munlockall()");
512		goto unmap;
513	}
514
515	if (unlock_lock_check(map))
516		goto unmap;
517
518	munmap(map, 2 * page_size);
519
520	map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
521		   MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
522
523	if (map == MAP_FAILED) {
524		perror("test_munlockall second mmap");
525		goto out;
526	}
527
528	if (mlockall(MCL_CURRENT | MCL_ONFAULT)) {
529		perror("mlockall(MCL_CURRENT | MCL_ONFAULT)");
530		goto unmap;
531	}
532
533	if (onfault_check(map))
534		goto unmap;
535
536	if (munlockall()) {
537		perror("munlockall()");
538		goto unmap;
539	}
540
541	if (unlock_onfault_check(map))
542		goto unmap;
543
544	if (mlockall(MCL_CURRENT | MCL_FUTURE)) {
545		perror("mlockall(MCL_CURRENT | MCL_FUTURE)");
546		goto out;
547	}
548
549	if (lock_check(map))
550		goto unmap;
551
552	if (munlockall()) {
553		perror("munlockall()");
554		goto unmap;
555	}
556
557	ret = unlock_lock_check(map);
558
559unmap:
560	munmap(map, 2 * page_size);
561out:
562	munlockall();
563	return ret;
564}
565
566static int test_vma_management(bool call_mlock)
567{
568	int ret = 1;
569	void *map;
570	unsigned long page_size = getpagesize();
571	struct vm_boundaries page1;
572	struct vm_boundaries page2;
573	struct vm_boundaries page3;
574
575	map = mmap(NULL, 3 * page_size, PROT_READ | PROT_WRITE,
576		   MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
577	if (map == MAP_FAILED) {
578		perror("mmap()");
579		return ret;
580	}
581
582	if (call_mlock && mlock2_(map, 3 * page_size, MLOCK_ONFAULT)) {
583		if (errno == ENOSYS) {
584			printf("Cannot call new mlock family, skipping test\n");
585			_exit(0);
586		}
587		perror("mlock(ONFAULT)\n");
588		goto out;
589	}
590
591	if (get_vm_area((unsigned long)map, &page1) ||
592	    get_vm_area((unsigned long)map + page_size, &page2) ||
593	    get_vm_area((unsigned long)map + page_size * 2, &page3)) {
594		printf("couldn't find mapping in /proc/self/maps\n");
595		goto out;
596	}
597
598	/*
599	 * Before we unlock a portion, we need to that all three pages are in
600	 * the same VMA.  If they are not we abort this test (Note that this is
601	 * not a failure)
602	 */
603	if (page1.start != page2.start || page2.start != page3.start) {
604		printf("VMAs are not merged to start, aborting test\n");
605		ret = 0;
606		goto out;
607	}
608
609	if (munlock(map + page_size, page_size)) {
610		perror("munlock()");
611		goto out;
612	}
613
614	if (get_vm_area((unsigned long)map, &page1) ||
615	    get_vm_area((unsigned long)map + page_size, &page2) ||
616	    get_vm_area((unsigned long)map + page_size * 2, &page3)) {
617		printf("couldn't find mapping in /proc/self/maps\n");
618		goto out;
619	}
620
621	/* All three VMAs should be different */
622	if (page1.start == page2.start || page2.start == page3.start) {
623		printf("failed to split VMA for munlock\n");
624		goto out;
625	}
626
627	/* Now unlock the first and third page and check the VMAs again */
628	if (munlock(map, page_size * 3)) {
629		perror("munlock()");
630		goto out;
631	}
632
633	if (get_vm_area((unsigned long)map, &page1) ||
634	    get_vm_area((unsigned long)map + page_size, &page2) ||
635	    get_vm_area((unsigned long)map + page_size * 2, &page3)) {
636		printf("couldn't find mapping in /proc/self/maps\n");
637		goto out;
638	}
639
640	/* Now all three VMAs should be the same */
641	if (page1.start != page2.start || page2.start != page3.start) {
642		printf("failed to merge VMAs after munlock\n");
643		goto out;
644	}
645
646	ret = 0;
647out:
648	munmap(map, 3 * page_size);
649	return ret;
650}
651
652static int test_mlockall(int (test_function)(bool call_mlock))
653{
654	int ret = 1;
655
656	if (mlockall(MCL_CURRENT | MCL_ONFAULT | MCL_FUTURE)) {
657		perror("mlockall");
658		return ret;
659	}
660
661	ret = test_function(false);
662	munlockall();
663	return ret;
664}
665
666int main(int argc, char **argv)
667{
668	int ret = 0;
669	ret += test_mlock_lock();
670	ret += test_mlock_onfault();
671	ret += test_munlockall();
672	ret += test_lock_onfault_of_present();
673	ret += test_vma_management(true);
674	ret += test_mlockall(test_vma_management);
675	return ret;
676}