起Linux虚拟机时发现kernel日志random: crng init done之前等了很久。通过debug kernel代码对这个问题稍微有了一点理解,记录一下。
当前使用的kernel 代码是5.19.2.
drivers/char/random.c
static void __cold _credit_init_bits(size_t bits)
{
...
pr_notice("crng init done\n");
...
}
dump stack找出caller function
[ 3.731810] CPU: 0 PID: 1 Comm: systemd Tainted: G W 5.19.2 #7
[ 3.732393] Hardware name: linux,dummy-virt (DT)
[ 3.732780] Call trace:
[ 3.733016] dump_backtrace.part.6+0xec/0xf8
[ 3.733409] show_stack+0x14/0x28
[ 3.733722] dump_stack_lvl+0x78/0x98
[ 3.734062] dump_stack+0x14/0x30
[ 3.734374] _credit_init_bits+0x1c/0x1ec
[ 3.734740] entropy_timer+0x50/0x60
[ 3.735072] call_timer_fn.isra.26+0x20/0x78
[ 3.735463] run_timer_softirq+0x4e4/0x578
[ 3.735841] _stext+0x11c/0x284
[ 3.736138] irq_exit_rcu+0x10c/0x138
...
本机上大部分的调用都是entropy_timer, 即大部分的熵的来源是时钟中断。注册timer在下面的code里。
static void __cold try_to_generate_entropy(void)
{
enum { NUM_TRIAL_SAMPLES = 8192, MAX_SAMPLES_PER_BIT = HZ / 30 };
struct entropy_timer_state stack;
unsigned int i, num_different = 0;
unsigned long last = random_get_entropy(); //read arm arch counter
// decide samples_per_bit
for (i = 0; i < NUM_TRIAL_SAMPLES - 1; ++i) {
stack.entropy = random_get_entropy();
if (stack.entropy != last)
++num_different;
last = stack.entropy;
}
stack.samples_per_bit = DIV_ROUND_UP(NUM_TRIAL_SAMPLES, num_different + 1);
if (stack.samples_per_bit > MAX_SAMPLES_PER_BIT)
return;
stack.samples = 0;
timer_setup_on_stack(&stack.timer, entropy_timer, 0); // register timer
while (!crng_ready() && !signal_pending(current)) {
if (!timer_pending(&stack.timer))
mod_timer(&stack.timer, jiffies + 1); // at least get sample every 2 jiffies
mix_pool_bytes(&stack.entropy, sizeof(stack.entropy));
schedule();
stack.entropy = random_get_entropy();
}
del_timer_sync(&stack.timer);
destroy_timer_on_stack(&stack.timer);
mix_pool_bytes(&stack.entropy, sizeof(stack.entropy));
}
上面的代码主要作用是注册一个新的定时器,决定多久调用一次定时器。如果当前这个新的定时器没有处于pending状态就会在下一个时钟中断再去trigger。通过打印,entropy_timer的调用频率大约是2个jiffies一次。此外samples_per_bit也会对采样频率产生影响。这个值的大小跟当前机器的arm arch timer的频率有关。系统时钟频率越高此值越大。
static void __cold entropy_timer(struct timer_list *timer)
{
struct entropy_timer_state *state = container_of(timer, struct entropy_timer_state, timer);
if (++state->samples == state->samples_per_bit) {
credit_init_bits(1);
state->samples = 0;
}
}
#define credit_init_bits(bits) if (!crng_ready()) _credit_init_bits(bits)
并非每次调用entropy_timer都会触发采样。samples_per_bit决定调用credit_init_bits的频率。根据测试在系统时钟是200M的机器上samples_per_bit的值是1,也就是每次都会调用credit_init_bits.在系统时钟频率是25M的机器上samples_per_bit是2,也即每两次entropy_timer调用,调用一次credit_init_bits.
根据测试,采样的用数量大约在250次左右,不同机器差异不大,如此,影响随机数初始化时间的因素包括硬件:系统时钟的频率; 软件:时钟中断的频率,由kenrel config配置。此外增加熵的来源也能大大提高初始化速度。