1#!/bin/sh
2# SPDX-License-Identifier: GPL-2.0
3
4# Kselftest framework requirement - SKIP code is 4.
5ksft_skip=4
6
7set -e
8
9if [[ $(id -u) -ne 0 ]]; then
10  echo "This test must be run as root. Skipping..."
11  exit $ksft_skip
12fi
13
14fault_limit_file=limit_in_bytes
15reservation_limit_file=rsvd.limit_in_bytes
16fault_usage_file=usage_in_bytes
17reservation_usage_file=rsvd.usage_in_bytes
18
19if [[ "$1" == "-cgroup-v2" ]]; then
20  cgroup2=1
21  fault_limit_file=max
22  reservation_limit_file=rsvd.max
23  fault_usage_file=current
24  reservation_usage_file=rsvd.current
25fi
26
27cgroup_path=/dev/cgroup/memory
28if [[ ! -e $cgroup_path ]]; then
29  mkdir -p $cgroup_path
30  if [[ $cgroup2 ]]; then
31    mount -t cgroup2 none $cgroup_path
32  else
33    mount -t cgroup memory,hugetlb $cgroup_path
34  fi
35fi
36
37if [[ $cgroup2 ]]; then
38  echo "+hugetlb" >/dev/cgroup/memory/cgroup.subtree_control
39fi
40
41function cleanup() {
42  if [[ $cgroup2 ]]; then
43    echo $$ >$cgroup_path/cgroup.procs
44  else
45    echo $$ >$cgroup_path/tasks
46  fi
47
48  if [[ -e /mnt/huge ]]; then
49    rm -rf /mnt/huge/*
50    umount /mnt/huge || echo error
51    rmdir /mnt/huge
52  fi
53  if [[ -e $cgroup_path/hugetlb_cgroup_test ]]; then
54    rmdir $cgroup_path/hugetlb_cgroup_test
55  fi
56  if [[ -e $cgroup_path/hugetlb_cgroup_test1 ]]; then
57    rmdir $cgroup_path/hugetlb_cgroup_test1
58  fi
59  if [[ -e $cgroup_path/hugetlb_cgroup_test2 ]]; then
60    rmdir $cgroup_path/hugetlb_cgroup_test2
61  fi
62  echo 0 >/proc/sys/vm/nr_hugepages
63  echo CLEANUP DONE
64}
65
66function expect_equal() {
67  local expected="$1"
68  local actual="$2"
69  local error="$3"
70
71  if [[ "$expected" != "$actual" ]]; then
72    echo "expected ($expected) != actual ($actual): $3"
73    cleanup
74    exit 1
75  fi
76}
77
78function get_machine_hugepage_size() {
79  hpz=$(grep -i hugepagesize /proc/meminfo)
80  kb=${hpz:14:-3}
81  mb=$(($kb / 1024))
82  echo $mb
83}
84
85MB=$(get_machine_hugepage_size)
86
87function setup_cgroup() {
88  local name="$1"
89  local cgroup_limit="$2"
90  local reservation_limit="$3"
91
92  mkdir $cgroup_path/$name
93
94  echo writing cgroup limit: "$cgroup_limit"
95  echo "$cgroup_limit" >$cgroup_path/$name/hugetlb.${MB}MB.$fault_limit_file
96
97  echo writing reseravation limit: "$reservation_limit"
98  echo "$reservation_limit" > \
99    $cgroup_path/$name/hugetlb.${MB}MB.$reservation_limit_file
100
101  if [ -e "$cgroup_path/$name/cpuset.cpus" ]; then
102    echo 0 >$cgroup_path/$name/cpuset.cpus
103  fi
104  if [ -e "$cgroup_path/$name/cpuset.mems" ]; then
105    echo 0 >$cgroup_path/$name/cpuset.mems
106  fi
107}
108
109function wait_for_hugetlb_memory_to_get_depleted() {
110  local cgroup="$1"
111  local path="/dev/cgroup/memory/$cgroup/hugetlb.${MB}MB.$reservation_usage_file"
112  # Wait for hugetlbfs memory to get depleted.
113  while [ $(cat $path) != 0 ]; do
114    echo Waiting for hugetlb memory to get depleted.
115    cat $path
116    sleep 0.5
117  done
118}
119
120function wait_for_hugetlb_memory_to_get_reserved() {
121  local cgroup="$1"
122  local size="$2"
123
124  local path="/dev/cgroup/memory/$cgroup/hugetlb.${MB}MB.$reservation_usage_file"
125  # Wait for hugetlbfs memory to get written.
126  while [ $(cat $path) != $size ]; do
127    echo Waiting for hugetlb memory reservation to reach size $size.
128    cat $path
129    sleep 0.5
130  done
131}
132
133function wait_for_hugetlb_memory_to_get_written() {
134  local cgroup="$1"
135  local size="$2"
136
137  local path="/dev/cgroup/memory/$cgroup/hugetlb.${MB}MB.$fault_usage_file"
138  # Wait for hugetlbfs memory to get written.
139  while [ $(cat $path) != $size ]; do
140    echo Waiting for hugetlb memory to reach size $size.
141    cat $path
142    sleep 0.5
143  done
144}
145
146function write_hugetlbfs_and_get_usage() {
147  local cgroup="$1"
148  local size="$2"
149  local populate="$3"
150  local write="$4"
151  local path="$5"
152  local method="$6"
153  local private="$7"
154  local expect_failure="$8"
155  local reserve="$9"
156
157  # Function return values.
158  reservation_failed=0
159  oom_killed=0
160  hugetlb_difference=0
161  reserved_difference=0
162
163  local hugetlb_usage=$cgroup_path/$cgroup/hugetlb.${MB}MB.$fault_usage_file
164  local reserved_usage=$cgroup_path/$cgroup/hugetlb.${MB}MB.$reservation_usage_file
165
166  local hugetlb_before=$(cat $hugetlb_usage)
167  local reserved_before=$(cat $reserved_usage)
168
169  echo
170  echo Starting:
171  echo hugetlb_usage="$hugetlb_before"
172  echo reserved_usage="$reserved_before"
173  echo expect_failure is "$expect_failure"
174
175  output=$(mktemp)
176  set +e
177  if [[ "$method" == "1" ]] || [[ "$method" == 2 ]] ||
178    [[ "$private" == "-r" ]] && [[ "$expect_failure" != 1 ]]; then
179
180    bash write_hugetlb_memory.sh "$size" "$populate" "$write" \
181      "$cgroup" "$path" "$method" "$private" "-l" "$reserve" 2>&1 | tee $output &
182
183    local write_result=$?
184    local write_pid=$!
185
186    until grep -q -i "DONE" $output; do
187      echo waiting for DONE signal.
188      if ! ps $write_pid > /dev/null
189      then
190        echo "FAIL: The write died"
191        cleanup
192        exit 1
193      fi
194      sleep 0.5
195    done
196
197    echo ================= write_hugetlb_memory.sh output is:
198    cat $output
199    echo ================= end output.
200
201    if [[ "$populate" == "-o" ]] || [[ "$write" == "-w" ]]; then
202      wait_for_hugetlb_memory_to_get_written "$cgroup" "$size"
203    elif [[ "$reserve" != "-n" ]]; then
204      wait_for_hugetlb_memory_to_get_reserved "$cgroup" "$size"
205    else
206      # This case doesn't produce visible effects, but we still have
207      # to wait for the async process to start and execute...
208      sleep 0.5
209    fi
210
211    echo write_result is $write_result
212  else
213    bash write_hugetlb_memory.sh "$size" "$populate" "$write" \
214      "$cgroup" "$path" "$method" "$private" "$reserve"
215    local write_result=$?
216
217    if [[ "$reserve" != "-n" ]]; then
218      wait_for_hugetlb_memory_to_get_reserved "$cgroup" "$size"
219    fi
220  fi
221  set -e
222
223  if [[ "$write_result" == 1 ]]; then
224    reservation_failed=1
225  fi
226
227  # On linus/master, the above process gets SIGBUS'd on oomkill, with
228  # return code 135. On earlier kernels, it gets actual oomkill, with return
229  # code 137, so just check for both conditions in case we're testing
230  # against an earlier kernel.
231  if [[ "$write_result" == 135 ]] || [[ "$write_result" == 137 ]]; then
232    oom_killed=1
233  fi
234
235  local hugetlb_after=$(cat $hugetlb_usage)
236  local reserved_after=$(cat $reserved_usage)
237
238  echo After write:
239  echo hugetlb_usage="$hugetlb_after"
240  echo reserved_usage="$reserved_after"
241
242  hugetlb_difference=$(($hugetlb_after - $hugetlb_before))
243  reserved_difference=$(($reserved_after - $reserved_before))
244}
245
246function cleanup_hugetlb_memory() {
247  set +e
248  local cgroup="$1"
249  if [[ "$(pgrep -f write_to_hugetlbfs)" != "" ]]; then
250    echo killing write_to_hugetlbfs
251    killall -2 write_to_hugetlbfs
252    wait_for_hugetlb_memory_to_get_depleted $cgroup
253  fi
254  set -e
255
256  if [[ -e /mnt/huge ]]; then
257    rm -rf /mnt/huge/*
258    umount /mnt/huge
259    rmdir /mnt/huge
260  fi
261}
262
263function run_test() {
264  local size=$(($1 * ${MB} * 1024 * 1024))
265  local populate="$2"
266  local write="$3"
267  local cgroup_limit=$(($4 * ${MB} * 1024 * 1024))
268  local reservation_limit=$(($5 * ${MB} * 1024 * 1024))
269  local nr_hugepages="$6"
270  local method="$7"
271  local private="$8"
272  local expect_failure="$9"
273  local reserve="${10}"
274
275  # Function return values.
276  hugetlb_difference=0
277  reserved_difference=0
278  reservation_failed=0
279  oom_killed=0
280
281  echo nr hugepages = "$nr_hugepages"
282  echo "$nr_hugepages" >/proc/sys/vm/nr_hugepages
283
284  setup_cgroup "hugetlb_cgroup_test" "$cgroup_limit" "$reservation_limit"
285
286  mkdir -p /mnt/huge
287  mount -t hugetlbfs -o pagesize=${MB}M,size=256M none /mnt/huge
288
289  write_hugetlbfs_and_get_usage "hugetlb_cgroup_test" "$size" "$populate" \
290    "$write" "/mnt/huge/test" "$method" "$private" "$expect_failure" \
291    "$reserve"
292
293  cleanup_hugetlb_memory "hugetlb_cgroup_test"
294
295  local final_hugetlb=$(cat $cgroup_path/hugetlb_cgroup_test/hugetlb.${MB}MB.$fault_usage_file)
296  local final_reservation=$(cat $cgroup_path/hugetlb_cgroup_test/hugetlb.${MB}MB.$reservation_usage_file)
297
298  echo $hugetlb_difference
299  echo $reserved_difference
300  expect_equal "0" "$final_hugetlb" "final hugetlb is not zero"
301  expect_equal "0" "$final_reservation" "final reservation is not zero"
302}
303
304function run_multiple_cgroup_test() {
305  local size1="$1"
306  local populate1="$2"
307  local write1="$3"
308  local cgroup_limit1="$4"
309  local reservation_limit1="$5"
310
311  local size2="$6"
312  local populate2="$7"
313  local write2="$8"
314  local cgroup_limit2="$9"
315  local reservation_limit2="${10}"
316
317  local nr_hugepages="${11}"
318  local method="${12}"
319  local private="${13}"
320  local expect_failure="${14}"
321  local reserve="${15}"
322
323  # Function return values.
324  hugetlb_difference1=0
325  reserved_difference1=0
326  reservation_failed1=0
327  oom_killed1=0
328
329  hugetlb_difference2=0
330  reserved_difference2=0
331  reservation_failed2=0
332  oom_killed2=0
333
334  echo nr hugepages = "$nr_hugepages"
335  echo "$nr_hugepages" >/proc/sys/vm/nr_hugepages
336
337  setup_cgroup "hugetlb_cgroup_test1" "$cgroup_limit1" "$reservation_limit1"
338  setup_cgroup "hugetlb_cgroup_test2" "$cgroup_limit2" "$reservation_limit2"
339
340  mkdir -p /mnt/huge
341  mount -t hugetlbfs -o pagesize=${MB}M,size=256M none /mnt/huge
342
343  write_hugetlbfs_and_get_usage "hugetlb_cgroup_test1" "$size1" \
344    "$populate1" "$write1" "/mnt/huge/test1" "$method" "$private" \
345    "$expect_failure" "$reserve"
346
347  hugetlb_difference1=$hugetlb_difference
348  reserved_difference1=$reserved_difference
349  reservation_failed1=$reservation_failed
350  oom_killed1=$oom_killed
351
352  local cgroup1_hugetlb_usage=$cgroup_path/hugetlb_cgroup_test1/hugetlb.${MB}MB.$fault_usage_file
353  local cgroup1_reservation_usage=$cgroup_path/hugetlb_cgroup_test1/hugetlb.${MB}MB.$reservation_usage_file
354  local cgroup2_hugetlb_usage=$cgroup_path/hugetlb_cgroup_test2/hugetlb.${MB}MB.$fault_usage_file
355  local cgroup2_reservation_usage=$cgroup_path/hugetlb_cgroup_test2/hugetlb.${MB}MB.$reservation_usage_file
356
357  local usage_before_second_write=$(cat $cgroup1_hugetlb_usage)
358  local reservation_usage_before_second_write=$(cat $cgroup1_reservation_usage)
359
360  write_hugetlbfs_and_get_usage "hugetlb_cgroup_test2" "$size2" \
361    "$populate2" "$write2" "/mnt/huge/test2" "$method" "$private" \
362    "$expect_failure" "$reserve"
363
364  hugetlb_difference2=$hugetlb_difference
365  reserved_difference2=$reserved_difference
366  reservation_failed2=$reservation_failed
367  oom_killed2=$oom_killed
368
369  expect_equal "$usage_before_second_write" \
370    "$(cat $cgroup1_hugetlb_usage)" "Usage changed."
371  expect_equal "$reservation_usage_before_second_write" \
372    "$(cat $cgroup1_reservation_usage)" "Reservation usage changed."
373
374  cleanup_hugetlb_memory
375
376  local final_hugetlb=$(cat $cgroup1_hugetlb_usage)
377  local final_reservation=$(cat $cgroup1_reservation_usage)
378
379  expect_equal "0" "$final_hugetlb" \
380    "hugetlbt_cgroup_test1 final hugetlb is not zero"
381  expect_equal "0" "$final_reservation" \
382    "hugetlbt_cgroup_test1 final reservation is not zero"
383
384  local final_hugetlb=$(cat $cgroup2_hugetlb_usage)
385  local final_reservation=$(cat $cgroup2_reservation_usage)
386
387  expect_equal "0" "$final_hugetlb" \
388    "hugetlb_cgroup_test2 final hugetlb is not zero"
389  expect_equal "0" "$final_reservation" \
390    "hugetlb_cgroup_test2 final reservation is not zero"
391}
392
393cleanup
394
395for populate in "" "-o"; do
396  for method in 0 1 2; do
397    for private in "" "-r"; do
398      for reserve in "" "-n"; do
399
400        # Skip mmap(MAP_HUGETLB | MAP_SHARED). Doesn't seem to be supported.
401        if [[ "$method" == 1 ]] && [[ "$private" == "" ]]; then
402          continue
403        fi
404
405        # Skip populated shmem tests. Doesn't seem to be supported.
406        if [[ "$method" == 2"" ]] && [[ "$populate" == "-o" ]]; then
407          continue
408        fi
409
410        if [[ "$method" == 2"" ]] && [[ "$reserve" == "-n" ]]; then
411          continue
412        fi
413
414        cleanup
415        echo
416        echo
417        echo
418        echo Test normal case.
419        echo private=$private, populate=$populate, method=$method, reserve=$reserve
420        run_test 5 "$populate" "" 10 10 10 "$method" "$private" "0" "$reserve"
421
422        echo Memory charged to hugtlb=$hugetlb_difference
423        echo Memory charged to reservation=$reserved_difference
424
425        if [[ "$populate" == "-o" ]]; then
426          expect_equal "$((5 * $MB * 1024 * 1024))" "$hugetlb_difference" \
427            "Reserved memory charged to hugetlb cgroup."
428        else
429          expect_equal "0" "$hugetlb_difference" \
430            "Reserved memory charged to hugetlb cgroup."
431        fi
432
433        if [[ "$reserve" != "-n" ]] || [[ "$populate" == "-o" ]]; then
434          expect_equal "$((5 * $MB * 1024 * 1024))" "$reserved_difference" \
435            "Reserved memory not charged to reservation usage."
436        else
437          expect_equal "0" "$reserved_difference" \
438            "Reserved memory not charged to reservation usage."
439        fi
440
441        echo 'PASS'
442
443        cleanup
444        echo
445        echo
446        echo
447        echo Test normal case with write.
448        echo private=$private, populate=$populate, method=$method, reserve=$reserve
449        run_test 5 "$populate" '-w' 5 5 10 "$method" "$private" "0" "$reserve"
450
451        echo Memory charged to hugtlb=$hugetlb_difference
452        echo Memory charged to reservation=$reserved_difference
453
454        expect_equal "$((5 * $MB * 1024 * 1024))" "$hugetlb_difference" \
455          "Reserved memory charged to hugetlb cgroup."
456
457        expect_equal "$((5 * $MB * 1024 * 1024))" "$reserved_difference" \
458          "Reserved memory not charged to reservation usage."
459
460        echo 'PASS'
461
462        cleanup
463        continue
464        echo
465        echo
466        echo
467        echo Test more than reservation case.
468        echo private=$private, populate=$populate, method=$method, reserve=$reserve
469
470        if [ "$reserve" != "-n" ]; then
471          run_test "5" "$populate" '' "10" "2" "10" "$method" "$private" "1" \
472            "$reserve"
473
474          expect_equal "1" "$reservation_failed" "Reservation succeeded."
475        fi
476
477        echo 'PASS'
478
479        cleanup
480
481        echo
482        echo
483        echo
484        echo Test more than cgroup limit case.
485        echo private=$private, populate=$populate, method=$method, reserve=$reserve
486
487        # Not sure if shm memory can be cleaned up when the process gets sigbus'd.
488        if [[ "$method" != 2 ]]; then
489          run_test 5 "$populate" "-w" 2 10 10 "$method" "$private" "1" "$reserve"
490
491          expect_equal "1" "$oom_killed" "Not oom killed."
492        fi
493        echo 'PASS'
494
495        cleanup
496
497        echo
498        echo
499        echo
500        echo Test normal case, multiple cgroups.
501        echo private=$private, populate=$populate, method=$method, reserve=$reserve
502        run_multiple_cgroup_test "3" "$populate" "" "10" "10" "5" \
503          "$populate" "" "10" "10" "10" \
504          "$method" "$private" "0" "$reserve"
505
506        echo Memory charged to hugtlb1=$hugetlb_difference1
507        echo Memory charged to reservation1=$reserved_difference1
508        echo Memory charged to hugtlb2=$hugetlb_difference2
509        echo Memory charged to reservation2=$reserved_difference2
510
511        if [[ "$reserve" != "-n" ]] || [[ "$populate" == "-o" ]]; then
512          expect_equal "3" "$reserved_difference1" \
513            "Incorrect reservations charged to cgroup 1."
514
515          expect_equal "5" "$reserved_difference2" \
516            "Incorrect reservation charged to cgroup 2."
517
518        else
519          expect_equal "0" "$reserved_difference1" \
520            "Incorrect reservations charged to cgroup 1."
521
522          expect_equal "0" "$reserved_difference2" \
523            "Incorrect reservation charged to cgroup 2."
524        fi
525
526        if [[ "$populate" == "-o" ]]; then
527          expect_equal "3" "$hugetlb_difference1" \
528            "Incorrect hugetlb charged to cgroup 1."
529
530          expect_equal "5" "$hugetlb_difference2" \
531            "Incorrect hugetlb charged to cgroup 2."
532
533        else
534          expect_equal "0" "$hugetlb_difference1" \
535            "Incorrect hugetlb charged to cgroup 1."
536
537          expect_equal "0" "$hugetlb_difference2" \
538            "Incorrect hugetlb charged to cgroup 2."
539        fi
540        echo 'PASS'
541
542        cleanup
543        echo
544        echo
545        echo
546        echo Test normal case with write, multiple cgroups.
547        echo private=$private, populate=$populate, method=$method, reserve=$reserve
548        run_multiple_cgroup_test "3" "$populate" "-w" "10" "10" "5" \
549          "$populate" "-w" "10" "10" "10" \
550          "$method" "$private" "0" "$reserve"
551
552        echo Memory charged to hugtlb1=$hugetlb_difference1
553        echo Memory charged to reservation1=$reserved_difference1
554        echo Memory charged to hugtlb2=$hugetlb_difference2
555        echo Memory charged to reservation2=$reserved_difference2
556
557        expect_equal "3" "$hugetlb_difference1" \
558          "Incorrect hugetlb charged to cgroup 1."
559
560        expect_equal "3" "$reserved_difference1" \
561          "Incorrect reservation charged to cgroup 1."
562
563        expect_equal "5" "$hugetlb_difference2" \
564          "Incorrect hugetlb charged to cgroup 2."
565
566        expect_equal "5" "$reserved_difference2" \
567          "Incorrected reservation charged to cgroup 2."
568        echo 'PASS'
569
570        cleanup
571
572      done # reserve
573    done   # private
574  done     # populate
575done       # method
576
577umount $cgroup_path
578rmdir $cgroup_path
579