Linux之random子系统问题解决分享
问题信息
问题描述
T673 V1.1项目在集成测试Build 1期间, 使用T673设备升级系统测试Build1整机包测试启动耗时, 发现设备启动至进入应用用时1分钟左右, 耗时较其他项目(T671B等)时间长, 用户体验差,需要修复。
- 与应用同事沟通收集信息:
- T673 V1.1项目在今年四月份异常结项,未有关注启动耗时优化;
- T673 V1.1项目在今年九月份重新开启,至十月下旬的系统测试Build1期间未有关注启动耗时优化;
- 在项目重启至系统测试B1之前有合入以下内容:
- 触摸屏新物料支持;
- PHY新物料支持;
- 韦根驱动从基线同步;
- 应用程序支持硬解密;
- 其他;
问题排查
测试一、Hicore卡顿业务定位
应用同事在Hicore启动过程中增加时间戳信息,以初步定位耗时位置。调试整机固件, ACS_673_F1plus_CN_STD_V1.0.0_build221020.zip
测试日志
[10-20 09:33:14.152][E][anymouse]|acs_config_db_set_version|206|timetj exec start // 卡顿起始位置
[10-20 09:33:24.009][W][anymouse]|hardWatchDog|172|Feed dog is working properly.
[10-20 09:33:34.025][W][anymouse]|hardWatchDog|172|Feed dog is working properly.
[10-20 09:33:34.420][E][anymouse]|acs_config_db_set_version|212|timetj exec end // 卡顿消失位置
结论
应用同事初步确认:卡顿长位置位于acs_config_db_set_version,用时约20s。
测试二、系统资源信息分析
top信息
Mem: 123584K used, 321324K free, 19752K shrd, 1088K buff, 70360K cached
CPU: 52.3% usr 0.0% sys 0.0% nic 47.6% idle 0.0% io 0.0% irq 0.0% sirq
Load average: 1.85 0.40 0.13 2/112 1000
PID PPID USER STAT VSZ %VSZ CPU %CPU COMMAND
955 951 root S 120m 27.7 0 52.3 [uicore] // uicore处于休眠状态,CPU占用较高
952 1 root S 178m 40.9 1 0.0 [hicore]
859 856 root S 37896 8.5 0 0.0 [log4j]
Uicore的CPU占用较高,做移除测试。
移除Uicore后,再次捕捉卡顿时间点的top信息
[01-25 01:14:25.143][E][anymouse]|acs_config_db_set_version|206|timetj exec start
Enter Debug Mode.
[root@dvrdvs ] # TMOUT=0
[root@dvrdvs ] # top -n1
Mem: 116800K used, 328108K free, 18524K shrd, 1076K buff, 64360K cached
CPU: 0.0% usr 4.5% sys 0.0% nic 95.4% idle 0.0% io 0.0% irq 0.0% sirq
Load average: 2.35 0.52 0.17 1/94 969
PID PPID USER STAT VSZ %VSZ CPU %CPU COMMAND
927 1 root S 178m 40.9 1 0.0 [hicore]
858 856 root S 28676 6.4 0 0.0 [log4j]
750 1 root S 14404 3.2 0 0.0 [bsp_log4j]
。。。
[01-25 01:14:51.871][E][anymouse]|acs_config_db_set_version|212|timetj exec end
perf分析文件,截图:

