[PATCH 0/1] coresched: Manage core scheduling cookies for tasks

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Phil,

Thank you for your comments. I've attached an interdiff to this email
with all the changes that I've made in response.

Phil Auld wrote on 26-03-2024 at 15:41:
> The exec should not fork first.

You're right, this is not necessary. I've removed it.

> And the error handling I think is just not right. I believe prctl()
> returns -1 on error and sets errno. This code will report EPERM for all
> prtcl errors.  While EPERM is likely I don't think it's the only error.

Good catch! I assumed that the return values of the `prctl()` call
matched the return values of `sched_core_share_pid` in
`kernel/sched/core_sched.c`. I've updated the error handling code to
just use errno instead.

> On Sat, Mar 16, 2024 at 06:10:03PM +0100, Thijs Raymakers wrote:
>> - How should the program behave if the prctl core scheduling API is not
>>   available? It has been in Linus' tree since november 2021
>>   (commit a41b74451b35f7a6529689760eb8c05241feecbc) but it can be
>>   disabled with CONFIG_SCHED_CORE=n
> 
> This one is tricky. If I recall the error you get is ambiguous in this case
> (EINVAL, which can be returned for other reasons). It would be nice to be
> able to report that specifically. 

In general, `prctl` does indeed return EINVAL if the operation is not
recognized, or not supported on the system. The `PR_SCHED_CORE`
operation itself only returns EINVAL if it is called with
  - an invalid operation
  - an invalid type
  - a negative PID
  - an invalid cookie store address (for PR_SCHED_CORE_GET)
Assuming that all these cases are prevented by the util, we could
interpret a EINVAL as a sign that PR_SCHED_CORE is not supported on
the system.

>> - I've currently licensed it under the EUPL v1.2, which is easier to
>>   enforce in my jurisdiction than the GPL. It is GPL compatible so it
>>   shouldn't be an issue, but if anybody has any remarks on this, please
>>   let me know.
> 
> The util-linux package is GPL so personally I think it would be good to just
> make any new addition to that package also GPL. 

As far as I understand it, util-linux is a collection of different works
with different licenses (see the README.licensing file). Not every
program included in util-linux has to be GPL.

>> +typedef enum {
>> +	SCHED_CORE_SCOPE_PID = PR_SCHED_CORE_SCOPE_THREAD,
>> +	SCHED_CORE_SCOPE_TGID = PR_SCHED_CORE_SCOPE_THREAD_GROUP,
>> +	SCHED_CORE_SCOPE_PGID = PR_SCHED_CORE_SCOPE_PROCESS_GROUP,
>> +} core_sched_type_t;
>> +
> 
> Is this needed? You've made the names a little shorter I guess.
> Also some versions of prctl.h don't have the PR_SCHED_CORE_SCOPE* defines
> even when they have the base PR_SCHED_CORE macros.

I've changed to to just use the PR_SCHED_CORE_SCOPE* definitions
instead.

>> +void core_sched_create_cookie(struct args *args)
>> +{
>> +	int prctl_errno = prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE,
>> +				args->from_pid, args->type, 0);
> 
> We are creating a new cookie on from_pid, seems odd.

I agree, I changed to to `args->to_pid` because that is more logical.

>> +	fprintf(stdout, _(" %s --get <PID>\n"), program_invocation_short_name);
>> +	fprintf(stdout, _(" %s --new <PID> [-t <TYPE>]\n"),
>> +		program_invocation_short_name);
>> +	fprintf(stdout, _(" %s --copy -s <PID> -d <PID> [-t <TYPE>]\n"),
>> +		program_invocation_short_name);
>> +	fprintf(stdout, _(" %s --exec [-s <PID>] -- PROGRAM ARGS... \n"),
>> +		program_invocation_short_name);
> 
> There are 4 different arguments that take a pid when there are really
> only at most 2 needed.

I chose this to keep the amount of options that you have to provide as
small possible. I thought that `coresched -n 123` would be easier to use
than `coresched -n -p 123`. I guess the amount of different arguments
that can take PIDs can be reduced to two, but it would make the commands
a bit longer.

> "Get" could just be the default with no arguments given.

I've removed the explicit --exec and made it the default when a program
name is provided. If no program name is provided, it defaults to --get.

