Explorar o código

Merge pull request #56 from kakra/feature/soft-realtime-scheduling

Leverage soft real time capabilities
Alex Smith %!s(int64=6) %!d(string=hai) anos
pai
achega
7a1eac27ae
Modificáronse 6 ficheiros con 138 adicións e 0 borrados
  1. 6 0
      README.md
  2. 23 0
      daemon/daemon_config.c
  3. 10 0
      daemon/daemon_config.h
  4. 83 0
      daemon/gamemode.c
  5. 8 0
      data/gamemoded.1
  6. 8 0
      example/gamemode.ini

+ 6 - 0
README.md

@@ -5,6 +5,12 @@ The design has a clear-cut abstraction between the host daemon and library (`gam
 
 GameMode was designed primarily as a stop-gap solution to problems with the Intel and AMD CPU powersave or ondemand governors, but is now able to launch custom user defined plugins, and is intended to be expanded further, as there are a wealth of automation tasks one might want to apply.
 
+GameMode can leverage support for soft real time mode if the running kernel supports `SCHED_ISO`. This adjusts the scheduling of the game to real time without sacrificing system stability by starving other processes.
+
+GameMode adjusts the nice priority of games to -4 by default to give it a slight IO and CPU priority over other background processes. This only works if your user is permitted to adjust priorities within the limits configured by PAM. See `/etc/security/limits.conf`.
+
+Please take note that some games may actually run seemingly slower with `SCHED_ISO` if the game makes use of busy looping while interacting with the graphic driver. The same may happen if you apply too strong nice values. This effect is called priority inversion: Due to the high priority given to busy loops, there may be too few resources left for the graphics driver. Thus, sane defaults were chosen to not expose this effect on most systems. Part of this default is a heuristic which automatically turns off `SCHED_ISO` if GameMode detects three or less CPU cores. Your experience may change based on using GL threaded optimizations, CPU core binding (taskset), the graphic driver, or different CPU architectures. If you experience bad input latency or inconsistent FPS, try switching these configurations on or off first and report back. `SCHED_ISO` comes with a protection against this effect by falling back to normal scheduling as soon as the `SCHED_ISO` process uses more than 70% avarage across all CPU cores. This default value can be adjusted outside of the scope of GameMode (it's in `/proc/sys/kernel/iso_cpu`). This value also protects against compromising system stability, do not set it to 100% as this would turn the game into a full real time process, thus potentially starving all other OS components from CPU resources.
+
 Issues with GameMode should be reported here in the issues section, and not reported to Feral directly.
 
 ---

+ 23 - 0
daemon/daemon_config.c

@@ -67,6 +67,9 @@ struct GameModeConfig {
 	char defaultgov[CONFIG_VALUE_MAX];
 	char desiredgov[CONFIG_VALUE_MAX];
 
+	char softrealtime[CONFIG_VALUE_MAX];
+	long renice;
+
 	long reaper_frequency;
 };
 
@@ -157,6 +160,10 @@ static int inih_handler(void *user, const char *section, const char *name, const
 			valid = get_string_value(value, self->defaultgov);
 		} else if (strcmp(name, "desiredgov") == 0) {
 			valid = get_string_value(value, self->desiredgov);
+		} else if (strcmp(name, "softrealtime") == 0) {
+			valid = get_string_value(value, self->softrealtime);
+		} else if (strcmp(name, "renice") == 0) {
+			valid = get_long_value(name, value, &self->renice);
 		}
 	} else if (strcmp(section, "custom") == 0) {
 		/* Custom subsection */
@@ -401,3 +408,19 @@ void config_get_desired_governor(GameModeConfig *self, char governor[CONFIG_VALU
 {
 	memcpy_locked_config(self, governor, self->desiredgov, sizeof(self->desiredgov));
 }
+
+/*
+ * Get the chosen soft realtime behavior
+ */
+void config_get_soft_realtime(GameModeConfig *self, char softrealtime[CONFIG_VALUE_MAX])
+{
+	memcpy_locked_config(self, softrealtime, self->softrealtime, sizeof(self->softrealtime));
+}
+
+/*
+ * Get the renice value
+ */
+void config_get_renice_value(GameModeConfig *self, long *value)
+{
+	memcpy_locked_config(self, value, &self->renice, sizeof(long));
+}

+ 10 - 0
daemon/daemon_config.h

@@ -103,3 +103,13 @@ void config_get_default_governor(GameModeConfig *self, char governor[CONFIG_VALU
  * Get the chosen desired governor
  */
 void config_get_desired_governor(GameModeConfig *self, char governor[CONFIG_VALUE_MAX]);
+
+/*
+ * Get the chosen soft realtime behavior
+ */
+void config_get_soft_realtime(GameModeConfig *self, char softrealtime[CONFIG_VALUE_MAX]);
+
+/*
+ * Get the renice value
+ */
+void config_get_renice_value(GameModeConfig *self, long *value);

+ 83 - 0
daemon/gamemode.c

@@ -38,12 +38,27 @@ POSSIBILITY OF SUCH DAMAGE.
 #include "logging.h"
 
 #include <linux/limits.h>
+#include <linux/sched.h>
 #include <pthread.h>
+#include <sched.h>
 #include <signal.h>
 #include <stdatomic.h>
 #include <string.h>
+#include <sys/resource.h>
+#include <sys/sysinfo.h>
 #include <systemd/sd-daemon.h>
 
+/* SCHED_ISO may not be defined as it is a reserved value not yet
+ * implemented in official kernel sources, see linux/sched.h.
+ */
+#ifndef SCHED_ISO
+#define SCHED_ISO 4
+#endif
+
+/* Priority to renice the process to.
+ */
+#define NICE_DEFAULT_PRIORITY -4
+
 /**
  * The GameModeClient encapsulates the remote connection, providing a list
  * form to contain the pid and credentials.
@@ -150,6 +165,71 @@ void game_mode_context_destroy(GameModeContext *self)
 	pthread_rwlock_destroy(&self->rwlock);
 }
 
+/**
+ * Apply scheduling policies
+ *
+ * This tries to change the scheduler of the client to soft realtime mode
+ * available in some kernels as SCHED_ISO. It also tries to adjust the nice
+ * level. If some of each fail, ignore this and log a warning.
+ *
+ * We don't need to store the current values because when the client exits,
+ * everything will be good: Scheduling is only applied to the client and
+ * its children.
+ */
+static void game_mode_apply_scheduler(GameModeContext *self, pid_t client)
+{
+	LOG_MSG("Setting scheduling policies...\n");
+
+	/*
+	 * read configuration "renice" (1..20)
+	 */
+	long int renice = 0;
+	config_get_renice_value(self->config, &renice);
+	if ((renice < 1) || (renice > 20)) {
+		LOG_ERROR("Renice value [%ld] defaulted to [%d].\n", renice, -NICE_DEFAULT_PRIORITY);
+		renice = NICE_DEFAULT_PRIORITY;
+	} else {
+		renice = -renice;
+	}
+
+	/*
+	 * don't adjust priority if it was already adjusted
+	 */
+	if (getpriority(PRIO_PROCESS, (id_t)client) != 0) {
+		LOG_ERROR("Client [%d] already reniced, ignoring.\n", client);
+	} else if (setpriority(PRIO_PROCESS, (id_t)client, (int)renice)) {
+		LOG_ERROR("Renicing client [%d] failed with error %d, ignoring.\n", client, errno);
+	}
+
+	/*
+	 * read configuration "softrealtime" (on, off, auto)
+	 */
+	char softrealtime[CONFIG_VALUE_MAX] = { 0 };
+	config_get_soft_realtime(self->config, softrealtime);
+
+	/*
+	 * Enable unconditionally or auto-detect soft realtime usage,
+	 * auto detection is based on observations where dual-core CPU suffered
+	 * priority inversion problems with the graphics driver thus running
+	 * slower as a result, so enable only with more than 3 cores.
+	 */
+	bool enable_softrealtime = (strcmp(softrealtime, "on") == 0) || (get_nprocs() > 3);
+
+	/*
+	 * Actually apply the scheduler policy if not explicitly turned off
+	 */
+	if (!(strcmp(softrealtime, "off") == 0) && (enable_softrealtime)) {
+		const struct sched_param p = { .sched_priority = 0 };
+		if (sched_setscheduler(client, SCHED_ISO, &p)) {
+			LOG_ERROR("Setting client [%d] to SCHED_ISO failed with error %d, ignoring.\n",
+			          client,
+			          errno);
+		}
+	} else {
+		LOG_ERROR("Not using softrealtime, setting is '%s'.\n", softrealtime);
+	}
+}
+
 /**
  * Pivot into game mode.
  *
@@ -343,6 +423,9 @@ bool game_mode_context_register(GameModeContext *self, pid_t client)
 		game_mode_context_enter(self);
 	}
 
+	/* Apply scheduler policies */
+	game_mode_apply_scheduler(self, client);
+
 	return true;
 }
 

+ 8 - 0
data/gamemoded.1

@@ -102,6 +102,14 @@ desiredgov=performance
 ; The default governer is used when leaving GameMode instead of restoring the original value
 defaultgov=powersave
 
+; By default, GameMode changes the scheduler policy to SCHED_ISO with 4 or more CPU cores,
+; force enable or disable with "on" or "off"
+softrealtime=auto
+
+; By default, GameMode renices the client to -4, you can put any value between 1 and 20 here,
+; the value will be negated and applied as a nice value
+renice = 4
+
 [filter]
 ; If "whitelist" entry has a value(s)
 ; gamemode will reject anything not in the whitelist

+ 8 - 0
example/gamemode.ini

@@ -7,6 +7,14 @@ desiredgov=performance
 ; The default governer is used when leaving GameMode instead of restoring the original value
 defaultgov=powersave
 
+; By default, GameMode changes the scheduler policy to SCHED_ISO with 4 or more CPU cores,
+; force enable or disable with "on" or "off"
+softrealtime=auto
+
+; By default, GameMode renices the client to -4, you can put any value between 1 and 20 here,
+; the value will be negated and applied as a nice value
+renice = 4
+
 [filter]
 ; If "whitelist" entry has a value(s)
 ; gamemode will reject anything not in the whitelist