Skip to content

Commit e895724

Browse files
committed
Manage ticks to suppress RCU CPU stall warning
Since the emulator currently operates using sequential emulation, the execution time for the boot process is relatively long, which can result in the generation of RCU CPU stall warnings. To address this issue, there are several potential solutions: 1. Scale the frequency to slow down emulator time during the boot process, thereby eliminating RCU CPU stall warnings. 2. During the boot process, avoid using 'clock_gettime' to update ticks and instead manage the tick increment relationship manually. 3. Implement multi-threaded emulation to accelerate the emulator's execution speed. For the third point, while implementing multi-threaded emulation can significantly accelerate the emulator's execution speed, it cannot guarantee that this issue will not reappear as the number of cores increases in the future. Therefore, a better approach is to use methods 1 and 2 to allow the emulator to set an expected time for completing the boot process. The scale method requires three pieces of information to be implemented: - the cost of 'semu_timer_clocksource' - percentage of 'semu_timer_clocksource' within the boot process - the number of times 'semu_timer_clocksource' is called. In contrast, the increment method only requires the third information to be implemented, making its implementation simpler. Furthermore, through statistical analysis, we found that the other two values (cost and percentage) exhibit different distributions across different environments. Therefore, using the scale method would require an additional profiling step when the emulator starts, adding complexity to the implementation. Finally, since the increment method does not obtain real-time timestamps during the boot process, its overhead is lower compared to the scale method. Because 'semu_timer_clocksource' is called frequently, this reduction in overhead accumulates and results in a noticeable performance improvement. Therefore, this commit opts for the increment method to address this issue. This commit divides time into emulator time and real time. During the boot process, the timer manage the ticks increment to slow down the growth of emulator time, eliminating RCU CPU stall warnings. After the boot process is complete, the growth of emulator time aligns with real time. According to Using RCU’s CPU Stall Detector [1], the grace period for RCU CPU stalls is typically set to 21 seconds. By dividing this value by two as the expected completion time, we can provide a sufficient buffer to reduce the impact of errors and avoid RCU CPU stall warnings. By statistic, the number of times 'semu_timer_clocksource' called is approximately 'SMP count * 2.15 * 1e8'. By the time the boot process is completed, the emulator will have a total of 'boot seconds * frequency' ticks. Therefore, each time, '(boot seconds * frequency) / (2.15 * 1e8 * SMP count)' ticks need to be added. Close #51 [1] https://docs.kernel.org/RCU/stallwarn.html#config-rcu-cpu-stall-timeout
1 parent 2a521f6 commit e895724

File tree

5 files changed

+103
-21
lines changed

5 files changed

+103
-21
lines changed

Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ E :=
133133
S := $E $E
134134

135135
SMP ?= 1
136+
CFLAGS += -D SEMU_BOOT_TARGET_TIME=10
136137
.PHONY: riscv-harts.dtsi
137138
riscv-harts.dtsi:
138139
$(Q)python3 scripts/gen-hart-dts.py $@ $(SMP) $(CLOCK_FREQ)

main.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -678,7 +678,7 @@ static int semu_init(emu_state_t *emu, int argc, char **argv)
678678
virtio_rng_init();
679679
#endif
680680
/* Set up ACLINT */
681-
semu_timer_init(&emu->mtimer.mtime, CLOCK_FREQ);
681+
semu_timer_init(&emu->mtimer.mtime, CLOCK_FREQ, hart_count);
682682
emu->mtimer.mtimecmp = calloc(vm->n_hart, sizeof(uint64_t));
683683
emu->mswi.msip = calloc(vm->n_hart, sizeof(uint32_t));
684684
emu->sswi.ssip = calloc(vm->n_hart, sizeof(uint32_t));

riscv.c

+8
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,14 @@ static void op_sret(hart_t *vm)
382382
vm->s_mode = vm->sstatus_spp;
383383
vm->sstatus_sie = vm->sstatus_spie;
384384

385+
/* After the booting process is complete, initrd will be loaded. At this
386+
* point, the sytstem will switch to U mode for the first time. Therefore,
387+
* by checking whether the switch to U mode has already occurred, we can
388+
* determine if the boot process has been completed.
389+
*/
390+
if (!boot_complete && !vm->s_mode)
391+
boot_complete = true;
392+
385393
/* Reset stack */
386394
vm->sstatus_spp = false;
387395
vm->sstatus_spie = true;

utils.c

+81-18
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#include <stdbool.h>
12
#include <time.h>
23

34
#include "utils.h"
@@ -19,6 +20,10 @@
1920
#endif
2021
#endif
2122