>> +	enum {
>> +		OPT_GET = 'g',
>> +		OPT_NEW = 'n',
>> +		OPT_COPY = 'c',
>> +		OPT_EXEC = 'e',
>> +		OPT_SRC = 's',
>> +		OPT_DEST = 'd',
>> +		OPT_TYPE = 't',
>> +		OPT_VERSION = 'V',
>> +		OPT_HELP = 'h'
>> +	};
> 
> This seems over-engineered.  You have to specificy the characters
> in the getopt_long call anyway so this just complicates the code for
> no reason it seems to me.

Fair point, I've removed this. I initially added this to make it easier
to experiment a bit with different values for the options, but that is
no longer necessary.

Thanks!

- Thijs

Thijs Raymakers (1):
  coresched: Manage core scheduling cookies for tasks

 .gitignore                  |   1 +
 bash-completion/coresched   |   0
 configure.ac                |  12 +-
 meson.build                 |  16 +-
 meson_options.txt           |   2 +-
 schedutils/Makemodule.am    |   8 +
 schedutils/coresched.1.adoc |  16 ++
 schedutils/coresched.c      | 353 ++++++++++++++++++++++++++++++++++++
 8 files changed, 402 insertions(+), 6 deletions(-)
 create mode 100644 bash-completion/coresched
 create mode 100644 schedutils/coresched.1.adoc
 create mode 100644 schedutils/coresched.c

Interdiff:
diff --git a/schedutils/coresched.c b/schedutils/coresched.c
index 4be8f9fda..17d775f2d 100644
--- a/schedutils/coresched.c
+++ b/schedutils/coresched.c
@@ -18,18 +18,24 @@
 #include "nls.h"
 #include "strutils.h"
 
+// These definitions might not be defined, even if the
+// prctl interface accepts them.
+#ifndef PR_SCHED_CORE_SCOPE_THREAD
+#define PR_SCHED_CORE_SCOPE_THREAD 0
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_THREAD_GROUP
+#define PR_SCHED_CORE_SCOPE_THREAD_GROUP 1
+#endif
+#ifndef PR_SCHED_CORE_SCOPE_PROCESS_GROUP
+#define PR_SCHED_CORE_SCOPE_PROCESS_GROUP 2
+#endif
+
+typedef int core_sched_type_t;
 typedef enum {
-	SCHED_CORE_SCOPE_PID = PR_SCHED_CORE_SCOPE_THREAD,
-	SCHED_CORE_SCOPE_TGID = PR_SCHED_CORE_SCOPE_THREAD_GROUP,
-	SCHED_CORE_SCOPE_PGID = PR_SCHED_CORE_SCOPE_PROCESS_GROUP,
-} core_sched_type_t;
-
-typedef enum {
-	SCHED_CORE_CMD_NONE = 0,
+	SCHED_CORE_CMD_EXEC = 0,
 	SCHED_CORE_CMD_GET = 1,
 	SCHED_CORE_CMD_CREATE = 2,
 	SCHED_CORE_CMD_COPY = 4,
-	SCHED_CORE_CMD_EXEC = 8,
 } core_sched_cmd_t;
 
 struct args {
@@ -50,44 +56,40 @@ void core_sched_exec_with_cookie(struct args *args, char **argv);
 core_sched_type_t parse_core_sched_type(char *str);
 bool verify_arguments(struct args *args);
 void parse_arguments(int argc, char **argv, struct args *args);
+void set_pid_or_err(pid_t *dest, pid_t src, const char *err_msg);
+static void __attribute__((__noreturn__)) usage(void);
 
 unsigned long core_sched_get_cookie(struct args *args)
 {
 	unsigned long cookie = 0;
-	int prctl_errno = prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET,
-				args->from_pid, SCHED_CORE_SCOPE_PID, &cookie);
-	if (prctl_errno) {
-		errx(-prctl_errno, "Failed to get cookie from PID %d",
-		     args->from_pid);
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_GET, args->from_pid,
+		  PR_SCHED_CORE_SCOPE_THREAD, &cookie)) {
+		err(errno, "Failed to get cookie from PID %d", args->from_pid);
 	}
 	return cookie;
 }
 
 void core_sched_create_cookie(struct args *args)
 {
-	int prctl_errno = prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE,
-				args->from_pid, args->type, 0);
-	if (prctl_errno) {
-		errx(-prctl_errno, "Failed to create cookie for PID %d",
-		     args->from_pid);
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, args->to_pid, args->type,
+		  0)) {
+		err(errno, "Failed to create cookie for PID %d",
+		    args->from_pid);
 	}
 }
 
 void core_sched_pull_cookie(pid_t from)
 {
-	int prctl_errno = prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
-				SCHED_CORE_SCOPE_PID, 0);
-	if (prctl_errno) {
-		errx(-prctl_errno, "Failed to pull cookie from PID %d", from);
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_FROM, from,
+		  PR_SCHED_CORE_SCOPE_THREAD, 0)) {
+		err(errno, "Failed to pull cookie from PID %d", from);
 	}
 }
 
 void core_sched_push_cookie(pid_t to, core_sched_type_t type)
 {
-	int prctl_errno =
-		prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0);
-	if (prctl_errno) {
-		errx(-prctl_errno, "Failed to push cookie to PID %d", to);
+	if (prctl(PR_SCHED_CORE, PR_SCHED_CORE_SHARE_TO, to, type, 0)) {
+		err(errno, "Failed to push cookie to PID %d", to);
 	}
 }
 
