diff options
| -rw-r--r-- | .gitignore | 7 | ||||
| -rw-r--r-- | Makefile | 5 | ||||
| -rw-r--r-- | bin/resctl.c | 235 |
3 files changed, 246 insertions, 1 deletions
| @@ -3,10 +3,13 @@ | |||
| 3 | *.a | 3 | *.a |
| 4 | 4 | ||
| 5 | 5 | ||
| 6 | # editor backup files | ||
| 7 | *~ | ||
| 6 | 8 | ||
| 7 | # generated files | 9 | # generated files |
| 8 | tests/test_catalog.inc | 10 | tests/test_catalog.inc |
| 9 | *.d | 11 | *.d |
| 12 | *.d.* | ||
| 10 | docs/ | 13 | docs/ |
| 11 | 14 | ||
| 12 | # executables | 15 | # executables |
| @@ -23,6 +26,8 @@ showst | |||
| 23 | rtspin | 26 | rtspin |
| 24 | cycles | 27 | cycles |
| 25 | measure_syscall | 28 | measure_syscall |
| 29 | uncache | ||
| 30 | resctrl | ||
| 26 | 31 | ||
| 27 | # build system files | 32 | # build system files |
| 28 | .config | 33 | .config |
| @@ -32,3 +37,5 @@ test_catalog.inc | |||
| 32 | # files copied in from the kernel tree | 37 | # files copied in from the kernel tree |
| 33 | include/litmus | 38 | include/litmus |
| 34 | arch/*/include/asm | 39 | arch/*/include/asm |
| 40 | arch/x86/include/generated | ||
| 41 | arch/x86/include/uapi | ||
| @@ -73,7 +73,7 @@ AR := ${CROSS_COMPILE}${AR} | |||
| 73 | 73 | ||
| 74 | all = lib ${rt-apps} | 74 | all = lib ${rt-apps} |
| 75 | rt-apps = cycles base_task rt_launch rtspin release_ts measure_syscall \ | 75 | rt-apps = cycles base_task rt_launch rtspin release_ts measure_syscall \ |
| 76 | base_mt_task uncache runtests | 76 | base_mt_task uncache runtests resctl |
| 77 | 77 | ||
| 78 | .PHONY: all lib clean dump-config TAGS tags cscope help doc | 78 | .PHONY: all lib clean dump-config TAGS tags cscope help doc |
| 79 | 79 | ||
| @@ -235,6 +235,9 @@ obj-release_ts = release_ts.o | |||
| 235 | obj-measure_syscall = null_call.o | 235 | obj-measure_syscall = null_call.o |
| 236 | lib-measure_syscall = -lm | 236 | lib-measure_syscall = -lm |
| 237 | 237 | ||
| 238 | obj-resctl = resctl.o | ||
| 239 | |||
| 240 | |||
| 238 | # ############################################################################## | 241 | # ############################################################################## |
| 239 | # Build everything that depends on liblitmus. | 242 | # Build everything that depends on liblitmus. |
| 240 | 243 | ||
diff --git a/bin/resctl.c b/bin/resctl.c new file mode 100644 index 0000000..f5eda85 --- /dev/null +++ b/bin/resctl.c | |||
| @@ -0,0 +1,235 @@ | |||
| 1 | #include <stdio.h> | ||
| 2 | #include <stdlib.h> | ||
| 3 | #include <string.h> | ||
| 4 | #include <unistd.h> | ||
| 5 | #include <limits.h> | ||
| 6 | #include <signal.h> | ||
| 7 | |||
| 8 | #include <sched.h> | ||
| 9 | |||
| 10 | #include "litmus.h" | ||
| 11 | #include "common.h" | ||
| 12 | |||
| 13 | const char *usage_msg = | ||
| 14 | "Usage: resctl OPTIONS [INTERVAL-START,INTERVAL-END]*\n" | ||
| 15 | " -n ID create new reservation with id ID\n" | ||
| 16 | " -a PID attach already-running process PID to reservation\n" | ||
| 17 | " -r ID specify which reservation to attach to (not needed with -n)\n" | ||
| 18 | " -t TYPE type of reservation (polling-periodic, polling-sporadic, table-driven)\n" | ||
| 19 | " -c CPU physical partition or cluster to assign to\n" | ||
| 20 | " -b BUDGET polling reservation budget (in ms, default: 10ms)\n" | ||
| 21 | " -p PERIOD polling reservation period (in ms, default: 100ms)\n" | ||
| 22 | " -d DEADLINE relative deadline, implicit by default (in ms)\n" | ||
| 23 | " -o OFFSET offset (also known as phase), zero by default (in ms)\n" | ||
| 24 | " -q PRIORITY priority to use (EDF by default, max=0)\n" | ||
| 25 | " -m MAJOR-CYCLE major cycle length (in ms, for table-driven reservations) \n" | ||
| 26 | "\n"; | ||
| 27 | |||
| 28 | void usage(char *error) { | ||
| 29 | fprintf(stderr, "%s\n%s", error, usage_msg); | ||
| 30 | exit(1); | ||
| 31 | } | ||
| 32 | |||
| 33 | |||
| 34 | static void attach_task(int attach_pid, struct reservation_config *config) | ||
| 35 | { | ||
| 36 | int ret; | ||
| 37 | struct rt_task param; | ||
| 38 | struct sched_param linux_param; | ||
| 39 | |||
| 40 | ret = be_migrate_thread_to_cpu(attach_pid, config->cpu); | ||
| 41 | if (ret < 0) { | ||
| 42 | fprintf(stderr, "failed to migrate task %d to CPU %d\n", | ||
| 43 | attach_pid, config->cpu); | ||
| 44 | exit(4); | ||
| 45 | } | ||
| 46 | |||
| 47 | init_rt_task_param(¶m); | ||
| 48 | /* dummy values */ | ||
| 49 | param.exec_cost = ms2ns(100); | ||
| 50 | param.period = ms2ns(100); | ||
| 51 | /* specify reservation as "virtual" CPU */ | ||
| 52 | param.cpu = config->id; | ||
| 53 | |||
| 54 | ret = set_rt_task_param(attach_pid, ¶m); | ||
| 55 | if (ret < 0) { | ||
| 56 | fprintf(stderr, "failed to set RT task parameters for task %d (%m)\n", | ||
| 57 | attach_pid); | ||
| 58 | exit(2); | ||
| 59 | } | ||
| 60 | |||
| 61 | linux_param.sched_priority = 0; | ||
| 62 | ret = sched_setscheduler(attach_pid, SCHED_LITMUS, &linux_param); | ||
| 63 | if (ret < 0) { | ||
| 64 | fprintf(stderr, "failed to transition task %d to LITMUS^RT class (%m)\n", | ||
| 65 | attach_pid); | ||
| 66 | exit(3); | ||
| 67 | } | ||
| 68 | } | ||
| 69 | |||
| 70 | static struct lt_interval* parse_td_intervals(int argc, char** argv, | ||
| 71 | unsigned int *num_intervals, lt_t major_cycle) | ||
| 72 | { | ||
| 73 | int i, matched; | ||
| 74 | struct lt_interval *slots = malloc(sizeof(slots[0]) * argc); | ||
| 75 | double start, end; | ||
| 76 | |||
| 77 | *num_intervals = 0; | ||
| 78 | for (i = 0; i < argc; i++) { | ||
| 79 | matched = sscanf(argv[i], "[%lf,%lf]", &start, &end); | ||
| 80 | if (matched != 2) { | ||
| 81 | fprintf(stderr, "could not parse '%s' as interval\n", argv[i]); | ||
| 82 | exit(5); | ||
| 83 | } | ||
| 84 | if (start < 0) { | ||
| 85 | fprintf(stderr, "interval %s: must not start before zero\n", argv[i]); | ||
| 86 | exit(5); | ||
| 87 | } | ||
| 88 | if (end <= start) { | ||
| 89 | fprintf(stderr, "interval %s: end before start\n", argv[i]); | ||
| 90 | exit(5); | ||
| 91 | } | ||
| 92 | |||
| 93 | slots[i].start = ms2ns(start); | ||
| 94 | slots[i].end = ms2ns(end); | ||
| 95 | |||
| 96 | if (i > 0 && slots[i - 1].end >= slots[i].start) { | ||
| 97 | fprintf(stderr, "interval %s: overlaps with previous interval\n", argv[i]); | ||
| 98 | exit(5); | ||
| 99 | } | ||
| 100 | |||
| 101 | if (slots[i].end >= major_cycle) { | ||
| 102 | fprintf(stderr, "interval %s: exceeds major cycle length\n", argv[i]); | ||
| 103 | exit(5); | ||
| 104 | } | ||
| 105 | |||
| 106 | (*num_intervals)++; | ||
| 107 | } | ||
| 108 | |||
| 109 | return slots; | ||
| 110 | } | ||
| 111 | |||
| 112 | #define OPTSTR "n:a:r:t:c:b:p:d:o:q:m:h" | ||
| 113 | |||
| 114 | int main(int argc, char** argv) | ||
| 115 | { | ||
| 116 | int ret, opt; | ||
| 117 | double budget_ms, period_ms, offset_ms, deadline_ms, major_cycle_ms; | ||
| 118 | int create_new = 0; | ||
| 119 | int attach_pid = 0; | ||
| 120 | int res_type = SPORADIC_POLLING; | ||
| 121 | |||
| 122 | struct reservation_config config; | ||
| 123 | |||
| 124 | /* Reasonable defaults */ | ||
| 125 | offset_ms = 0; | ||
| 126 | deadline_ms = 0; | ||
| 127 | budget_ms = 10; | ||
| 128 | period_ms = 100; | ||
| 129 | major_cycle_ms = 1000; | ||
| 130 | |||
| 131 | config.id = 0; | ||
| 132 | config.priority = LITMUS_NO_PRIORITY; /* use EDF by default */ | ||
| 133 | config.cpu = 0; | ||
| 134 | |||
| 135 | while ((opt = getopt(argc, argv, OPTSTR)) != -1) { | ||
| 136 | switch (opt) { | ||
| 137 | case 'b': | ||
| 138 | budget_ms = atof(optarg); | ||
| 139 | break; | ||
| 140 | case 'p': | ||
| 141 | period_ms = atof(optarg); | ||
| 142 | break; | ||
| 143 | case 'd': | ||
| 144 | deadline_ms = atof(optarg); | ||
| 145 | if (deadline_ms <= 0) { | ||
| 146 | usage("The relative deadline must be a positive" | ||
| 147 | " number."); | ||
| 148 | } | ||
| 149 | break; | ||
| 150 | case 'o': | ||
| 151 | offset_ms = atof(optarg); | ||
| 152 | break; | ||
| 153 | case 'm': | ||
| 154 | major_cycle_ms = atof(optarg); | ||
| 155 | break; | ||
| 156 | |||
| 157 | case 'q': | ||
| 158 | config.priority = atoi(optarg); | ||
| 159 | break; | ||
| 160 | case 'c': | ||
| 161 | config.cpu = atoi(optarg); | ||
| 162 | break; | ||
| 163 | |||
| 164 | case 'n': | ||
| 165 | create_new = 1; | ||
| 166 | config.id = atoi(optarg); | ||
| 167 | break; | ||
| 168 | case 'a': | ||
| 169 | attach_pid = atoi(optarg); | ||
| 170 | if (!attach_pid) | ||
| 171 | usage("-a: invalid PID"); | ||
| 172 | break; | ||
| 173 | |||
| 174 | case 'r': | ||
| 175 | config.id = atoi(optarg); | ||
| 176 | break; | ||
| 177 | |||
| 178 | case 't': | ||
| 179 | if (strcmp(optarg, "polling-periodic") == 0) { | ||
| 180 | res_type = PERIODIC_POLLING; | ||
| 181 | } else if (strcmp(optarg, "polling-sporadic") == 0) { | ||
| 182 | res_type = SPORADIC_POLLING; | ||
| 183 | } else if (strcmp(optarg, "table-driven") == 0) { | ||
| 184 | res_type = TABLE_DRIVEN; | ||
| 185 | } else { | ||
| 186 | usage("Unknown reservation type."); | ||
| 187 | } | ||
| 188 | break; | ||
| 189 | |||
| 190 | case 'h': | ||
| 191 | usage(""); | ||
| 192 | break; | ||
| 193 | case ':': | ||
| 194 | usage("Argument missing."); | ||
| 195 | break; | ||
| 196 | case '?': | ||
| 197 | default: | ||
| 198 | usage("Bad argument."); | ||
| 199 | break; | ||
| 200 | } | ||
| 201 | } | ||
| 202 | |||
| 203 | if (res_type != TABLE_DRIVEN) { | ||
| 204 | config.polling_params.budget = ms2ns(budget_ms); | ||
| 205 | config.polling_params.period = ms2ns(period_ms); | ||
| 206 | config.polling_params.offset = ms2ns(offset_ms); | ||
| 207 | config.polling_params.relative_deadline = ms2ns(deadline_ms); | ||
| 208 | if (config.polling_params.budget > config.polling_params.period) { | ||
| 209 | usage("The budget must not exceed the period."); | ||
| 210 | } | ||
| 211 | } else { | ||
| 212 | config.table_driven_params.major_cycle_length = ms2ns(major_cycle_ms); | ||
| 213 | argc -= optind; | ||
| 214 | argv += optind; | ||
| 215 | config.table_driven_params.intervals = parse_td_intervals( | ||
| 216 | argc, argv, &config.table_driven_params.num_intervals, | ||
| 217 | config.table_driven_params.major_cycle_length); | ||
| 218 | if (!config.table_driven_params.num_intervals) | ||
| 219 | usage("Table-driven reservations require at least one interval to be specified."); | ||
| 220 | } | ||
| 221 | |||
| 222 | if (create_new) { | ||
| 223 | ret = reservation_create(res_type, &config); | ||
| 224 | if (ret < 0) { | ||
| 225 | fprintf(stderr, "failed to create reservation %u (%m)\n", | ||
| 226 | config.id); | ||
| 227 | exit(1); | ||
| 228 | } | ||
| 229 | } | ||
| 230 | |||
| 231 | if (attach_pid) | ||
| 232 | attach_task(attach_pid, &config); | ||
| 233 | |||
| 234 | return 0; | ||
| 235 | } | ||
