1 /*
2  * chrt.c - manipulate a task's real-time attributes
3  *
4  * 27-Apr-2002: initial version - Robert Love <rml@tech9.net>
5  * 04-May-2011: make it thread-aware - Davidlohr Bueso <dave@gnu.org>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License, version 2, as
9  * published by the Free Software Foundation
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19  *
20  * Copyright (C) 2004 Robert Love
21  */
22 
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <sched.h>
26 #include <unistd.h>
27 #include <getopt.h>
28 #include <errno.h>
29 #include <sys/time.h>
30 #include <sys/resource.h>
31 
32 #include "c.h"
33 #include "nls.h"
34 #include "closestream.h"
35 #include "strutils.h"
36 #include "procutils.h"
37 
38 /* the SCHED_BATCH is supported since Linux 2.6.16
39  *  -- temporary workaround for people with old glibc headers
40  */
41 #if defined (__linux__) && !defined(SCHED_BATCH)
42 # define SCHED_BATCH 3
43 #endif
44 
45 /* the SCHED_IDLE is supported since Linux 2.6.23
46  * commit id 0e6aca43e08a62a48d6770e9a159dbec167bf4c6
47  * -- temporary workaround for people with old glibc headers
48  */
49 #if defined (__linux__) && !defined(SCHED_IDLE)
50 # define SCHED_IDLE 5
51 #endif
52 
53 /* flag by sched_getscheduler() */
54 #if defined(__linux__) && !defined(SCHED_RESET_ON_FORK)
55 # define SCHED_RESET_ON_FORK 0x40000000
56 #endif
57 
58 /* flag by sched_getattr() */
59 #if defined(__linux__) && !defined(SCHED_FLAG_RESET_ON_FORK)
60 # define SCHED_FLAG_RESET_ON_FORK 0x01
61 #endif
62 
63 #if defined (__linux__)
64 # include <sys/syscall.h>
65 #endif
66 
67 /* usable kernel-headers, but old glibc-headers */
68 #if defined (__linux__) && !defined(SYS_sched_setattr) && defined(__NR_sched_setattr)
69 # define SYS_sched_setattr __NR_sched_setattr
70 #endif
71 
72 #if defined (__linux__) && !defined(SYS_sched_getattr) && defined(__NR_sched_getattr)
73 # define SYS_sched_getattr __NR_sched_getattr
74 #endif
75 
76 #if defined (__linux__) && !defined(HAVE_SCHED_SETATTR) && defined(SYS_sched_setattr)
77 # define HAVE_SCHED_SETATTR
78 
79 struct sched_attr {
80 	uint32_t size;
81 	uint32_t sched_policy;
82 	uint64_t sched_flags;
83 
84 	/* SCHED_NORMAL, SCHED_BATCH */
85 	int32_t sched_nice;
86 
87 	/* SCHED_FIFO, SCHED_RR */
88 	uint32_t sched_priority;
89 
90 	/* SCHED_DEADLINE (nsec) */
91 	uint64_t sched_runtime;
92 	uint64_t sched_deadline;
93 	uint64_t sched_period;
94 };
95 
sched_setattr(pid_t pid,const struct sched_attr * attr,unsigned int flags)96 static int sched_setattr(pid_t pid, const struct sched_attr *attr, unsigned int flags)
97 {
98 	return syscall(SYS_sched_setattr, pid, attr, flags);
99 }
100 
sched_getattr(pid_t pid,struct sched_attr * attr,unsigned int size,unsigned int flags)101 static int sched_getattr(pid_t pid, struct sched_attr *attr, unsigned int size, unsigned int flags)
102 {
103 	return syscall(SYS_sched_getattr, pid, attr, size, flags);
104 }
105 #endif
106 
107 /* the SCHED_DEADLINE is supported since Linux 3.14
108  * commit id aab03e05e8f7e26f51dee792beddcb5cca9215a5
109  * -- sched_setattr() is required for this policy!
110  */
111 #if defined (__linux__) && !defined(SCHED_DEADLINE) && defined(HAVE_SCHED_SETATTR)
112 # define SCHED_DEADLINE 6
113 #endif
114 
115 /* control struct */
116 struct chrt_ctl {
117 	pid_t	pid;
118 	int	policy;				/* SCHED_* */
119 	int	priority;
120 
121 	uint64_t runtime;			/* --sched-* options */
122 	uint64_t deadline;
123 	uint64_t period;
124 
125 	unsigned int all_tasks : 1,		/* all threads of the PID */
126 		     reset_on_fork : 1,		/* SCHED_RESET_ON_FORK */
127 		     altered : 1,		/* sched_set**() used */
128 		     verbose : 1;		/* verbose output */
129 };
130 
usage(void)131 static void __attribute__((__noreturn__)) usage(void)
132 {
133 	FILE *out = stdout;
134 
135 	fputs(_("Show or change the real-time scheduling attributes of a process.\n"), out);
136 	fputs(USAGE_SEPARATOR, out);
137 	fputs(_("Set policy:\n"
138 	" chrt [options] <priority> <command> [<arg>...]\n"
139 	" chrt [options] --pid <priority> <pid>\n"), out);
140 	fputs(USAGE_SEPARATOR, out);
141 	fputs(_("Get policy:\n"
142 	" chrt [options] -p <pid>\n"), out);
143 
144 	fputs(USAGE_SEPARATOR, out);
145 	fputs(_("Policy options:\n"), out);
146 	fputs(_(" -b, --batch          set policy to SCHED_BATCH\n"), out);
147 	fputs(_(" -d, --deadline       set policy to SCHED_DEADLINE\n"), out);
148 	fputs(_(" -f, --fifo           set policy to SCHED_FIFO\n"), out);
149 	fputs(_(" -i, --idle           set policy to SCHED_IDLE\n"), out);
150 	fputs(_(" -o, --other          set policy to SCHED_OTHER\n"), out);
151 	fputs(_(" -r, --rr             set policy to SCHED_RR (default)\n"), out);
152 
153 	fputs(USAGE_SEPARATOR, out);
154 	fputs(_("Scheduling options:\n"), out);
155 	fputs(_(" -R, --reset-on-fork       set SCHED_RESET_ON_FORK for FIFO or RR\n"), out);
156 	fputs(_(" -T, --sched-runtime <ns>  runtime parameter for DEADLINE\n"), out);
157 	fputs(_(" -P, --sched-period <ns>   period parameter for DEADLINE\n"), out);
158 	fputs(_(" -D, --sched-deadline <ns> deadline parameter for DEADLINE\n"), out);
159 
160 	fputs(USAGE_SEPARATOR, out);
161 	fputs(_("Other options:\n"), out);
162 	fputs(_(" -a, --all-tasks      operate on all the tasks (threads) for a given pid\n"), out);
163 	fputs(_(" -m, --max            show min and max valid priorities\n"), out);
164 	fputs(_(" -p, --pid            operate on existing given pid\n"), out);
165 	fputs(_(" -v, --verbose        display status information\n"), out);
166 
167 	fputs(USAGE_SEPARATOR, out);
168 	printf(USAGE_HELP_OPTIONS(22));
169 
170 	printf(USAGE_MAN_TAIL("chrt(1)"));
171 	exit(EXIT_SUCCESS);
172 }
173 
get_policy_name(int policy)174 static const char *get_policy_name(int policy)
175 {
176 	switch (policy) {
177 	case SCHED_OTHER:
178 		return "SCHED_OTHER";
179 	case SCHED_FIFO:
180 #ifdef SCHED_RESET_ON_FORK
181 	case SCHED_FIFO | SCHED_RESET_ON_FORK:
182 #endif
183 		return "SCHED_FIFO";
184 #ifdef SCHED_IDLE
185 	case SCHED_IDLE:
186 		return "SCHED_IDLE";
187 #endif
188 	case SCHED_RR:
189 #ifdef SCHED_RESET_ON_FORK
190 	case SCHED_RR | SCHED_RESET_ON_FORK:
191 #endif
192 		return "SCHED_RR";
193 #ifdef SCHED_BATCH
194 	case SCHED_BATCH:
195 		return "SCHED_BATCH";
196 #endif
197 #ifdef SCHED_DEADLINE
198 	case SCHED_DEADLINE:
199 		return "SCHED_DEADLINE";
200 #endif
201 	default:
202 		break;
203 	}
204 
205 	return _("unknown");
206 }
207 
show_sched_pid_info(struct chrt_ctl * ctl,pid_t pid)208 static void show_sched_pid_info(struct chrt_ctl *ctl, pid_t pid)
209 {
210 	int policy = -1, reset_on_fork = 0, prio = 0;
211 #ifdef SCHED_DEADLINE
212 	uint64_t deadline = 0, runtime = 0, period = 0;
213 #endif
214 
215 	/* don't display "pid 0" as that is confusing */
216 	if (!pid)
217 		pid = getpid();
218 
219 	errno = 0;
220 
221 	/*
222 	 * New way
223 	 */
224 #ifdef HAVE_SCHED_SETATTR
225 	{
226 		struct sched_attr sa;
227 
228 		if (sched_getattr(pid, &sa, sizeof(sa), 0) != 0) {
229 			if (errno == ENOSYS)
230 				goto fallback;
231 			err(EXIT_FAILURE, _("failed to get pid %d's policy"), pid);
232 		}
233 
234 		policy = sa.sched_policy;
235 		prio = sa.sched_priority;
236 		reset_on_fork = sa.sched_flags & SCHED_FLAG_RESET_ON_FORK;
237 		deadline = sa.sched_deadline;
238 		runtime = sa.sched_runtime;
239 		period = sa.sched_period;
240 	}
241 
242 	/*
243 	 * Old way
244 	 */
245 fallback:
246 	if (errno == ENOSYS)
247 #endif
248 	{
249 		struct sched_param sp;
250 
251 		policy = sched_getscheduler(pid);
252 		if (policy == -1)
253 			err(EXIT_FAILURE, _("failed to get pid %d's policy"), pid);
254 
255 		if (sched_getparam(pid, &sp) != 0)
256 			err(EXIT_FAILURE, _("failed to get pid %d's attributes"), pid);
257 		else
258 			prio = sp.sched_priority;
259 # ifdef SCHED_RESET_ON_FORK
260 		if (policy == (SCHED_FIFO|SCHED_RESET_ON_FORK) || policy == (SCHED_BATCH|SCHED_RESET_ON_FORK))
261 			reset_on_fork = 1;
262 # endif
263 	}
264 
265 	if (ctl->altered)
266 		printf(_("pid %d's new scheduling policy: %s"), pid, get_policy_name(policy));
267 	else
268 		printf(_("pid %d's current scheduling policy: %s"), pid, get_policy_name(policy));
269 
270 	if (reset_on_fork)
271 		printf("|SCHED_RESET_ON_FORK");
272 	putchar('\n');
273 
274 	if (ctl->altered)
275 		printf(_("pid %d's new scheduling priority: %d\n"), pid, prio);
276 	else
277 		printf(_("pid %d's current scheduling priority: %d\n"), pid, prio);
278 
279 #ifdef SCHED_DEADLINE
280 	if (policy == SCHED_DEADLINE) {
281 		if (ctl->altered)
282 			printf(_("pid %d's new runtime/deadline/period parameters: %ju/%ju/%ju\n"),
283 					pid, runtime, deadline, period);
284 		else
285 			printf(_("pid %d's current runtime/deadline/period parameters: %ju/%ju/%ju\n"),
286 					pid, runtime, deadline, period);
287 	}
288 #endif
289 }
290 
291 
show_sched_info(struct chrt_ctl * ctl)292 static void show_sched_info(struct chrt_ctl *ctl)
293 {
294 	if (ctl->all_tasks) {
295 		pid_t tid;
296 		struct proc_tasks *ts = proc_open_tasks(ctl->pid);
297 
298 		if (!ts)
299 			err(EXIT_FAILURE, _("cannot obtain the list of tasks"));
300 
301 		while (!proc_next_tid(ts, &tid))
302 			show_sched_pid_info(ctl, tid);
303 
304 		proc_close_tasks(ts);
305 	} else
306 		show_sched_pid_info(ctl, ctl->pid);
307 }
308 
show_min_max(void)309 static void show_min_max(void)
310 {
311 	unsigned long i;
312 	int policies[] = {
313 		SCHED_OTHER,
314 		SCHED_FIFO,
315 		SCHED_RR,
316 #ifdef SCHED_BATCH
317 		SCHED_BATCH,
318 #endif
319 #ifdef SCHED_IDLE
320 		SCHED_IDLE,
321 #endif
322 #ifdef SCHED_DEADLINE
323 		SCHED_DEADLINE,
324 #endif
325 	};
326 
327 	for (i = 0; i < ARRAY_SIZE(policies); i++) {
328 		int plc = policies[i];
329 		int max = sched_get_priority_max(plc);
330 		int min = sched_get_priority_min(plc);
331 
332 		if (max >= 0 && min >= 0)
333 			printf(_("%s min/max priority\t: %d/%d\n"),
334 					get_policy_name(plc), min, max);
335 		else
336 			printf(_("%s not supported?\n"), get_policy_name(plc));
337 	}
338 }
339 
set_sched_one_by_setscheduler(struct chrt_ctl * ctl,pid_t pid)340 static int set_sched_one_by_setscheduler(struct chrt_ctl *ctl, pid_t pid)
341 {
342 	struct sched_param sp = { .sched_priority = ctl->priority };
343 	int policy = ctl->policy;
344 
345 	errno = 0;
346 # ifdef SCHED_RESET_ON_FORK
347 	if (ctl->reset_on_fork)
348 		policy |= SCHED_RESET_ON_FORK;
349 # endif
350 
351 #if defined (__linux__) && defined(SYS_sched_setscheduler)
352 	/* musl libc returns ENOSYS for its sched_setscheduler library
353 	 * function, because the sched_setscheduler Linux kernel system call
354 	 * does not conform to Posix; so we use the system call directly
355 	 */
356 	return syscall(SYS_sched_setscheduler, pid, policy, &sp);
357 #else
358 	return sched_setscheduler(pid, policy, &sp);
359 #endif
360 }
361 
362 
363 #ifndef HAVE_SCHED_SETATTR
set_sched_one(struct chrt_ctl * ctl,pid_t pid)364 static int set_sched_one(struct chrt_ctl *ctl, pid_t pid)
365 {
366 	return set_sched_one_by_setscheduler(ctl, pid);
367 }
368 
369 #else /* !HAVE_SCHED_SETATTR */
set_sched_one(struct chrt_ctl * ctl,pid_t pid)370 static int set_sched_one(struct chrt_ctl *ctl, pid_t pid)
371 {
372 	struct sched_attr sa = { .size = sizeof(struct sched_attr) };
373 
374 	/* old API is good enough for non-deadline */
375 	if (ctl->policy != SCHED_DEADLINE)
376 		return set_sched_one_by_setscheduler(ctl, pid);
377 
378 	/* no changeed by chrt, follow the current setting */
379 	sa.sched_nice = getpriority(PRIO_PROCESS, pid);
380 
381 	/* use main() to check if the setting makes sense */
382 	sa.sched_policy	  = ctl->policy;
383 	sa.sched_priority = ctl->priority;
384 	sa.sched_runtime  = ctl->runtime;
385 	sa.sched_period   = ctl->period;
386 	sa.sched_deadline = ctl->deadline;
387 
388 # ifdef SCHED_RESET_ON_FORK
389 	if (ctl->reset_on_fork)
390 		sa.sched_flags |= SCHED_RESET_ON_FORK;
391 # endif
392 	errno = 0;
393 	return sched_setattr(pid, &sa, 0);
394 }
395 #endif /* HAVE_SCHED_SETATTR */
396 
set_sched(struct chrt_ctl * ctl)397 static void set_sched(struct chrt_ctl *ctl)
398 {
399 	if (ctl->all_tasks) {
400 		pid_t tid;
401 		struct proc_tasks *ts = proc_open_tasks(ctl->pid);
402 
403 		if (!ts)
404 			err(EXIT_FAILURE, _("cannot obtain the list of tasks"));
405 
406 		while (!proc_next_tid(ts, &tid))
407 			if (set_sched_one(ctl, tid) == -1)
408 				err(EXIT_FAILURE, _("failed to set tid %d's policy"), tid);
409 
410 		proc_close_tasks(ts);
411 
412 	} else if (set_sched_one(ctl, ctl->pid) == -1)
413 		err(EXIT_FAILURE, _("failed to set pid %d's policy"), ctl->pid);
414 
415 	ctl->altered = 1;
416 }
417 
main(int argc,char ** argv)418 int main(int argc, char **argv)
419 {
420 	struct chrt_ctl _ctl = { .pid = -1, .policy = SCHED_RR }, *ctl = &_ctl;
421 	int c;
422 
423 	static const struct option longopts[] = {
424 		{ "all-tasks",  no_argument, NULL, 'a' },
425 		{ "batch",	no_argument, NULL, 'b' },
426 		{ "deadline",   no_argument, NULL, 'd' },
427 		{ "fifo",	no_argument, NULL, 'f' },
428 		{ "idle",	no_argument, NULL, 'i' },
429 		{ "pid",	no_argument, NULL, 'p' },
430 		{ "help",	no_argument, NULL, 'h' },
431 		{ "max",        no_argument, NULL, 'm' },
432 		{ "other",	no_argument, NULL, 'o' },
433 		{ "rr",		no_argument, NULL, 'r' },
434 		{ "sched-runtime",  required_argument, NULL, 'T' },
435 		{ "sched-period",   required_argument, NULL, 'P' },
436 		{ "sched-deadline", required_argument, NULL, 'D' },
437 		{ "reset-on-fork",  no_argument,       NULL, 'R' },
438 		{ "verbose",	no_argument, NULL, 'v' },
439 		{ "version",	no_argument, NULL, 'V' },
440 		{ NULL,		no_argument, NULL, 0 }
441 	};
442 
443 	setlocale(LC_ALL, "");
444 	bindtextdomain(PACKAGE, LOCALEDIR);
445 	textdomain(PACKAGE);
446 	close_stdout_atexit();
447 
448 	while((c = getopt_long(argc, argv, "+abdD:fiphmoP:T:rRvV", longopts, NULL)) != -1)
449 	{
450 		switch (c) {
451 		case 'a':
452 			ctl->all_tasks = 1;
453 			break;
454 		case 'b':
455 #ifdef SCHED_BATCH
456 			ctl->policy = SCHED_BATCH;
457 #endif
458 			break;
459 
460 		case 'd':
461 #ifdef SCHED_DEADLINE
462 			ctl->policy = SCHED_DEADLINE;
463 #endif
464 			break;
465 		case 'f':
466 			ctl->policy = SCHED_FIFO;
467 			break;
468 		case 'R':
469 			ctl->reset_on_fork = 1;
470 			break;
471 		case 'i':
472 #ifdef SCHED_IDLE
473 			ctl->policy = SCHED_IDLE;
474 #endif
475 			break;
476 		case 'm':
477 			show_min_max();
478 			return EXIT_SUCCESS;
479 		case 'o':
480 			ctl->policy = SCHED_OTHER;
481 			break;
482 		case 'p':
483 			errno = 0;
484 			ctl->pid = strtos32_or_err(argv[argc - 1], _("invalid PID argument"));
485 			break;
486 		case 'r':
487 			ctl->policy = SCHED_RR;
488 			break;
489 		case 'v':
490 			ctl->verbose = 1;
491 			break;
492 		case 'T':
493 			ctl->runtime = strtou64_or_err(optarg, _("invalid runtime argument"));
494 			break;
495 		case 'P':
496 			ctl->period = strtou64_or_err(optarg, _("invalid period argument"));
497 			break;
498 		case 'D':
499 			ctl->deadline = strtou64_or_err(optarg, _("invalid deadline argument"));
500 			break;
501 
502 		case 'V':
503 			print_version(EXIT_SUCCESS);
504 		case 'h':
505 			usage();
506 		default:
507 			errtryhelp(EXIT_FAILURE);
508 		}
509 	}
510 
511 	if (((ctl->pid > -1) && argc - optind < 1) ||
512 	    ((ctl->pid == -1) && argc - optind < 2)) {
513 		warnx(_("bad usage"));
514 		errtryhelp(EXIT_FAILURE);
515 }
516 
517 	if ((ctl->pid > -1) && (ctl->verbose || argc - optind == 1)) {
518 		show_sched_info(ctl);
519 		if (argc - optind == 1)
520 			return EXIT_SUCCESS;
521 	}
522 
523 	errno = 0;
524 	ctl->priority = strtos32_or_err(argv[optind], _("invalid priority argument"));
525 
526 #ifdef SCHED_RESET_ON_FORK
527 	if (ctl->reset_on_fork && ctl->policy != SCHED_FIFO && ctl->policy != SCHED_RR)
528 		errx(EXIT_FAILURE, _("--reset-on-fork option is supported for "
529 				     "SCHED_FIFO and SCHED_RR policies only"));
530 #endif
531 #ifdef SCHED_DEADLINE
532 	if ((ctl->runtime || ctl->deadline || ctl->period) && ctl->policy != SCHED_DEADLINE)
533 		errx(EXIT_FAILURE, _("--sched-{runtime,deadline,period} options "
534 				     "are supported for SCHED_DEADLINE only"));
535 	if (ctl->policy == SCHED_DEADLINE) {
536 		/* The basic rule is runtime <= deadline <= period, so we can
537 		 * make deadline and runtime optional on command line. Note we
538 		 * don't check any values or set any defaults, it's kernel
539 		 * responsibility.
540 		 */
541 		if (ctl->deadline == 0)
542 			ctl->deadline = ctl->period;
543 		if (ctl->runtime == 0)
544 			ctl->runtime = ctl->deadline;
545 	}
546 #else
547 	if (ctl->runtime || ctl->deadline || ctl->period)
548 		errx(EXIT_FAILURE, _("SCHED_DEADLINE is unsupported"));
549 #endif
550 	if (ctl->pid == -1)
551 		ctl->pid = 0;
552 	if (ctl->priority < sched_get_priority_min(ctl->policy) ||
553 	    sched_get_priority_max(ctl->policy) < ctl->priority)
554 		errx(EXIT_FAILURE,
555 		     _("unsupported priority value for the policy: %d: see --max for valid range"),
556 		     ctl->priority);
557 	set_sched(ctl);
558 
559 	if (ctl->verbose)
560 		show_sched_info(ctl);
561 
562 	if (!ctl->pid) {
563 		argv += optind + 1;
564 		execvp(argv[0], argv);
565 		errexec(argv[0]);
566 	}
567 
568 	return EXIT_SUCCESS;
569 }
570