@@ -100,46 +102,35 @@ void core_sched_copy_cookie(struct args *args)
 void core_sched_exec_with_cookie(struct args *args, char **argv)
 {
 	if (!args->exec_argv_offset) {
-		errx(EINVAL, "when --exec is provided, a program name "
-			     "has to be given.");
+		usage();
 	}
 
 	// Move the argument list to the first argument of the program
 	argv = &argv[args->exec_argv_offset];
 
-	pid_t pid = fork();
-	if (pid == -1) {
-		errx(errno, "Failed to spawn new process");
+	// If a source PID is provided, try to copy the cookie from
+	// that PID. Otherwise, create a brand new cookie with the
+	// provided type.
+	if (args->from_pid) {
+		core_sched_pull_cookie(args->from_pid);
+	} else {
+		args->to_pid = getpid();
+		core_sched_create_cookie(args);
 	}
 
-	if (!pid) {
-		// If a source PID is provided, try to copy the cookie from
-		// that PID. Otherwise, create a brand new cookie with the
-		// provided type.
-		if (args->from_pid) {
-			core_sched_pull_cookie(args->from_pid);
-		} else {
-			args->from_pid = getpid();
-			core_sched_create_cookie(args);
-		}
-		if (execvp(argv[0], argv)) {
-			errexec(argv[0]);
-		}
-	} else {
-		int status = 0;
-		waitpid(pid, &status, 0);
-		exit(status);
+	if (execvp(argv[0], argv)) {
+		errexec(argv[0]);
 	}
 }
 
 core_sched_type_t parse_core_sched_type(char *str)
 {
 	if (!strncmp(str, "pid\0", 4)) {
-		return SCHED_CORE_SCOPE_PID;
+		return PR_SCHED_CORE_SCOPE_THREAD;
 	} else if (!strncmp(str, "tgid\0", 5)) {
-		return SCHED_CORE_SCOPE_TGID;
+		return PR_SCHED_CORE_SCOPE_THREAD_GROUP;
 	} else if (!strncmp(str, "pgid\0", 5)) {
-		return SCHED_CORE_SCOPE_PGID;
+		return PR_SCHED_CORE_SCOPE_PROCESS_GROUP;
 	}
 
 	errx(EINVAL, "'%s' is an invalid option. Must be one of pid/tgid/pgid",
@@ -155,7 +146,7 @@ static void __attribute__((__noreturn__)) usage(void)
 		program_invocation_short_name);
 	fprintf(stdout, _(" %s --copy -s <PID> -d <PID> [-t <TYPE>]\n"),
 		program_invocation_short_name);
-	fprintf(stdout, _(" %s --exec [-s <PID>] -- PROGRAM ARGS... \n"),
+	fprintf(stdout, _(" %s [-s <PID>] -- PROGRAM ARGS... \n"),
 		program_invocation_short_name);
 
 	fputs(USAGE_SEPARATOR, stdout);
@@ -169,9 +160,6 @@ static void __attribute__((__noreturn__)) usage(void)
 	fputsln(_(" -c, --copy              copy the core scheduling cookie from PID to\n"
 		  "                           another PID, requires the --source and --dest option"),
 		stdout);
-	fputsln(_(" -e, --exec              execute a program with a new core scheduling\n"
-		  "                           cookie."),
-		stdout);
 
 	fputs(USAGE_OPTIONS, stdout);
 	fputsln(_(" -s, --source <PID>      where to copy the core scheduling cookie from."),
@@ -193,116 +181,141 @@ static void __attribute__((__noreturn__)) usage(void)
 
 bool verify_arguments(struct args *args)
 {
-	if (args->cmd == SCHED_CORE_CMD_NONE) {
-		usage();
-	}
-
 	// Check if the value of args->cmd is a power of 2
 	// In that case, only a single function option was set.
-	if (!(args->cmd && !(args->cmd & (args->cmd - 1)))) {
-		errx(EINVAL, "Cannot do more than one function at a time.");
-	}
-
-	if (args->from_pid < 0) {
-		errx(EINVAL, "source PID cannot be negative");
+	if (args->cmd & (args->cmd - 1)) {
+		errx(EINVAL,
+		     "Cannot do more than one function at a time. See %s --help",
+		     program_invocation_short_name);
 	}
 
-	if (args->to_pid < 0) {
-		errx(EINVAL, "destination PID cannot be negative");
-	}
-
-	if (args->from_pid == 0 && args->cmd == SCHED_CORE_CMD_COPY) {
-		errx(EINVAL, "valid argument to --source is required");
-	}
-
-	if (args->to_pid == 0 && args->cmd == SCHED_CORE_CMD_COPY) {
-		errx(EINVAL, "valid argument to --dest is required");
+	switch (args->cmd) {
+	case SCHED_CORE_CMD_GET:
+		if (args->to_pid) {
+			errx(EINVAL,
+			     "Cannot use -d/--dest with this -g/--get. See %s --help",
+			     program_invocation_short_name);
+		}
+		break;
+	case SCHED_CORE_CMD_CREATE:
+		if (args->from_pid) {
+			errx(EINVAL,
+			     "Cannot use -s/--source with this -n/--new. See %s --help",
+			     program_invocation_short_name);
+		}
+		break;
+	case SCHED_CORE_CMD_COPY:
+		if (!args->from_pid) {
+			errx(EINVAL,
+			     "-s/--source PID is required when copying");
+		}
+		if (!args->to_pid) {
+			errx(EINVAL, "-d/--dest PID is required when copying");
+		}
+		break;
+	case SCHED_CORE_CMD_EXEC:
+		if (args->to_pid) {
+			errx(EINVAL,
+			     "Cannot use -d/--dest when spawning a program. See %s --help",
+			     program_invocation_short_name);
+		}
+		break;
 	}
+	return true;
+}
 
-	if (args->from_pid == 0 && args->cmd != SCHED_CORE_CMD_EXEC) {
-		errx(EINVAL, "PID cannot be zero");
+void set_pid_or_err(pid_t *dest, pid_t src, const char *err_msg)
+{
+	if (*dest) {
+		errx(EINVAL, "Ambigious usage: %s", err_msg);
+	} else {
+		*dest = src;
 	}
-
-	return true;
 }
 
+static const char *ERR_MSG_MULTIPLE_SOURCE_PIDS =
+	"Multiple source PIDs defined";
 void parse_arguments(int argc, char **argv, struct args *args)
 {
 	int c;
-
-	enum {
-		OPT_GET = 'g',
-		OPT_NEW = 'n',
-		OPT_COPY = 'c',
-		OPT_EXEC = 'e',
-		OPT_SRC = 's',
-		OPT_DEST = 'd',
-		OPT_TYPE = 't',
-		OPT_VERSION = 'V',
-		OPT_HELP = 'h'
-	};
+	pid_t tmp;
 
 	static const struct option longopts[] = {
-		{ "get", required_argument, NULL, OPT_GET },
-		{ "new", required_argument, NULL, OPT_NEW },
-		{ "copy", no_argument, NULL, OPT_COPY },
-		{ "exec", no_argument, NULL, OPT_EXEC },
-		{ "source", required_argument, NULL, OPT_SRC },
-		{ "destination", required_argument, NULL, OPT_DEST },
-		{ "type", required_argument, NULL, OPT_TYPE },
-		{ "version", no_argument, NULL, OPT_VERSION },
-		{ "help", no_argument, NULL, OPT_HELP },
+		{ "get", required_argument, NULL, 'g' },
+		{ "new", required_argument, NULL, 'n' },
+		{ "copy", no_argument, NULL, 'c' },
+		{ "source", required_argument, NULL, 's' },
+		{ "destination", required_argument, NULL, 'd' },
+		{ "type", required_argument, NULL, 't' },
+		{ "version", no_argument, NULL, 'V' },
+		{ "help", no_argument, NULL, 'h' },
 		{ NULL, 0, NULL, 0 }
 	};
 
-	while ((c = getopt_long(argc, argv, "g:n:ces:d:t:Vh", longopts,
-				NULL)) != -1)
+	while ((c = getopt_long(argc, argv, "g:n:cs:d:t:Vh", longopts, NULL)) !=
+	       -1)
 		switch (c) {
-		case OPT_GET:
+		case 'g':
 			args->cmd |= SCHED_CORE_CMD_GET;
-			args->from_pid = strtos32_or_err(
-				optarg, "Failed to parse PID for --get");
+			tmp = strtopid_or_err(
+				optarg, "Failed to parse PID for -g/--get");
+			set_pid_or_err(&args->from_pid, tmp,
+				       ERR_MSG_MULTIPLE_SOURCE_PIDS);
 			break;
-		case OPT_NEW:
+		case 'n':
 			args->cmd |= SCHED_CORE_CMD_CREATE;
-			args->from_pid = strtos32_or_err(
-				optarg, "Failed to parse PID for --new");
+			tmp = strtopid_or_err(
+				optarg, "Failed to parse PID for -n/--new");
+			set_pid_or_err(&args->to_pid, tmp,
+				       ERR_MSG_MULTIPLE_SOURCE_PIDS);
 			break;
-		case OPT_COPY:
+		case 'c':
 			args->cmd |= SCHED_CORE_CMD_COPY;
 			break;
-		case OPT_EXEC:
-			args->cmd |= SCHED_CORE_CMD_EXEC;
-			break;
-		case OPT_SRC:
-			args->from_pid = strtos32_or_err(
-				optarg, "Failed to parse PID for --source");
+		case 's':
+			tmp = strtopid_or_err(
+				optarg, "Failed to parse PID for -s/--source");
+			set_pid_or_err(&args->from_pid, tmp,
+				       ERR_MSG_MULTIPLE_SOURCE_PIDS);
 			break;
-		case OPT_DEST:
-			args->to_pid = strtos32_or_err(
-				optarg, "Failed to parse PID for --dest");
+		case 'd':
+			tmp = strtopid_or_err(
+				optarg, "Failed to parse PID for -d/--dest");
+			set_pid_or_err(&args->to_pid, tmp,
+				       "Multiple destination PIDs defined");
 			break;
-		case OPT_TYPE:
+		case 't':
 			args->type = parse_core_sched_type(optarg);
 			break;
-		case OPT_VERSION:
+		case 'V':
 			print_version(EXIT_SUCCESS);
-		case OPT_HELP:
+		case 'h':
 			usage();
 		default:
 			errtryhelp(EXIT_FAILURE);
 		}
 
 	if (argc > optind) {
-		args->exec_argv_offset = optind;
+		if (args->cmd == SCHED_CORE_CMD_EXEC) {
+			args->exec_argv_offset = optind;
+		} else {
+			// -g, -n or -c AND a program to run is provided
+			errx(EINVAL, "bad usage, see %s --help",
+			     program_invocation_short_name);
+		}
+	} else if (argc == optind && args->from_pid) {
+		// Neither a function (-g, -n, or -c), nor a program to
+		// run is given
+		args->cmd = SCHED_CORE_CMD_GET;
 	}
+
 	verify_arguments(args);
 }
 
 int main(int argc, char **argv)
 {
 	struct args arguments = { 0 };
-	arguments.type = SCHED_CORE_SCOPE_TGID;
+	arguments.type = PR_SCHED_CORE_SCOPE_THREAD_GROUP;
 
 	setlocale(LC_ALL, "");
 	bindtextdomain(PACKAGE, LOCALEDIR);
-- 
2.44.0





[Index of Archives]     [Netdev]     [Ethernet Bridging]     [Linux Wireless]     [Kernel Newbies]     [Security]     [Linux for Hams]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux RAID]     [Linux Admin]     [Samba]

  Powered by Linux