23+
bool boot_complete = false;
24+
static double ticks_increment;
25+
static double boot_ticks;
26+
2227
/* Calculate "x * n / d" without unnecessary overflow or loss of precision.
2328
*
2429
* Reference:
@@ -32,35 +37,93 @@ static inline uint64_t mult_frac(uint64_t x, uint64_t n, uint64_t d)
3237
return q * n + r * n / d;
3338
}
3439

35-
void semu_timer_init(semu_timer_t *timer, uint64_t freq)
36-
{
37-
timer->freq = freq;
38-
semu_timer_rebase(timer, 0);
39-
}
40-
41-
static uint64_t semu_timer_clocksource(uint64_t freq)
40+
/* High-precision time measurement:
41+
* - POSIX systems: clock_gettime() for nanosecond precision
42+
* - macOS: mach_absolute_time() with timebase conversion
43+
* - Other platforms: time(0) with conversion to nanoseconds as fallback
44+
*
45+
* The platform-specific timing logic is now clearly separated: POSIX and macOS
46+
* implementations provide high-precision measurements, while the fallback path
47+
* uses time(0) for a coarser but portable approach.
48+
*/
49+
static inline uint64_t host_time_ns()
4250
{
4351
#if defined(HAVE_POSIX_TIMER)
44-
struct timespec t;
45-
clock_gettime(CLOCKID, &t);
46-
return t.tv_sec * freq + mult_frac(t.tv_nsec, freq, 1e9);
52+
struct timespec ts;
53+
clock_gettime(CLOCKID, &ts);
54+
return (uint64_t) ts.tv_sec * 1e9 + (uint64_t) ts.tv_nsec;
55+
4756
#elif defined(HAVE_MACH_TIMER)
48-
static mach_timebase_info_data_t t;
49-
if (t.denom == 0)
50-
(void) mach_timebase_info(&t);
51-
return mult_frac(mult_frac(mach_absolute_time(), t.numer, t.denom), freq,
52-
1e9);
57+
static mach_timebase_info_data_t ts = {0};
58+
if (ts.denom == 0)
59+
(void) mach_timebase_info(&ts);
60+
61+
uint64_t now = mach_absolute_time();
62+
/* convert to nanoseconds: (now * t.numer / t.denom) */
63+
return mult_frac(now, ts.numer, (uint64_t) ts.denom);
64+
5365
#else
54-
return time(0) * freq;
66+
/* Fallback to non-HRT calls time(0) in seconds => convert to ns. */
67+
time_t now_sec = time(0);
68+
return (uint64_t) now_sec * 1e9;
5569
#endif
5670
}
5771

72+
/* The function that returns the "emulator time" in ticks.
73+
*
74+
* Before the boot process is completed, the emulator manually manages the
75+
* growth of ticks to suppress RCU CPU stall warnings. After the boot process is
76+
* completed, the emulator switches back to the real-time timer, using an offset
77+
* bridging to ensure that the ticks of both timers remain consistent.
78+
*/
79+
static uint64_t semu_timer_clocksource(semu_timer_t *timer)
80+
{
81+
/* After boot process complete, the timer will switch to real time. Thus,
82+
* there is an offset between the real time and the emulator time.
83+
*
84+
* After switching to real time, the correct way to update time is to
85+
* calculate the increment of time. Then add it to the emulator time.
86+
*/
87+
static int64_t offset = 0;
88+
static bool first_switch = true;
89+
90+
if (!boot_complete) {
91+
boot_ticks += ticks_increment;
92+
return (uint64_t) boot_ticks;
93+
}
94+
95+
uint64_t real_ticks = mult_frac(host_time_ns(), timer->freq, 1e9);
96+
if (first_switch) {
97+
first_switch = false;
98+
99+
/* Calculate the offset between the real time and the emulator time */
100+
offset = (int64_t) (real_ticks - boot_ticks);
101+
}
102+
return (uint64_t) ((int64_t) real_ticks - offset);
103+
}
104+
105+
void semu_timer_init(semu_timer_t *timer, uint64_t freq, int n_harts)
106+
{
107+
timer->freq = freq;
108+
timer->begin = mult_frac(host_time_ns(), timer->freq, 1e9);
109+
boot_ticks = timer->begin; /* Initialize the fake ticks for boot process */
110+
111+
/* According to statistics, the number of times 'semu_timer_clocksource'
112+
* called is approximately 'SMP count * 2.15 * 1e8'. By the time the boot
113+
* process is completed, the emulator will have a total of 'boot seconds *
114+
* frequency' ticks. Therefore, each time, '(boot seconds * frequency) /
115+
* (2.15 * 1e8 * SMP count)' ticks need to be added.
116+
*/
117+
ticks_increment =
118+
(SEMU_BOOT_TARGET_TIME * CLOCK_FREQ) / (2.15 * 1e8 * n_harts);
119+
}
120+
58121
uint64_t semu_timer_get(semu_timer_t *timer)
59122
{
60-
return semu_timer_clocksource(timer->freq) - timer->begin;
123+
return semu_timer_clocksource(timer) - timer->begin;
61124
}
62125

63126
void semu_timer_rebase(semu_timer_t *timer, uint64_t time)
64127
{
65-
timer->begin = semu_timer_clocksource(timer->freq) - time;
128+
timer->begin = semu_timer_clocksource(timer) - time;
66129
}

utils.h

+12-2
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,23 @@
33
#include <stddef.h>
44
#include <stdint.h>
55

6+
/* To suppress RCU CPU stall warnings, the emulator provides a fake timer to
7+
* the Guest OS during the boot process. After the boot process is complete, the
8+
* emulator will switch to real-time timer.
9+
*
10+
* Since the Guest OS transitions to U mode for the first time when it loads the
11+
* initial user-mode process, we use this transition to determine whether the
12+
* boot process has completed.
13+
*/
14+
extern bool boot_complete;
15+
616
/* TIMER */
717
typedef struct {
818
uint64_t begin;
919
uint64_t freq;
1020
} semu_timer_t;
1121

12-
void semu_timer_init(semu_timer_t *timer, uint64_t freq);
22+
void semu_timer_init(semu_timer_t *timer, uint64_t freq, int n_harts);
1323
uint64_t semu_timer_get(semu_timer_t *timer);
1424
void semu_timer_rebase(semu_timer_t *timer, uint64_t time);
1525

@@ -77,7 +87,7 @@ static inline void list_del_init(struct list_head *node)
7787
})
7888
#else
7989
#define container_of(ptr, type, member) \
80-
((type *) ((char *) (ptr) -offsetof(type, member)))
90+
((type *) ((char *) (ptr) - offsetof(type, member)))
8191
#endif
8292

8393
#define list_entry(node, type, member) container_of(node, type, member)

0 commit comments

Comments
 (0)