Skip to content

Commit 0650387

Browse files
jmberg-intelrichardweinberger
authored andcommitted
um: Support time travel mode
Sometimes it can be useful to run with "time travel" inside the UML instance, for example for testing. For example, some tests for the wireless subsystem and userspace are based on hwsim, a virtual wireless adapter. Some tests can take a long time to run because they e.g. wait for 120 seconds to elapse for some regulatory checks. This obviously goes faster if it need not actually wait that long, but time inside the test environment just "bumps up" when there's nothing to do. Add CONFIG_UML_TIME_TRAVEL_SUPPORT to enable code to support such modes at runtime, selected on the command line: * just "time-travel", in which time inside the UML instance can move faster than real time, if there's nothing to do * "time-travel=inf-cpu" in which time also moves slower and any CPU processing takes no time at all, which allows to implement consistent behaviour regardless of host CPU load (or speed) or debug overhead. An additional "time-travel-start=<seconds>" parameter is also supported in this case to start the wall clock at this time (in unix epoch). With this enabled, the test mentioned above goes from a runtime of about 140 seconds (with startup overhead and all) to being CPU bound and finishing in 15 seconds (on my slow laptop). Signed-off-by: Johannes Berg <[email protected]> Signed-off-by: Richard Weinberger <[email protected]>
1 parent c7c6f3b commit 0650387

File tree

5 files changed

+233
-7
lines changed

5 files changed

+233
-7
lines changed

arch/um/Kconfig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,18 @@ config SECCOMP
184184

185185
If unsure, say Y.
186186

187+
config UML_TIME_TRAVEL_SUPPORT
188+
bool
189+
prompt "Support time-travel mode (e.g. for test execution)"
190+
help
191+
Enable this option to support time travel inside the UML instance.
192+
193+
After enabling this option, two modes are accessible at runtime
194+
(selected by the kernel command line), see the kernel's command-
195+
line help for more details.
196+
197+
It is safe to say Y, but you probably don't need this.
198+
187199
endmenu
188200

189201
source "arch/um/drivers/Kconfig"

arch/um/include/shared/timer-internal.h

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,50 @@
1010
#define TIMER_MULTIPLIER 256
1111
#define TIMER_MIN_DELTA 500
1212

13+
enum time_travel_mode {
14+
TT_MODE_OFF,
15+
TT_MODE_BASIC,
16+
TT_MODE_INFCPU,
17+
};
18+
19+
enum time_travel_timer_mode {
20+
TT_TMR_DISABLED,
21+
TT_TMR_ONESHOT,
22+
TT_TMR_PERIODIC,
23+
};
24+
25+
#ifdef CONFIG_UML_TIME_TRAVEL_SUPPORT
26+
extern enum time_travel_mode time_travel_mode;
27+
extern unsigned long long time_travel_time;
28+
extern enum time_travel_timer_mode time_travel_timer_mode;
29+
extern unsigned long long time_travel_timer_expiry;
30+
extern unsigned long long time_travel_timer_interval;
31+
32+
static inline void time_travel_set_time(unsigned long long ns)
33+
{
34+
time_travel_time = ns;
35+
}
36+
37+
static inline void time_travel_set_timer(enum time_travel_timer_mode mode,
38+
unsigned long long expiry)
39+
{
40+
time_travel_timer_mode = mode;
41+
time_travel_timer_expiry = expiry;
42+
}
43+
#else
44+
#define time_travel_mode TT_MODE_OFF
45+
#define time_travel_time 0
46+
#define time_travel_timer_expiry 0
47+
#define time_travel_timer_interval 0
48+
49+
static inline void time_travel_set_time(unsigned long long ns)
50+
{
51+
}
52+
53+
static inline void time_travel_set_timer(enum time_travel_timer_mode mode,
54+
unsigned long long expiry)
55+
{
56+
}
57+
#endif
58+
1359
#endif

arch/um/kernel/process.c

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,10 +203,50 @@ void initial_thread_cb(void (*proc)(void *), void *arg)
203203
kmalloc_ok = save_kmalloc_ok;
204204
}
205205

