1// SPDX-License-Identifier: GPL-2.0
2/*
3 * This program tests for hugepage leaks after DIO writes to a file using a
4 * hugepage as the user buffer. During DIO, the user buffer is pinned and
5 * should be properly unpinned upon completion. This patch verifies that the
6 * kernel correctly unpins the buffer at DIO completion for both aligned and
7 * unaligned user buffer offsets (w.r.t page boundary), ensuring the hugepage
8 * is freed upon unmapping.
9 */
10
11#define _GNU_SOURCE
12#include <stdio.h>
13#include <sys/stat.h>
14#include <stdlib.h>
15#include <fcntl.h>
16#include <stdint.h>
17#include <unistd.h>
18#include <string.h>
19#include <sys/mman.h>
20#include "vm_util.h"
21#include "../kselftest.h"
22
23void run_dio_using_hugetlb(unsigned int start_off, unsigned int end_off)
24{
25 int fd;
26 char *buffer = NULL;
27 char *orig_buffer = NULL;
28 size_t h_pagesize = 0;
29 size_t writesize;
30 int free_hpage_b = 0;
31 int free_hpage_a = 0;
32 const int mmap_flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB;
33 const int mmap_prot = PROT_READ | PROT_WRITE;
34
35 writesize = end_off - start_off;
36
37 /* Get the default huge page size */
38 h_pagesize = default_huge_page_size();
39 if (!h_pagesize)
40 ksft_exit_fail_msg(msg: "Unable to determine huge page size\n");
41
42 /* Open the file to DIO */
43 fd = open("/tmp", O_TMPFILE | O_RDWR | O_DIRECT, 0664);
44 if (fd < 0)
45 ksft_exit_fail_perror(msg: "Error opening file\n");
46
47 /* Get the free huge pages before allocation */
48 free_hpage_b = get_free_hugepages();
49 if (free_hpage_b == 0) {
50 close(fd);
51 ksft_exit_skip(msg: "No free hugepage, exiting!\n");
52 }
53
54 /* Allocate a hugetlb page */
55 orig_buffer = mmap(NULL, h_pagesize, mmap_prot, mmap_flags, -1, 0);
56 if (orig_buffer == MAP_FAILED) {
57 close(fd);
58 ksft_exit_fail_perror(msg: "Error mapping memory\n");
59 }
60 buffer = orig_buffer;
61 buffer += start_off;
62
63 memset(buffer, 'A', writesize);
64
65 /* Write the buffer to the file */
66 if (write(fd, buffer, writesize) != (writesize)) {
67 munmap(orig_buffer, h_pagesize);
68 close(fd);
69 ksft_exit_fail_perror(msg: "Error writing to file\n");
70 }
71
72 /* unmap the huge page */
73 munmap(orig_buffer, h_pagesize);
74 close(fd);
75
76 /* Get the free huge pages after unmap*/
77 free_hpage_a = get_free_hugepages();
78
79 ksft_print_msg(msg: "No. Free pages before allocation : %d\n", free_hpage_b);
80 ksft_print_msg(msg: "No. Free pages after munmap : %d\n", free_hpage_a);
81
82 /*
83 * If the no. of free hugepages before allocation and after unmap does
84 * not match - that means there could still be a page which is pinned.
85 */
86 ksft_test_result(free_hpage_a == free_hpage_b,
87 "free huge pages from %u-%u\n", start_off, end_off);
88}
89
90int main(void)
91{
92 size_t pagesize = 0;
93 int fd;
94
95 ksft_print_header();
96
97 /* Open the file to DIO */
98 fd = open("/tmp", O_TMPFILE | O_RDWR | O_DIRECT, 0664);
99 if (fd < 0)
100 ksft_exit_skip(msg: "Unable to allocate file: %s\n", strerror(errno));
101 close(fd);
102
103 /* Check if huge pages are free */
104 if (!get_free_hugepages())
105 ksft_exit_skip(msg: "No free hugepage, exiting\n");
106
107 ksft_set_plan(plan: 4);
108
109 /* Get base page size */
110 pagesize = psize();
111
112 /* start and end is aligned to pagesize */
113 run_dio_using_hugetlb(start_off: 0, end_off: (pagesize * 3));
114
115 /* start is aligned but end is not aligned */
116 run_dio_using_hugetlb(start_off: 0, end_off: (pagesize * 3) - (pagesize / 2));
117
118 /* start is unaligned and end is aligned */
119 run_dio_using_hugetlb(start_off: pagesize / 2, end_off: (pagesize * 3));
120
121 /* both start and end are unaligned */
122 run_dio_using_hugetlb(start_off: pagesize / 2, end_off: (pagesize * 3) + (pagesize / 2));
123
124 ksft_finished();
125}
126

source code of linux/tools/testing/selftests/mm/hugetlb_dio.c