xref: /spdk/test/scheduler/cgroups.sh (revision c680e3a05b1a903c18bf3f75b732765607126f45)
1#  SPDX-License-Identifier: BSD-3-Clause
2#  Copyright (C) 2021 Intel Corporation.
3#  All rights reserved.
4
5check_cgroup() {
6	# Try to work with both, cgroup-v1 and cgroup-v2. Verify which version is
7	# in use by looking up interfaces common for either of the versions.
8	if [[ -e $sysfs_cgroup/cgroup.controllers ]]; then
9		# cgroup2 is mounted, check if cpuset controller is available
10		[[ $(< "$sysfs_cgroup/cgroup.controllers") == *cpuset* ]] && echo 2
11	elif [[ -e $sysfs_cgroup/cpuset/tasks ]]; then
12		# cgroup's cpuset subsystem is mounted
13		echo 1
14	fi || return 1
15}
16
17init_cpuset_cgroup() {
18	local cgroup pid
19	local -A cgroups=()
20
21	# For cgroup-v2 we need to prepare cpuset subsystem on our own
22	if ((cgroup_version == 2)); then
23		set_cgroup_attr / cgroup.subtree_control "+cpuset"
24		create_cgroup /cpuset
25		set_cgroup_attr /cpuset cgroup.subtree_control "+cpuset"
26		# On distros which use cgroup-v2 under systemd, each process is
27		# maintained under separate, pre-configured subtree. With the rule of
28		# "internal processes are not permitted" this means that we won't find
29		# ourselves under subsystem's root, rather on the bottom of the cgroup
30		# maintaining user's session. To recreate the simple /cpuset setup from
31		# v1, move all the threads from all the existing cgroups to the top
32		# cgroup / and then migrate it to the /cpuset we created above.
33		for pid in /proc/+([0-9]); do
34			cgroup=$(get_cgroup "${pid##*/}") || continue
35			[[ $cgroup != / ]] || continue
36			cgroups["$cgroup"]=$cgroup
37		done 2> /dev/null
38		for cgroup in "${!cgroups[@]}"; do
39			move_cgroup_procs "$cgroup" /
40		done
41		# Now, move all the threads to the cpuset
42		move_cgroup_procs / /cpuset
43	elif ((cgroup_version == 1)); then
44		set_cgroup_attr /cpuset cgroup.procs "$$"
45	fi
46}
47
48is_cgroup_threaded() {
49	[[ -e $sysfs_cgroup/$1/cgroup.type ]] || return 1
50	[[ $(< "$sysfs_cgroup/$1/cgroup.type") == threaded ]]
51}
52
53move_cgroup_procs() {
54	local old_cgroup=$1
55	local new_cgroup=$2
56	local proc procs old_proc_interface new_proc_interface
57
58	# If target cgroups don't exist then there's nothing to do.
59	[[ -e $sysfs_cgroup/$old_cgroup ]] || return 0
60	[[ -e $sysfs_cgroup/$new_cgroup ]] || return 0
61
62	old_proc_interface=cgroup.procs
63	new_proc_interface=cgroup.procs
64	if ((cgroup_version == 2)); then
65		if is_cgroup_threaded "$new_cgroup"; then
66			new_proc_interface=cgroup.threads
67		fi
68		if is_cgroup_threaded "$old_cgroup"; then
69			old_proc_interface=cgroup.threads
70		fi
71	fi
72
73	fold_list_onto_array procs $(< "$sysfs_cgroup/$old_cgroup/$old_proc_interface")
74
75	for proc in "${!procs[@]}"; do
76		# We can't move every kernel thread around and every process can
77		# exit at any point so ignore any failures upon writing the
78		# processes out. FIXME: Check PF_KTHREAD instead?
79		[[ -n $(readlink -f "/proc/$proc/exe") ]] || continue
80		echo "$proc" > "$sysfs_cgroup/$new_cgroup/$new_proc_interface" 2> /dev/null || :
81	done
82}
83
84set_cgroup_attr() {
85	local cgroup=$1
86	local attr=$2
87	local val=$3
88
89	[[ -e $sysfs_cgroup/$cgroup/$attr ]] || return 1
90
91	if [[ -n $val ]]; then
92		echo "$val" > "$sysfs_cgroup/$cgroup/$attr"
93	fi
94}
95
96create_cgroup() {
97	[[ ! -e $sysfs_cgroup/$1 ]] || return 0
98	mkdir "$sysfs_cgroup/$1"
99	if ((cgroup_version == 2)); then
100		echo "threaded" > "$sysfs_cgroup/$1/cgroup.type"
101	fi
102}
103
104remove_cgroup() {
105	local root_cgroup
106	root_cgroup=$(dirname "$1")
107
108	[[ -e $sysfs_cgroup/$1 ]] || return 0
109	move_cgroup_procs "$1" "$root_cgroup"
110	rmdir "$sysfs_cgroup/$1"
111}
112
113exec_in_cgroup() {
114	# Run this function as a background job - the reason why it remains {} instead
115	# of being declared as a subshell is to avoid having an extra bash fork around
116	# - note the exec call.
117
118	local cgroup=$1
119	local proc_interface=cgroup.procs
120
121	shift || return 1
122
123	if ((cgroup_version == 2)) && is_cgroup_threaded "$cgroup"; then
124		proc_interface=cgroup.threads
125	fi
126	set_cgroup_attr "$cgroup" "$proc_interface" "$BASHPID"
127	exec "$@"
128}
129
130kill_in_cgroup() {
131	local cgroup=$1
132	local pid=$2
133	local proc_interface=cgroup.procs
134	local cgroup_pids
135
136	if ((cgroup_version == 2)) && is_cgroup_threaded "$cgroup"; then
137		proc_interface=cgroup.threads
138	fi
139
140	fold_list_onto_array \
141		cgroup_pids \
142		$(< "$sysfs_cgroup/$cgroup/$proc_interface")
143
144	if [[ -n $pid ]]; then
145		if [[ -n ${cgroup_pids[pid]} ]]; then
146			kill "$pid"
147		fi
148	elif ((${#cgroup_pids[@]} > 0)); then
149		kill "${cgroup_pids[@]}"
150	fi
151}
152
153remove_cpuset_cgroup() {
154	if ((cgroup_version == 2)); then
155		remove_cgroup /cpuset
156	fi
157}
158
159get_cgroup() {
160	local pid=${1:-self} cgroup
161
162	[[ -e /proc/$pid/cgroup ]] || return 1
163	cgroup=$(< "/proc/$pid/cgroup")
164	echo "${cgroup##*:}"
165}
166
167get_cgroup_path() {
168	local cgroup
169
170	cgroup=$(get_cgroup "$1") || return 1
171	echo "$sysfs_cgroup$cgroup"
172}
173
174_set_cgroup_attr_top_bottom() {
175	local cgroup_path=$1 attr=$2 val=$3
176
177	if [[ -e ${cgroup_path%/*}/$attr ]]; then
178		_set_cgroup_attr_top_bottom "${cgroup_path%/*}" "$attr" "$val"
179	fi
180
181	if [[ -e $cgroup_path/$attr ]]; then
182		echo "$val" > "$cgroup_path/$attr"
183	fi
184}
185
186set_cgroup_attr_top_bottom() {
187	_set_cgroup_attr_top_bottom "$(get_cgroup_path "$1")" "$2" "$3"
188}
189
190declare -r sysfs_cgroup=/sys/fs/cgroup
191cgroup_version=$(check_cgroup)
192