206+
static void time_travel_sleep(unsigned long long duration)
207+
{
208+
unsigned long long next = time_travel_time + duration;
209+
210+
if (time_travel_mode != TT_MODE_INFCPU)
211+
os_timer_disable();
212+
213+
if (time_travel_timer_mode != TT_TMR_DISABLED ||
214+
time_travel_timer_expiry < next) {
215+
if (time_travel_timer_mode == TT_TMR_ONESHOT)
216+
time_travel_timer_mode = TT_TMR_DISABLED;
217+
/*
218+
* time_travel_time will be adjusted in the timer
219+
* IRQ handler so it works even when the signal
220+
* comes from the OS timer
221+
*/
222+
deliver_alarm();
223+
} else {
224+
time_travel_set_time(next);
225+
}
226+
227+
if (time_travel_mode != TT_MODE_INFCPU) {
228+
if (time_travel_timer_mode == TT_TMR_PERIODIC)
229+
os_timer_set_interval(time_travel_timer_interval);
230+
else if (time_travel_timer_mode == TT_TMR_ONESHOT)
231+
os_timer_one_shot(time_travel_timer_expiry - next);
232+
}
233+
}
234+
235+
static void um_idle_sleep(void)
236+
{
237+
unsigned long long duration = UM_NSEC_PER_SEC;
238+
239+
if (time_travel_mode != TT_MODE_OFF) {
240+
time_travel_sleep(duration);
241+
} else {
242+
os_idle_sleep(duration);
243+
}
244+
}
245+
206246
void arch_cpu_idle(void)
207247
{
208248
cpu_tasks[current_thread_info()->cpu].pid = os_getpid();
209-
os_idle_sleep(UM_NSEC_PER_SEC);
249+
um_idle_sleep();
210250
local_irq_enable();
211251
}
212252

arch/um/kernel/skas/syscall.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,23 @@
1010
#include <sysdep/ptrace.h>
1111
#include <sysdep/ptrace_user.h>
1212
#include <sysdep/syscalls.h>
13+
#include <shared/timer-internal.h>
1314

