Skip to content

Commit b4a0201

Browse files
committed
Refactor QEMU handling and improve cgroup management; update subproject reference
1 parent afcefd6 commit b4a0201

6 files changed

Lines changed: 77 additions & 76 deletions

File tree

‎.vscode/extensions.json‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
{
22
"recommendations": [
33
"llvm-vs-code-extensions.vscode-clangd",
4-
"charliermarsh.ruff",
5-
"eamodio.gitlens"
4+
"charliermarsh.ruff"
65
]
76
}

‎crates/core/src/qemu.rs‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,13 @@ impl Boot {
167167
]
168168
.map(String::from),
169169
);
170+
// Under TCG a virtqueue kick on virt's MMIO transport is cheaper handled on the vCPU than
171+
// hopped through an eventfd and the main loop (native tick -17%; in the browser, where an
172+
// eventfd is a JS pipe on the page's thread, -25%). KVM keeps it, and x86's PCI transport
173+
// is unmeasured.
174+
if !kvm_available() && self.arch.virtio_serial == "virtio-serial-device" {
175+
argv.extend(["-global".into(), "virtio-mmio.ioeventfd=off".into()]);
176+
}
170177
// A snapshot restores RAM, so the kernel, initrd and command line it was booted with are
171178
// not loaded again; the machine and devices must match what it was taken on.
172179
match &self.snapshot {
@@ -322,9 +329,12 @@ mod tests {
322329
// the accelerator is the host's: KVM where /dev/kvm is usable
323330
assert!(a.starts_with("-machine virt -accel "), "{a}");
324331
assert!(
325-
a.contains("-smp 3 -m 64M -kernel /kernel -initrd /rootfs.cpio -append rw nokaslr"),
332+
a.contains("-smp 3 -m 64M -kernel /kernel -initrd /rootfs.cpio -append rw nokaslr")
333+
|| a.contains("-smp 3 -m 64M -global virtio-mmio.ioeventfd=off -kernel /kernel -initrd /rootfs.cpio -append rw nokaslr"),
326334
"{a}"
327335
);
336+
// and TCG, always the browser's, handles a virtqueue kick on the vCPU
337+
assert_eq!(a.contains("ioeventfd=off"), !kvm_available(), "{a}");
328338
// the browser's two-way channels are the wasm32 build's (served()); run.mjs --check boots them
329339
assert!(a.contains("-device virtio-serial-device,id=vs0 -device virtconsole,bus=vs0.0,nr=0,chardev=port -chardev file,id=console,path=/kernel.log -chardev "), "{a}");
330340
}
@@ -355,6 +365,7 @@ mod tests {
355365
|| a.contains("-accel tcg,tb-size=64,thread=multi -cpu "),
356366
"{a}"
357367
);
368+
assert!(!a.contains("ioeventfd"), "{a}"); // x86's PCI transport keeps it
358369
assert!(a.ends_with("-chardev file,id=console,path=/r/kernel.log -chardev socket,path=/r/kstep.sock,server=on,wait=off,logfile=/r/kstep.jsonl,id=port -chardev socket,path=/r/monitor.sock,server=on,wait=off,id=monitor -monitor chardev:monitor"), "{a}");
359370
assert!(!a.contains("memory-backend"));
360371
boot.ram_file = Some("/dev/shm/x".into());

‎kmod/driver.h‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,13 @@ void kstep_task_chan_write(struct task_struct *p);
7777

7878
// kernel.c
7979
int kstep_write(const char *path, const char *buf, size_t size);
80-
int kstep_read(const char *path, char *buf, size_t size);
8180
int kstep_mkdir(const char *dir);
8281
void kstep_sysctl_write(const char *name, const char *fmt, ...);
8382
void kstep_sched_feat_write(const char *fmt, ...);
8483
void kstep_sched_feat_enable(const char *name);
8584
void kstep_sched_feat_disable(const char *name);
8685
int kstep_cgroup_write(const char *name, const char *filename, const char *fmt,
8786
...);
88-
int kstep_cgroup_read(const char *name, const char *filename, char *buf, size_t size);
8987
bool kstep_cgroup_exists(const char *name);
9088
int kstep_cgroup_create(const char *name);
9189
void kstep_cgroup_destroy(const char *name);

‎kmod/kernel.c‎

Lines changed: 6 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -29,37 +29,18 @@ int kstep_write(const char *path, const char *buf, size_t size) {
2929
loff_t pos = 0;
3030
ssize_t ret = kernel_write(file, buf, size, &pos);
3131
filp_close(file, NULL);
32-
if (ret < 0) {
33-
TRACE_INFO("write %s failed: %ld", path, ret);
34-
return ret;
35-
}
36-
return 0;
37-
}
38-
39-
// The counterpart of kstep_write, for the cgroup files kSTEP reports back (shm.c). Returns the
40-
// bytes read, with the trailing newline cgroupfs writes stripped, or a negative errno.
41-
int kstep_read(const char *path, char *buf, size_t size) {
42-
struct file *file = filp_open(path, O_RDONLY, 0);
43-
loff_t pos = 0;
44-
ssize_t ret;
45-
46-
if (IS_ERR(file))
47-
return PTR_ERR(file);
48-
ret = kernel_read(file, buf, size - 1, &pos);
49-
filp_close(file, NULL);
5032
// filp_close() only queues the release: fput() adds it to this task's task_work, which the
5133
// kernel runs on the way back to user mode. kSTEP's controller never goes back -- it is the
52-
// module init, looping inside finit_module for the whole session -- so without this every read
53-
// leaks its struct file and the seq_file buffer under it, about 4.6 KB a time.
34+
// module init, looping inside finit_module for the whole session -- so without this every
35+
// write leaks its struct file.
5436
KSYM_IMPORT(task_work_run);
5537
if (KSYM_task_work_run)
5638
KSYM_task_work_run();
57-
if (ret < 0)
39+
if (ret < 0) {
40+
TRACE_INFO("write %s failed: %ld", path, ret);
5841
return ret;
59-
while (ret > 0 && buf[ret - 1] == '\n')
60-
ret--;
61-
buf[ret] = '\0';
62-
return ret;
42+
}
43+
return 0;
6344
}
6445

6546
int kstep_mkdir(const char *dir) {
@@ -286,14 +267,6 @@ void kstep_cgroup_init(void) {
286267
KSYM_rcu_sync_enter(&cpuset_rwsem->rss);
287268
}
288269

289-
int kstep_cgroup_read(const char *name, const char *filename, char *buf, size_t size) {
290-
char path[MAX_PATH_LENGTH];
291-
292-
if (scnprintf(path, sizeof(path), CGROUP_ROOT "%s/%s", name, filename) >= sizeof(path))
293-
return -ENAMETOOLONG;
294-
return kstep_read(path, buf, size);
295-
}
296-
297270
bool kstep_cgroup_exists(const char *name) {
298271
char path[MAX_PATH_LENGTH];
299272
struct path p;

‎kmod/shm.c‎

Lines changed: 57 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
#include <linux/cgroup.h>
44
#include <linux/cpumask.h>
55
#include <linux/gfp.h>
6-
#include <linux/kstrtox.h>
76
#include <linux/sched/rt.h>
87
#include <linux/version.h>
98
#include <asm/io.h>
@@ -143,26 +142,51 @@ static struct kstep_shm_rt_entity shm_rt_entity(struct task_struct *p, u32 task,
143142
return out;
144143
}
145144

146-
// The cgroup's group entities, one record per test CPU, appended to `ents` -- the level the pick
147-
// happens at, which nothing else reports: a task's own counters are kept in its group's queue, so
148-
// four equal tasks split two cgroups' halves without anything in the task table saying why. The
149-
// cpu controller's css is taken from the cgroup rather than the walk's, which is cgroup->self.
150-
// Called under RCU; the entities are stable because the isolated CPUs are held for the whole
151-
// command.
152-
static void shm_group_entities(u32 cgroup, struct cgroup *cgrp, struct kstep_shm_entity *ents, u32 *nents) {
153-
#ifdef CONFIG_FAIR_GROUP_SCHED
145+
// The cgroup's task group, the cpu controller's state; NULL where the controller is off. The css
146+
// is taken from the cgroup rather than the walk's, which is cgroup->self. Under RCU.
147+
static struct task_group *shm_tg(struct cgroup *cgrp) {
148+
#ifdef CONFIG_CGROUP_SCHED
154149
struct cgroup_subsys_state *css = rcu_dereference(cgrp->subsys[cpu_cgrp_id]);
155-
struct task_group *tg;
156150

157151
if (!css)
158-
return;
152+
return NULL;
159153
// css_tg() is private to core.c before 6.12, when sched_ext moved it into sched.h
160154
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
161-
tg = css_tg(css);
155+
return css_tg(css);
162156
#else
163-
tg = container_of(css, struct task_group, css);
157+
return container_of(css, struct task_group, css);
164158
#endif
165-
if (!tg->parent) // the root task group owns no entity: its cfs_rq is the runqueue's own
159+
#else
160+
return NULL;
161+
#endif
162+
}
163+
164+
#ifdef CONFIG_CPUSETS
165+
// The head of struct cpuset, private to kernel/cgroup/cpuset.c (cpuset-internal.h on newer
166+
// kernels) and the same on every kernel kSTEP builds: enough to read the mask that
167+
// cpuset.cpus.effective shows without opening the file, which under emulation costs more than
168+
// the rest of an update together.
169+
struct kstep_cpuset_head {
170+
struct cgroup_subsys_state css;
171+
unsigned long flags;
172+
cpumask_var_t cpus_allowed;
173+
nodemask_t mems_allowed;
174+
cpumask_var_t effective_cpus;
175+
};
176+
#if __has_include(<kernel/cgroup/cpuset-internal.h>)
177+
#include <kernel/cgroup/cpuset-internal.h>
178+
static_assert(offsetof(struct kstep_cpuset_head, effective_cpus) == offsetof(struct cpuset, effective_cpus));
179+
#endif
180+
#endif
181+
182+
// The cgroup's group entities, one record per test CPU, appended to `ents` -- the level the pick
183+
// happens at, which nothing else reports: a task's own counters are kept in its group's queue, so
184+
// four equal tasks split two cgroups' halves without anything in the task table saying why.
185+
// Called under RCU; the entities are stable because the isolated CPUs are held for the whole
186+
// command.
187+
static void shm_group_entities(u32 cgroup, struct task_group *tg, struct kstep_shm_entity *ents, u32 *nents) {
188+
#ifdef CONFIG_FAIR_GROUP_SCHED
189+
if (!tg || !tg->parent) // the root task group owns no entity: its cfs_rq is the runqueue's own
166190
return;
167191
for_each_test_cpu(cpu) {
168192
struct sched_entity *se = tg->se[cpu];
@@ -181,12 +205,9 @@ static void shm_group_entities(u32 cgroup, struct cgroup *cgrp, struct kstep_shm
181205
#endif
182206
}
183207

184-
// The cgroups: the root and every live cgroup under it, in tree order, collected into `out`.
185-
// The tree walk needs RCU and the two control files sleep, so the walk takes the paths first and
186-
// the files are read after it. Reading the files is how the kernel's own interface reports these
187-
// values -- the cpu controller's weight could come from the task_group, but a cpuset's effective
188-
// mask lives in struct cpuset, which is private to kernel/cgroup/cpuset.c. A cgroup with no cpu
189-
// controller (the root among them, which has no cpu.weight) keeps weight 0.
208+
// The cgroups: the root and every live cgroup under it, in tree order, collected into `out` with
209+
// what their cpu.weight and cpuset.cpus.effective files show, read off the controllers' state. A
210+
// cgroup without the controller (the root among them, which has no cpu.weight) keeps 0.
190211
static u32 shm_collect_cgroups(struct kstep_shm_cgroup *out, struct kstep_shm_entity *ents, u32 *nents) {
191212
struct cgroup_subsys_state *pos;
192213
struct cgroup *root;
@@ -198,34 +219,33 @@ static u32 shm_collect_cgroups(struct kstep_shm_cgroup *out, struct kstep_shm_en
198219
return 0;
199220
rcu_read_lock();
200221
css_for_each_descendant_pre(pos, &root->self) {
222+
struct task_group *tg = shm_tg(pos->cgroup);
223+
201224
if (n == KSTEP_SHM_CGROUPS)
202225
break;
203226
if (!(pos->flags & CSS_ONLINE))
204227
continue;
205228
memset(&out[n], 0, sizeof(out[n]));
206229
if (cgroup_path(pos->cgroup, out[n].path, sizeof(out[n].path)) < 0)
207230
continue; // a path that does not fit is one no kSTEP driver made
208-
shm_group_entities(n, pos->cgroup, ents, nents);
231+
#ifdef CONFIG_FAIR_GROUP_SCHED
232+
if (tg && tg->parent) // cpu.weight's own conversion of the shares
233+
out[n].weight = clamp_t(u64, DIV_ROUND_CLOSEST_ULL(scale_load_down(tg->shares) * CGROUP_WEIGHT_DFL, 1024),
234+
CGROUP_WEIGHT_MIN, CGROUP_WEIGHT_MAX);
235+
#endif
236+
#ifdef CONFIG_CPUSETS
237+
{
238+
struct cgroup_subsys_state *cs = rcu_dereference(pos->cgroup->subsys[cpuset_cgrp_id]);
239+
240+
if (cs)
241+
out[n].cpus = cpumask_bits(container_of(cs, struct kstep_cpuset_head, css)->effective_cpus)[0];
242+
}
243+
#endif
244+
shm_group_entities(n, tg, ents, nents);
209245
n++;
210246
}
211247
rcu_read_unlock();
212248
cgroup_put(root);
213-
214-
for (u32 i = 0; i < n; i++) {
215-
const char *name = out[i].path[1] ? out[i].path : ""; // the root's files live at CGROUP_ROOT
216-
char buf[64];
217-
cpumask_var_t mask;
218-
219-
if (kstep_cgroup_read(name, "cpu.weight", buf, sizeof(buf)) > 0 &&
220-
kstrtouint(buf, 10, &out[i].weight))
221-
out[i].weight = 0; // a value that does not parse is one this kernel does not report
222-
if (kstep_cgroup_read(name, "cpuset.cpus.effective", buf, sizeof(buf)) > 0 &&
223-
zalloc_cpumask_var(&mask, GFP_KERNEL)) {
224-
if (!cpulist_parse(buf, mask))
225-
out[i].cpus = cpumask_bits(mask)[0];
226-
free_cpumask_var(mask);
227-
}
228-
}
229249
return n;
230250
}
231251

0 commit comments

Comments
 (0)