结论
- 从数据分析:CPU 95.4% idle, CPU占用少,IO占用低, 中断少。
- 因为禁用uicore启动后,卡顿问题仍旧存在且时间上没有变化,所以排除掉uicore程序占用CPU高而导致的卡顿问题。
- 如上说明top信息分析:CPU占用低、IO占用低、中断触发少,所以卡顿问题与负载无直接关联,可以关注其他性能分析方向,如:
- 锁竞争;
- 资源同步;
- 进程调度;
测试三、random相关分析
[11.16-15:44:27]/home/config/config.db init ok...[ 19.600532] random: hicore: uninitialized urandom read (256 bytes read)
[11.16-15:44:27]
[11.16-15:44:27][11-16 15:48:49.144][E][anymouse]|dev_param_cfg_file_init|113|timetj cccccccccccccccc
[11.16-15:44:27][11-16 15:48:49.144][E][anymouse]|acs_config_db_set_version|206|timetj exec start // 应用调试打印
...
[11.16-15:44:36][ 28.118694] random: crng init done
[11.16-15:44:36][11-16 15:48:57.673][E][anymouse]|acs_config_db_set_version|212|timetj exec end // 退出
经测试,每次进入(timetj exec start)、退出(timetj exec end)卡顿时都有随机妖数相关打印信息:
-
Hicore向随机随机数发生器读取256节点随机数:
random: hicore: uninitialized urandom read (256 bytes read); -
随机数发生器初始化完成:
random: crng init done; -
源码查找
gaoyang3513@General:~/Source_code/xxx/SDK_T673_V1.1$ grep -rn "urandom read" . ./kernel/drivers/char/random.c:1896: "urandom read (%zd bytes read)\n", gaoyang3513@General:~/Source_code/xxx/SDK_T673_V1.1$ grep -rn "crng init done" kernel/ kernel/drivers/char/random.c:962: pr_notice("random: crng init done\n"); -
栈回溯确认调用路径
测试补丁:Index: kernel/drivers/char/random.c =================================================================== --- kernel/drivers/char/random.c (revision 523917) +++ kernel/drivers/char/random.c (working copy) @@ -238,6 +238,7 @@ * Eastlake, Steve Crocker, and Jeff Schiller. */ +#include "linux/printk.h" #include <linux/utsname.h> #include <linux/module.h> #include <linux/kernel.h> @@ -960,6 +961,7 @@ process_random_ready_list(); wake_up_interruptible(&crng_init_wait); pr_notice("random: crng init done\n"); + dump_stack(); if (unseeded_warning.missed) { pr_notice("random: %d get_random_xx warning(s) missed " "due to ratelimiting\n", @@ -1824,6 +1826,9 @@ urandom_warning.interval = 0; unseeded_warning.interval = 0; } + + printk("[%s|%u] -- GY\n", __FUNCTION__, __LINE__); + return 0; } early_initcall(rand_initialize); @@ -1891,10 +1896,12 @@ if (!crng_ready() && maxwarn > 0) { maxwarn--; - if (__ratelimit(&urandom_warning)) + if (__ratelimit(&urandom_warning)) { printk(KERN_NOTICE "random: %s: uninitialized " "urandom read (%zd bytes read)\n", current->comm, nbytes); + dump_stack(); + } spin_lock_irqsave(&primary_crng.lock, flags); crng_init_cnt = 0; spin_unlock_irqrestore(&primary_crng.lock, flags);- 使用内核
dump_stack实现栈回溯打印; - 在random模块初始化位置,增加打印信息;
- 在打印
random: hicore: uninitialized urandom read (256 bytes read)之后,打印栈回溯信息; - 在打印
random: crng init done之后,打印栈回溯信息;
打印信息如下:
[ 1.063632] nvt_rng f0680000.rng: Register nvt_rng_probe successfully // 硬件随机数驱动加载成功 ... [ 8.726708] random: fast init done // fast_pool初始化成功,crng_init=1; ... /home/config/config.db init ok...[ 9.267438] random: hicore: uninitialized urandom read (256 bytes read) // 获取random [ 9.276544] CPU: 0 PID: 946 Comm: hicore Tainted: P O 4.19.91 #2-ga8b08bf6-dirty [ 9.285224] Hardware name: Novatek Video Platform [ 9.289913] Backtrace: [ 9.292362] [<8010c3d4>] (dump_backtrace) from [<8010c500>] (show_stack+0x18/0x1c) [ 9.299914] r7:7ee32760 r6:60030013 r5:00000000 r4:80c890bc [ 9.305563] [<8010c4e8>] (show_stack) from [<80742d2c>] (dump_stack+0x90/0xac) [ 9.312775] [<80742c9c>] (dump_stack) from [<8043b454>] (urandom_read+0x84/0x2e4) [ 9.320239] r7:7ee32760 r6:00000100 r5:00000100 r4:80ccc784 [ 9.325889] [<8043b3d0>] (urandom_read) from [<80231ca4>] (__vfs_read+0x40/0x15c) // vfs_read调用字符设备的read实际:urandom_read, [ 9.333357] r10:00000003 r9:00000100 r8:c10d7f60 r7:80c24508 r6:00000100 r5:8043b3d0 [ 9.341165] r4:c2a91200 [ 9.343692] [<80231c64>] (__vfs_read) from [<80231e58>] (vfs_read+0x98/0x104) [ 9.350812] r9:00000100 r8:c10d7f60 r7:7ee32760 r6:00000100 r5:00000000 r4:c2a91200 [ 9.358539] [<80231dc0>] (vfs_read) from [<8023234c>] (ksys_read+0x64/0xc0) [ 9.365485] r9:00000100 r8:7ee32760 r7:80c24508 r6:c2a91201 r5:7ee32760 r4:c2a91200 [ 9.373211] [<802322e8>] (ksys_read) from [<802323b8>] (sys_read+0x10/0x14) [ 9.380157] r9:c10d6000 r8:80101204 r7:00000003 r6:00000015 r5:7ee32760 r4:00000100 [ 9.387885] [<802323a8>] (sys_read) from [<80101000>] (ret_fast_syscall+0x0/0x58) [ 9.395347] Exception stack(0xc10d7fa8 to 0xc10d7ff0) [ 9.400385] 7fa0: 00000100 7ee32760 00000015 7ee32760 00000100 00000000 [ 9.408545] 7fc0: 00000100 7ee32760 00000015 00000003 00fcd90c 00000000 76f25000 7ee3273c [ 9.416702] 7fe0: 745ff4d0 7ee32700 00000000 74c07a24 BusyBox v1.31.1 (2022-11-20 20:01:27 CST) built-in shell (ash) Enter 'help' for a list of built-in commands. BusyBox v1.2.1 Protect Shell (psh svn326548) Build Time: Mar 17 2021:10:03:53 Enter 'help' for a list system commands. Random number is:18050941 # [ 24.626902] random: crng init done // crng初始化完成 [ 24.630304] CPU: 0 PID: 0 Comm: swapper/0 Tainted: P O 4.19.91 #2-ga8b08bf6-dirty [ 24.639067] Hardware name: Novatek Video Platform [ 24.643755] Backtrace: [ 24.646204] [<8010c3d4>] (dump_backtrace) from [<8010c500>] (show_stack+0x18/0x1c) [ 24.653756] r7:80c5bc68 r6:600e0193 r5:00000000 r4:80c890bc [ 24.659404] [<8010c4e8>] (show_stack) from [<80742d2c>] (dump_stack+0x90/0xac) [ 24.666616] [<80742c9c>] (dump_stack) from [<8043a198>] (crng_reseed.constprop.21+0x11c/0x1e8) [ 24.675208] r7:80c5bc68 r6:80c5bc68 r5:80c24508 r4:80c5bb2c [ 24.680858] [<8043a07c>] (crng_reseed.constprop.21) from [<8043a3dc>] (credit_entropy_bits+0x178/0x31c) [ 24.690230] r9:0000004c r8:809bb505 r7:00000001 r6:80c5bb2c r5:80c5bb5c r4:00000080 [ 24.697958] [<8043a264>] (credit_entropy_bits) from [<8043aa54>] (add_interrupt_randomness+0x1c0/0x1e4) [ 24.707333] r10:80c01ef0 r9:80169c44 r8:436c1000 r7:80c5bb80 r6:fffebd4b r5:80c24508 [ 24.715140] r4:c4232388 [ 24.717672] [<8043a894>] (add_interrupt_randomness) from [<80169c44>] (handle_irq_event_percpu+0x40/0x84) // 硬件随机数据生成器,在random中注册的回调; [ 24.727218] r10:80b5ca38 r9:80c00000 r8:c3c24000 r7:00000030 r6:80c08500 r5:00000001 [ 24.735027] r4:80c24508 [ 24.737555] [<80169c04>] (handle_irq_event_percpu) from [<80169cc8>] (handle_irq_event+0x40/0x64) [ 24.746405] r6:80c08568 r5:80c08568 r4:80c08500 [ 24.751013] [<80169c88>] (handle_irq_event) from [<8016ddfc>] (handle_fasteoi_irq+0xc4/0x138) // CPU在每次处理完中断后发出EOI(end of interrupt), 由GIC自实现中断流控; [ 24.759518] r7:00000030 r6:80c08568 r5:80c24a78 r4:80c08500 [ 24.765166] [<8016dd38>] (handle_fasteoi_irq) from [<801690e0>] (generic_handle_irq+0x20/0x30) [ 24.773757] r7:00000030 r6:80b71988 r5:00000000 r4:00000000 [ 24.779404] [<801690c0>] (generic_handle_irq) from [<801693b8>] (__handle_domain_irq+0xa8/0xbc) [ 24.788087] [<80169310>] (__handle_domain_irq) from [<803e9f64>] (gic_handle_irq+0x54/0x80) [ 24.796420] r9:80c00000 r8:80c24508 r7:c4803100 r6:80c01ef0 r5:80c24a78 r4:c4802100 [ 24.804147] [<803e9f10>] (gic_handle_irq) from [<80101a0c>] (__irq_svc+0x6c/0xa8) // 中断回调, [ 24.811609] Exception stack(0x80c01ef0 to 0x80c01f38) [ 24.816647] 1ee0: 00000000 00058e50 c422f48c 80117dc0 [ 24.824807] 1f00: 00000001 80c00000 80c24530 80c24500 80c24508 c45fcb00 80b5ca38 80c01f4c [ 24.832966] 1f20: 80c01f50 80c01f40 80108be8 80108bd8 600e0013 ffffffff [ 24.839564] r7:80c01f24 r6:ffffffff r5:600e0013 r4:80108bd8 [ 24.845215] [<80108bb4>] (arch_cpu_idle) from [<8075dd0c>] (default_idle_call+0x30/0x34) [ 24.853290] [<8075dcdc>] (default_idle_call) from [<80150eec>] (do_idle+0xd8/0x128) [ 24.860928] [<80150e14>] (do_idle) from [<801511d8>] (cpu_startup_entry+0x20/0x28) [ 24.868479] r7:80c24500 r6:ffffffff r5:00000002 r4:000000c7 [ 24.874127] [<801511b8>] (cpu_startup_entry) from [<807568c8>] (rest_init+0xbc/0xdc) [ 24.881858] [<8075680c>] (rest_init) from [<80b00f10>] (start_kernel+0x478/0x518) [ 24.889320] r5:80c9e48c r4:00000000 [ 24.892884] [<80b00a98>] (start_kernel) from [<00000000>] ( (null)) - 使用内核
-
源码跟踪
urandom_read# kernel/drivers/char/random.c static ssize_t urandom_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos) if (!crng_ready() && maxwarn > 0) { // crng未初始化完成 maxwarn--; if (__ratelimit(&urandom_warning)) // 在多次(10次)尝试仍失败时,问题打印 printk(KERN_NOTICE "random: %s: uninitialized " "urandom read (%zd bytes read)\n", current->comm, nbytes); nbytes = min_t(size_t, nbytes, INT_MAX >> (ENTROPY_SHIFT + 3)); extract_crng_user(buf, nbytes); int large_request = (nbytes > 256); while (nbytes) { if (large_request && need_resched()) { // 单次获取超过256字节时,主动调度释放CPU; schedule(); extract_crng(tmp); crng = &primary_crng; _extract_crng(crng, out); if (crng_ready() && // crng初始化完成,且超过老化时间时,重新写入熵值; (time_after(crng_global_init_time, crng->init_time) || time_after(jiffies, crng->init_time + CRNG_RESEED_INTERVAL))) crng_reseed(crng, crng == &primary_crng ? &input_pool : NULL); if (crng == &primary_crng && crng_init < 2) { if (arch_get_random_long(&v)) crng->state[14] ^= v; chacha20_block(&crng->state[0], out); // 一个block包含64字节; i = min_t(int, nbytes, CHACHA20_BLOCK_SIZE); copy_to_user(buf, tmp, i); // 拷贝至用户空间; nbytes -= i; // 读取i字节数据; buf += i; ret += i;查找所有引用
kernel/drivers/char/random.c 2099, 11: .read = urandom_read, 2127, 9: return urandom_read(NULL, buf, count, NULL);-
第一处:字符设备/dev/urandom, read接口
# kernel/drivers/char/random.c const struct file_operations urandom_fops = { .read = urandom_read, // read为字符/dev/urandom的读实现 .write = random_write, .unlocked_ioctl = random_ioctl, }; # kernel/drivers/char/mem.c static const struct memdev devlist[] = { ... [8] = { "random", 0666, &random_fops, 0 }, [9] = { "urandom", 0666, &urandom_fops, 0 }, }; static int __init chr_dev_init(void) register_chrdev(MEM_MAJOR, "mem", &memory_fops); // 注册字符设备mem, 主设备 mem_class = class_create(THIS_MODULE, "mem"); // 创建设备类 for (minor = 1; minor < ARRAY_SIZE(devlist); minor++) { // 遍历次设备,创建从设备(包urandom) device_create(mem_class, NULL, MKDEV(MEM_MAJOR, minor), NULL, devlist[minor].name);第一处进入条件为:open(“dev/urandom”, xxx); read(“dev/urandom”, xxx, 256);
结合问题分析:在随机字节池未填满的前提下,当Hicore陷入内核态后先打印random: hicore: uninitialized urandom read (256 bytes read)后,因Hicore使用第二种方式获取随机数,长度256;字节(小于调度限制:大于256字节),带个流程执行完成后返回用户状,整个调用流程无阻塞。 -
第二处:系统调用getrandom
#define INT_MAX ((int)(~0U>>1)) #define crng_ready() (likely(crng_init > 1)) SYSCALL_DEFINE3(getrandom, char __user *, buf, size_t, count, unsigned int, flags) { int ret; if (flags & ~(GRND_NONBLOCK|GRND_RANDOM)) return -EINVAL; if (count > INT_MAX) // 限制最大读取长度(int值长度) count = INT_MAX; if (flags & GRND_RANDOM) // 如果指定使用random,替代urandom return _random_read(flags & GRND_NONBLOCK, buf, count); if (!crng_ready()) { // 如果crng未准备完成,等待 if (flags & GRND_NONBLOCK) // 如果传入的flags指定不阻塞,直接返回 return -EAGAIN; ret = wait_for_random_bytes(); // 否则挂起等待 if (likely(crng_ready())) // 如果crng准备完成,退出 return 0; return wait_event_interruptible(crng_init_wait, crng_ready()); // 提交至等待对列crng_init_wait, 条件为crng_ready() if (unlikely(ret)) return ret; } return urandom_read(NULL, buf, count, NULL); // 读取随机数 }第二处进入条件为: 直接调用系统接口
getrandom。
其中wait_event_interruptible为同步阻塞,所以系统调用getrandom历可能将线程挂起直至条件crng_ready()满足;
结合问题分析:在随机字节池未填满的前提下(crng_ready()不满足),Hicore陷入内核态后直接被挂起,不可能打印random: hicore: uninitialized urandom read (256 bytes read)(该打印在urandom_read中执行, 在唤醒后被调用)。
-
结论一
综上,有以下结论:
1. `/dev/urandom`不会引起进程阻塞,另外Hicore有调用`read("dev/urandom", xxx, 256)`,进而有的内核打印信息`random: hicore: uninitialized urandom read (256 bytes read)`;
2. `getrandom`可能会引起进程阻塞,如果进程被挂起,可能是调用了系统接口`getrandom`;
结合问题,random: hicore: uninitialized urandom read (256 bytes read)由用户使用urandom调用read(“dev/urandom”, xxx, 256)而有的内核打印信息。该流程无阻塞发生完整结束。
-
源码跟踪
crng_reseed# kernel/drivers/char/random.c #define crng_ready() (likely(crng_init > 1)) static void crng_reseed(struct crng_state *crng, struct entropy_store *r) ... if (crng == &primary_crng && crng_init < 2) { crng_init = 2; process_random_ready_list(); wake_up_interruptible(&crng_init_wait); // 唤醒等待对列:crng_init_wait pr_notice("random: crng init done\n");crng_ready()用于检查crng是否初始化完成,当
crng_init = 2;时,即主crng初始化完成。
随即唤醒初始化等待对列中的线程。
查找所有引用kernel/drivers/char/random.c 719, 4: crng_reseed(&primary_crng, r); 987, 3: crng_reseed(crng, crng == &primary_crng ? &input_pool : NULL); 2076, 3: crng_reseed(&primary_crng, NULL);-
第一处:
credit_entropy_bits, 加入(或取出)目标熵池的bit数量static void credit_entropy_bits(struct entropy_store *r, int nbits) const int pool_size = r->poolinfo->poolfracbits; int nfrac = nbits << ENTROPY_SHIFT; entropy_count = orig = READ_ONCE(r->entropy_count); if (nfrac < 0) entropy_count += nfrac; else int pnfrac = nfrac; const int s = r->poolinfo->poolbitshift + ENTROPY_SHIFT + 2; do { unsigned int anfrac = min(pnfrac, pool_size/2); unsigned int add = ((pool_size - entropy_count)*anfrac*3) >> s; entropy_count += add; pnfrac -= anfrac; } while (unlikely(entropy_count < pool_size-2 && pnfrac)); } if (r == &input_pool) { int entropy_bits = entropy_count >> ENTROPY_SHIFT; if (crng_init < 2 && entropy_bits >= 128) { crng_reseed(&primary_crng, r);调用条件为:
- 用户主动调用ioctl(“/dev/random”, RNDADDTOENTCNT(或RNDADDENTROPY), xxx);
- RNDADDTOENTCNT, 仅增加熵池数量;
- RNDADDENTROPY, 向熵池增加若干随机字(单位:字节) 1;
- 熵池输入源一:
add_timer_randomness;- input_handle_event, input子系统event事件: 键盘、触摸等输入事件;
- 熵池输入源二:
add_disk_randomness;- blk_update_bidi_request, 块设备的的BIO请求:读、写磁盘(包括eMMC)动作;
- 熵池输入源三:
add_interrupt_randomness;- handle_irq_event_percpu, 所有中断的必经之路:所有硬中断;
- 熵池输入源三:
add_hwgenerator_randomness;- hwrng_fillfn, 内核线程"hwrng": 持续写入,最大延时1s;
- 其他:
- xfer_secondary_pool, 从主熵池推送到次熵池;
- push_to_pool, 从‘主熵池’推送到‘输出熵池’,工作对列回调;
由上可知
credit_entropy_bits做为熵池填充的入口,与crng初始化完成有直接关系;-
第二处:
_extract_crng,static void _extract_crng(struct crng_state *crng, __u8 out[CHACHA20_BLOCK_SIZE]) if (crng_ready() && (time_after(crng_global_init_time, crng->init_time) || time_after(jiffies, crng->init_time + CRNG_RESEED_INTERVAL))) crng_reseed(crng, crng == &primary_crng ? &input_pool : NULL);必要条件
- crng_ready(),crng初始化完成;
调用条件为
- crng_reseed, 当入参entropy_store(熵池抽象类),即未指定熵池对象,递归一次;
- extract_crng, 当入参entropy_store(熵池抽象类)为input_pool,即指定主crng;
- crng_backtrack_protect, 规避chacha20-key回溯保护机制;
- urandom_read, 用户主动调用read(“/dev/urandom”, …);
- get_random_(bytes/u32/u64), 导出符号:涉及模块过多;
以上调用条件都基于必要条件:crng_ready()。
-
第三处:
random_ioctl, /dev/randomr操作static long random_ioctl(struct file *f, unsigned int cmd, unsigned long arg) case RNDRESEEDCRNG: if (crng_init < 2) return -ENODATA; crng_reseed(&primary_crng, NULL);必要条件:
- crng_ready(),crng初始化完成;
调用说明:
- 用户主动要求主crng重新填充;
以上调用条件都基于必要条件:crng_ready()。
- 用户主动调用ioctl(“/dev/random”, RNDADDTOENTCNT(或RNDADDENTROPY), xxx);
-
结论二
综上,有以下结论:
- 因为crng_ready()做为前提条件下,只有
credit_entropy_bits可以触发主crng初始化完成;
结合问题,random: crng init done由硬件中断回调中使用add_interrupt_randomness向主crng的熵池中添加随机值而触发了主crng的初始化完成,进而唤醒了在等待对列crng_init_wait中挂起的线程(包含Hicore)。
总结
结合urandom_read与crng_reseed的代码跟读,整理出问题流程时序图表示如下:

结合图示,复述问题过程:
-
启动启动中,系统通过中断的方式不断向input_pool熵池填充状态随机数,而blocking_pool为空。
-
应用程序Hicore调用系统接口getrandom()获取256个随机数而陷入内核态;
-
此时input_pool熵池未收到阈值128 bits的随机数而处于处于未初始化状态(crng_init值为1),即crng_read()为假(crng_int值为2时,crng_read()为真)。进而random驱动将Hicore挂起至crng_init_wait等待对列,等待crng初始化完成;
-
20s后,终于有"通用中断不断向input_pool熵池填充随机数到达了初始化阈值(128 bit),crng_ready()为真”的条件满足;
-
应用程序Hicore从等待对列恢复运行,通过urandom_read读取到256 Bytes随机数后,最终返回到用户状完成"读取随机数"工作。
其中有步骤4消耗了20s时间,于是有使用上可以感受到的卡顿效果。
简单总结问题原因:应用程序Hicore调用系统接口getrandom尝试获取256 Byte随机数,但因crng未初始化完成,于是Hicore在内核态时被挂起,直至20s后crng初始化完成,Hicore恢复运行。
问题拓展
random模块
基本概念
- random模块自持有三个熵池:fast_pool、input_pool、blocking_pool和一个一致性随机数生成器:primary_crng。其中fast_pool较次要,不做说明;
-
熵池的大小:entropy_count, 计数单位为1/8 bit,所以有计算熵池大小宏ENTROPY_BITS®,定义如下:
#define ENTROPY_SHIFT 3 #define ENTROPY_BITS(r) ((r)->entropy_count >> ENTROPY_SHIFT) -
随机数导出接口
extract_entropy,对应熵池:input_pool、blocking_pool;extract_crng,对应熵池:primary_crng;
-
熵池变化:
credit_entropy_bits,增加,单位:bit;account,消耗,单位:byte;
-
- 熵池输入(四个输入源)、输出(三个输出接口)的关系
![在这里插入图片描述]()
接口说明
-
random、urandom、getrandom三种方式差异
-
/dev/random,read
![在这里插入图片描述]()
-
/dev/urandom,read
![在这里插入图片描述]()
-
getrandom
![在这里插入图片描述]()
以下对接口归纳说明如下:
接口 IO类型 备注 /dev/urandom,read 1. 同步非阻塞
2. 异步/dev/random,read 1. IO多路复用
2. 异步真随机数 getrandom 1. 同步阻塞 集成random与urandom,取决于flags -
使用关注
-
熵池更新策略与实现?
- input_pool 更新策略:
- 由随机数熵池章节可知,input_pool熵池不断由4个输入源更新;
- 用户主动调用ioctl(RNDADDTOENTCNT/RNDADDENTROPY,…)更新;
- blocking_pool更新策略:
- 每次“真随机数”读取前更新;
- input_pool 水位阈值触发时更新;
- primary_state更新策略:
- 每次随机数读取后更新;
- 用户调用ioctl(RNDRESEEDCRNG,…)更新;
- input_pool 更新策略:
-
消耗熵池生成随机数?
从读取随机数的入口而言,仅有两个熵池与随机数生成有关联:blocking_pool与primary_crng;
- blocking_pool熵池与随机数生成;
- primary_crng熵池与随机数生成;
-
crng_ready()控制什么状态?
从crng_ready()定义分析
static int crng_init = 0; #define crng_ready() (likely(crng_init > 1))可知,crng_ready()关联的是primary_crng熵池,且crng_init变量只会单调增长。其值分别代表如下意义
值 说明 备注 0 初始值 random驱动加载初始化状态 1 primary_crng从fast_pool第一次填充完成 fast_pool熵池,已经收集64次数据
日志打印:random: fast init done
每个CPU都私有一个fast_pool,每次中断处理都混写一次,64次之后认为fast初始化完成,2 primary_crng从input_pool第一次填充完成 input_pool熵池,已经收集128 bit(16字节)数据;
日志打印:random: crng init done
从input_pool熵池导出16字节简单处理后给到crng,完成crng的初始化; -
proc调试接口有哪些
random模块相关proc文件系统重要集中于目录/proc/sys/kernel/random下,有:
- entropy_avail, input_pool熵池当前收集的随机数大小,单位:bit;
- poolsize, input_pool熵池大小,单位:bit;
- read_wakeup_threshold, 读唤醒阈值;
- write_wakeup_threshold, 写唤醒阈值;
- urandom_min_reseed_secs,crng重新填充的老化时间;
hw_random模块
基本概念
-
硬件随机数注册
static struct hwrng *current_rng; static int hwrng_fillfn(void *unused) { long rc; while (!kthread_should_stop()) { struct hwrng *rng; rng = get_current_rng(); if (IS_ERR(rng) || !rng) break; mutex_lock(&reading_mutex); rc = rng_get_data(rng, rng_fillbuf, rng_buffer_size(), 1); mutex_unlock(&reading_mutex); put_rng(rng); if (rc <= 0) { pr_warn("hwrng: no data available\n"); msleep_interruptible(10000); continue; } add_hwgenerator_randomness((void *)rng_fillbuf, rc, rc * current_quality * 8 >> 10); // random 输入方式之一 } hwrng_fill = NULL; return 0; } static void start_khwrngd(void) { hwrng_fill = kthread_run(hwrng_fillfn, NULL, "hwrng"); } static void add_early_randomness(struct hwrng *rng) { int bytes_read; size_t size = min_t(size_t, 16, rng_buffer_size()); mutex_lock(&reading_mutex); bytes_read = rng_get_data(rng, rng_buffer, size, 0); mutex_unlock(&reading_mutex); if (bytes_read > 0) add_device_randomness(rng_buffer, bytes_read); // random模块输入源之一,但不增加熵池数据量 } int hwrng_register(struct hwrng *rng) { struct hwrng *old_rng, *tmp; struct list_head *rng_list_ptr; old_rng = current_rng; if (!old_rng || (!cur_rng_set_by_user && rng->quality > old_rng->quality)) { set_current_rng(rng); hwrng_init(rng); add_early_randomness(rng); // 完成random模块 input_pool的初始化 current_quality = rng->quality ? : default_quality; if (current_quality == 0 && hwrng_fill) // 如果新的hwrng设置了quality,即每毫秒生成的随机数数量,开启定期线程 kthread_stop(hwrng_fill); if (current_quality > 0 && !hwrng_fill) // 否则关闭定期输送随机数的线程 start_khwrngd(); ... if (old_rng && !rng->init) { add_early_randomness(rng); // 完成random模块 input_pool的初始化 } /************************************************/ static int nvt_rng_probe(struct platform_device *pdev) { struct nvt_rng *rng; rng = devm_kzalloc(&pdev->dev, sizeof(*rng), GFP_KERNEL); ... rng->rng.name = pdev->name; rng->rng.init = nvt_rng_init; rng->rng.cleanup = nvt_rng_cleanup; rng->rng.read = nvt_rng_read; ... devm_hwrng_register(&pdev->dev, &rng->rng); hwrng_register(rng);数据类型与rng注册流程如下:
![在这里插入图片描述]()
接口说明
-
系统调用read
目前混杂驱动hwrng只提供了read操作接口,该接口用于获取硬件随机数模块生成的随机数。
接口 IO类型 备注 /dev/hw_random,read 1. 同步阻塞 真随机数
使用说明
-
hw_random与random两个模块之间联系?
random对外有两个随机数更新接口,hw_random使用这个两个接口实现了不同的目的:
- add_device_randomness, 只混写input_pool熵池,但不增加熵池数量,用于前期初始化;
- add_hwgenerator_randomness,混写且增加input_pool熵池数据,用于定期更新熵池;
-
如何使用hw_random快速填充random的input_pool完成crng初始化?
由硬件随机数模块的注册函数可知:当新rng注册到hw_random时,会检查目标rng的品质参数quality,再决定是否启动定期更新input_pool的内核线程。
quality即随机数的品质系数,等级范围:0~1024。这个系数决定了两个关键信息:
- 决定是否启动内核线程hwrng_fill,在线程运行期间向input_pool熵池增加熵值;
- 决定随机数输入到input_pool时,有效bit数(nbytes * 8 * (quality / 1024));
问题解决
结合“问题排查”分析,对于主crng熵池未能及时初始化导致的Hicore应用程序启动慢问题,解决方案分两个大类:

crng_ready方案
结合章节模块关注点3,可知crng是否初始化完成由变量crng_init标记,由函数crng_ready()实现检查。结合crng_init实际代表的状态,确认跳过crng初始化检查的影响。修改如下:
diff --git a/drivers/char/random.c b/drivers/char/random.c
index 0e0619b0..f147a9be 100644
--- a/drivers/char/random.c
+++ b/drivers/char/random.c
@@ -429,7 +429,7 @@ struct crng_state primary_crng = {
* its value (from 0->1->2).
*/
static int crng_init = 0;
-#define crng_ready() (likely(crng_init > 1))
+#define crng_ready() (likely(crng_init > 0))
static int crng_init_cnt = 0;
static unsigned long crng_global_init_time = 0;
#define CRNG_INIT_CNT_THRESH (2*CHACHA20_KEY_SIZE)
可知,此过程将跳过“input_pool收集128 bit数据并处理后导出到crng”,所以评估如下:
| 动作 | 效果 | 风险 |
|---|---|---|
| 跳过crng状态检查 | 可以解决问题 | input_pool、crng变化量不足 |
trust_cpu方案
如果我们(客户)信任SOC产商,即可在random模块初始化前期跳过crng的初始化过程,其中需要具备以下两个前提:
- 开启内核配置宏
CONFIG_RANDOM_TRUST_CPU,该宏由Kconfig读法决定。当该宏的信赖未开启时无法配置; - 需要实现平台随机数实现接口:arch_get_random_long。arm64平台一般直接有实现;
修改补丁(非正式代码,仅供思路参考)
diff --git a/drivers/char/random.c b/drivers/char/random.c
index 86fe1df9..d59380ff 100644
--- a/drivers/char/random.c
+++ b/drivers/char/random.c
@@ -780,7 +780,7 @@ static struct crng_state **crng_node_pool __read_mostly;
static void invalidate_batched_entropy(void);
static void numa_crng_init(void);
-static bool trust_cpu __ro_after_init = IS_ENABLED(CONFIG_RANDOM_TRUST_CPU);
+static bool trust_cpu __ro_after_init = 1;
static int __init parse_trust_cpu(char *arg)
{
return kstrtobool(arg, &trust_cpu);
diff --git a/include/linux/random.h b/include/linux/random.h
index 445a0ea4..df7cf7f7 100644
--- a/include/linux/random.h
+++ b/include/linux/random.h
@@ -9,6 +9,7 @@
#include <linux/list.h>
#include <linux/once.h>
+#include <linux/timex.h>
#include <uapi/linux/random.h>
@@ -167,7 +168,9 @@ static inline void prandom_seed_state(struct rnd_state *state, u64 seed)
#else
static inline bool arch_get_random_long(unsigned long *v)
{
- return 0;
+ *v = random_get_entropy();
+
+ return 1;
}
static inline bool arch_get_random_int(unsigned int *v)
{
信任CPU方案的分析如下:
| 动作 | 效果 | 风险 | 备注 |
|---|---|---|---|
| 信任CPU产商以跳过crng检查 | 可以解决问题 | 1. CPU厂商的随机数生成不可靠 2. 平台未实现相关接口; | 1. 打印内容:random: crng done (trusting CPU’s manufacturer) |
hw_random方案
F1平台的硬件随机数生成,对应注释到Linux的hwrng子系统下。使用了random模块的两个回调:
add_device_randomness, 只混写了random模块的input_pool熵池,但未增加熵池的大小,所以对crng初始化无直接帮助;add_hwgenerator_randomness, 混写了random模块的input_pool熵池且增加了熵池大小,所以对crng初始化有作用;
说明:
- nvt_rng,未设置质量评估值quarity,需要修改;
- 启动阶段预设置才有效;
信任CPU方案的分析如下:
| 动作 | 效果 | 风险 | 备注 |
|---|---|---|---|
| 使用hw_rng加速crng初始化 | 可以解决问题 | 1. 对无硬随机数外设的SOC无效; 2. hw_rng的可靠性暂无评估(0~1024); 3. 需要修改源码以支持或模块形式加载时附带参数; | 1. F1平台,未实现平台级的随机数获取接口; 2. 厂商的硬件随机数模块未设置quality品质系数; |
random加速方案
结合问题,知道本问题卡顿问题原因为使用getrandom接口时crng未初始化未完成而导致的卡顿问题,所以在Linux v5.4版本对random驱动优化为:使用timer(软中断),间隔时间1HZ(F1平台,HZ = 300, 即调度粒度约为3ms)快速填充input_pool熵池。每一次中断填充1 bit数据,128 bits就大约需要:3 * 128 = 384 ms。
参考linux v5.4对random驱动的修改,补丁如下:
Index: drivers/char/random.c
===================================================================
--- drivers/char/random.c (ѦѾ 524382)
+++ drivers/char/random.c (ѦѾ 524383)
@@ -1654,6 +1654,55 @@
EXPORT_SYMBOL(get_random_bytes);
/*
+ * Each time the timer fires, we expect that we got an unpredictable
+ * jump in the cycle counter. Even if the timer is running on another
+ * CPU, the timer activity will be touching the stack of the CPU that is
+ * generating entropy..
+ *
+ * Note that we don't re-arm the timer in the timer itself - we are
+ * happy to be scheduled away, since that just makes the load more
+ * complex, but we do not want the timer to keep ticking unless the
+ * entropy loop is running.
+ *
+ * So the re-arming always happens in the entropy loop itself.
+ */
+static void entropy_timer(struct timer_list *t)
+{
+ credit_entropy_bits(&input_pool, 1);
+}
+
+/*
+ * If we have an actual cycle counter, see if we can
+ * generate enough entropy with timing noise
+ */
+static void try_to_generate_entropy(void)
+{
+ struct {
+ unsigned long now;
+ struct timer_list timer;
+ } stack;
+
+ stack.now = random_get_entropy();
+
+ /* Slow counter - or none. Don't even bother */
+ if (stack.now == random_get_entropy())
+ return;
+
+ timer_setup_on_stack(&stack.timer, entropy_timer, 0);
+ while (!crng_ready()) {
+ if (!timer_pending(&stack.timer))
+ mod_timer(&stack.timer, jiffies+1);
+ mix_pool_bytes(&input_pool, &stack.now, sizeof(stack.now));
+ schedule();
+ stack.now = random_get_entropy();
+ }
+
+ del_timer_sync(&stack.timer);
+ destroy_timer_on_stack(&stack.timer);
+ mix_pool_bytes(&input_pool, &stack.now, sizeof(stack.now));
+}
+
+/*
* Wait for the urandom pool to be seeded and thus guaranteed to supply
* cryptographically secure random numbers. This applies to: the /dev/urandom
* device, the get_random_bytes function, and the get_random_{u32,u64,int,long}
@@ -1667,7 +1716,17 @@
{
if (likely(crng_ready()))
return 0;
- return wait_event_interruptible(crng_init_wait, crng_ready());
+
+ do {
+ int ret;
+ ret = wait_event_interruptible_timeout(crng_init_wait, crng_ready(), HZ);
+ if (ret)
+ return ret > 0 ? 0 : ret;
+
+ try_to_generate_entropy();
+ } while (!crng_ready());
+
+ return 0;
}
EXPORT_SYMBOL(wait_for_random_bytes);
说明:
1. 在crng未初始化完成时,要避免与通用中断的调用add_device_randomness输入的slow方式相冲突;
2. 增加的是input_pool熵池也会间接作用于fast_pool与crng_state,所以是比较完善的初始化加速机制;
| 动作 | 效果 | 风险 | 备注 |
|---|---|---|---|
| random加速方案 | 可以解决问题 | 1. 驱动补丁在V4.19版本内核未充分测试; | 分析驱动补丁,适用于F1平台 |
haveged工具
硬件熵池收集与扩展器((HArdware Volatile Entropy Gathering and Expansion,HAVEGE)是由用户空间实现的算法库。使用该算法库可以快速实现random熵池的快速填充以完成初始化。从Linux内核v5.4版本开始,HAVEGED的扩展算法已经内置到random模块,另外到v5.6版本,/dev/random也不再阻塞。haveged工具的该算法库提供的后台工具,其安装与使用如下。
# 下载haveged
git clone https://github.com/jirka-h/haveged.git
# 配置
autoreconf -f -i
./configure --prefix=${PWD}/__install CC=arm-ca9-linux-gnueabihf-gcc --host=arm --enable-shared
# 编译与安装
make && make install
# 产物
__install/
├── bin
│ └── haveged
...
├── lib
│ ├── libhavege.a
│ └── libhavege.la
## 使用
haveged -e
说明:
1. haveged是后台守护进程,而在内核启动initrd阶段使用且仅使用一次完成熵池扩充即可退出,所以使用命令附带参数`haveged -e`;
2. haveged基于random驱动ioctl宏`RNDADDENTROPY`填充熵池;
3. haveged使用GPL-3.0证书,有使用限制;
| 动作 | 效果 | 风险 | 备注 |
|---|---|---|---|
| 使用havege快速填充熵池 | 可以解决问题 | 1. 引入GPL-3.0证书限制; 2. 需要改动initrd初始化脚本; 3. 需要部署havege环境; | 海康对证书管理有GPL-3.0有申报要求 |
综上,解决方案决策如下:

本问题使用“random加速方案”。
备注
术语与缩写
- CRNG(congruent random number generator, 一致性随机数生成器);
调试命令
cp /tmp/start.sh /mnt/nfs/T673/
chmod 0777 /mnt/nfs/T673/start.sh
- add_hwgenerator_randomness, 增加硬件随机数发生器;
haveged 基于此接口实现; ↩︎





浙公网安备 33010602011771号