1415
void handle_syscall(struct uml_pt_regs *r)
1516
{
1617
struct pt_regs *regs = container_of(r, struct pt_regs, regs);
1718
int syscall;
1819

20+
/*
21+
* If we have infinite CPU resources, then make every syscall also a
22+
* preemption point, since we don't have any other preemption in this
23+
* case, and kernel threads would basically never run until userspace
24+
* went to sleep, even if said userspace interacts with the kernel in
25+
* various ways.
26+
*/
27+
if (time_travel_mode == TT_MODE_INFCPU)
28+
schedule();
29+
1930
/* Initialize the syscall number and default return value. */
2031
UPT_SYSCALL_NR(r) = PT_SYSCALL_NR(r->gp);
2132
PT_REGS_SET_SYSCALL_RETURN(regs, -ENOSYS);

arch/um/kernel/time.c

Lines changed: 123 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,38 +19,77 @@
1919
#include <kern_util.h>
2020
#include <os.h>
2121
#include <timer-internal.h>
22+
#include <shared/init.h>
23+
24+
#ifdef CONFIG_UML_TIME_TRAVEL_SUPPORT
25+
enum time_travel_mode time_travel_mode;
26+
unsigned long long time_travel_time;
27+
enum time_travel_timer_mode time_travel_timer_mode;
28+
unsigned long long time_travel_timer_expiry;
29+
unsigned long long time_travel_timer_interval;
30+
31+
static bool time_travel_start_set;
32+
static unsigned long long time_travel_start;
33+
#else
34+
#define time_travel_start_set 0
35+
#define time_travel_start 0
36+
#endif
2237

2338
void timer_handler(int sig, struct siginfo *unused_si, struct uml_pt_regs *regs)
2439
{
2540
unsigned long flags;
2641

42+
if (time_travel_mode != TT_MODE_OFF)
43+
time_travel_set_time(time_travel_timer_expiry);
44+
2745
local_irq_save(flags);
2846
do_IRQ(TIMER_IRQ, regs);
2947
local_irq_restore(flags);
3048
}
3149

3250
static int itimer_shutdown(struct clock_event_device *evt)
3351
{
34-
os_timer_disable();
52+
if (time_travel_mode != TT_MODE_OFF)
53+
time_travel_set_timer(TT_TMR_DISABLED, 0);
54+
55+
if (time_travel_mode != TT_MODE_INFCPU)
56+
os_timer_disable();
57+
3558
return 0;
3659
}
3760

3861
static int itimer_set_periodic(struct clock_event_device *evt)
3962
{
40-
os_timer_set_interval(NSEC_PER_SEC / HZ);
63+
unsigned long long interval = NSEC_PER_SEC / HZ;
64+
65+
if (time_travel_mode != TT_MODE_OFF)
66+
time_travel_set_timer(TT_TMR_PERIODIC,
67+
time_travel_time + interval);
68+
69+
if (time_travel_mode != TT_MODE_INFCPU)
70+
os_timer_set_interval(interval);
71+
4172
return 0;
4273
}
4374

4475
static int itimer_next_event(unsigned long delta,
4576
struct clock_event_device *evt)
4677
{
47-
return os_timer_one_shot(delta + 1);
78+
delta += 1;
79+
80+
if (time_travel_mode != TT_MODE_OFF)
81+
time_travel_set_timer(TT_TMR_ONESHOT,
82+
time_travel_time + delta);
83+
84+
if (time_travel_mode != TT_MODE_INFCPU)
85+
return os_timer_one_shot(delta);
86+
87+
return 0;
4888
}
4989

5090
static int itimer_one_shot(struct clock_event_device *evt)
5191
{
52-
os_timer_one_shot(1);
53-
return 0;
92+
return itimer_next_event(0, evt);
5493
}
5594

5695
static struct clock_event_device timer_clockevent = {
@@ -87,6 +126,17 @@ static irqreturn_t um_timer(int irq, void *dev)
87126

88127
static u64 timer_read(struct clocksource *cs)
89128
{
129+
if (time_travel_mode != TT_MODE_OFF) {
130+
/*
131+
* We make reading the timer cost a bit so that we don't get
132+
* stuck in loops that expect time to move more than the
133+
* exact requested sleep amount, e.g. python's socket server,
134+
* see https://bugs.python.org/issue37026.
135+
*/
136+
time_travel_set_time(time_travel_time + TIMER_MULTIPLIER);
137+
return time_travel_time / TIMER_MULTIPLIER;
138+
}
139+
90140
return os_nsecs() / TIMER_MULTIPLIER;
91141
}
92142

@@ -123,7 +173,12 @@ static void __init um_timer_setup(void)
123173

124174
void read_persistent_clock64(struct timespec64 *ts)
125175
{
126-
long long nsecs = os_persistent_clock_emulation();
176+
long long nsecs;
177+
178+
if (time_travel_start_set)
179+
nsecs = time_travel_start + time_travel_time;
180+
else
181+
nsecs = os_persistent_clock_emulation();
127182

128183
set_normalized_timespec64(ts, nsecs / NSEC_PER_SEC,
129184
nsecs % NSEC_PER_SEC);
@@ -134,3 +189,65 @@ void __init time_init(void)
134189
timer_set_signal_handler();
135190
late_time_init = um_timer_setup;
136191
}
192+
193+
#ifdef CONFIG_UML_TIME_TRAVEL_SUPPORT
194+
unsigned long calibrate_delay_is_known(void)
195+
{
196+
if (time_travel_mode == TT_MODE_INFCPU)
197+
return 1;
198+
return 0;
199+
}
200+
201+
int setup_time_travel(char *str)
202+
{
203+
if (strcmp(str, "=inf-cpu") == 0) {
204+
time_travel_mode = TT_MODE_INFCPU;
205+
timer_clockevent.name = "time-travel-timer-infcpu";
206+
timer_clocksource.name = "time-travel-clock";
207+
return 1;
208+
}
209+
210+
if (!*str) {
211+
time_travel_mode = TT_MODE_BASIC;
212+
timer_clockevent.name = "time-travel-timer";
213+
timer_clocksource.name = "time-travel-clock";
214+
return 1;
215+
}
216+
217+
return -EINVAL;
218+
}
219+
220+
__setup("time-travel", setup_time_travel);
221+
__uml_help(setup_time_travel,
222+
"time-travel\n"
223+
"This option just enables basic time travel mode, in which the clock/timers\n"
224+
"inside the UML instance skip forward when there's nothing to do, rather than\n"
225+
"waiting for real time to elapse. However, instance CPU speed is limited by\n"
226+
"the real CPU speed, so e.g. a 10ms timer will always fire after ~10ms wall\n"
227+
"clock (but quicker when there's nothing to do).\n"
228+
"\n"
229+
"time-travel=inf-cpu\n"
230+
"This enables time travel mode with infinite processing power, in which there\n"
231+
"are no wall clock timers, and any CPU processing happens - as seen from the\n"
232+
"guest - instantly. This can be useful for accurate simulation regardless of\n"
233+
"debug overhead, physical CPU speed, etc. but is somewhat dangerous as it can\n"
234+
"easily lead to getting stuck (e.g. if anything in the system busy loops).\n");
235+
236+
int setup_time_travel_start(char *str)
237+
{
238+
int err;
239+
240+
err = kstrtoull(str, 0, &time_travel_start);
241+
if (err)
242+
return err;
243+
244+
time_travel_start_set = 1;
245+
return 1;
246+
}
247+
248+
__setup("time-travel-start", setup_time_travel_start);
249+
__uml_help(setup_time_travel_start,
250+
"time-travel-start=<seconds>\n"
251+
"Configure the UML instance's wall clock to start at this value rather than\n"
252+
"the host's wall clock at the time of UML boot.\n");
253+
#endif

0 commit comments

Comments
 (0)