T673 V1.1项目在集成测试Build 1期间, 使用T673设备升级系统测试Build1整机包测试启动耗时, 发现设备启动至进入应用用时1分钟左右, 耗时较其他项目(T671B等)时间长, 用户体验差,需要修复。
应用同事在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 1000PID PPID USER STAT VSZ %VSZ CPU %CPU COMMAND955 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 startEnter 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分析文件,截图:
[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/01-Nt9852x/04-T673/01-IBPPrj/SDK_T673_V1.1$ grep -rn "urandom read" .
./kernel/drivers/char/random.c:1896: "urandom read (%zd bytes read)\n",gaoyang3513@General:~/Source_code/01-Nt9852x/04-T673/01-IBPPrj/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 #include #include
@@ -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: 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 74c07a24BusyBox 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,替代urandomreturn _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_waitpr_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;elseint 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);
调用条件为:
add_timer_randomness
; add_disk_randomness
; add_interrupt_randomness
; add_hwgenerator_randomness
; 由上可知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()。
第三处: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()。
综上,有以下结论:
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恢复运行。
熵池的大小: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 |
熵池更新策略与实现?
消耗熵池生成随机数?
从读取随机数的入口而言,仅有两个熵池与随机数生成有关联: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下,有:
硬件随机数注册
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使用这个两个接口实现了不同的目的:
如何使用hw_random快速填充random的input_pool完成crng初始化?
由硬件随机数模块的注册函数可知:当新rng注册到hw_random时,会检查目标rng的品质参数quality,再决定是否启动定期更新input_pool的内核线程。
quality即随机数的品质系数,等级范围:0~1024。这个系数决定了两个关键信息:
结合“问题排查”分析,对于主crng熵池未能及时初始化导致的Hicore应用程序启动慢问题,解决方案分两个大类:
结合章节模块关注点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变化量不足 |
如果我们(客户)信任SOC产商,即可在random模块初始化前期跳过crng的初始化过程,其中需要具备以下两个前提:
CONFIG_RANDOM_TRUST_CPU
,该宏由Kconfig读法决定。当该宏的信赖未开启时无法配置;修改补丁(非正式代码,仅供思路参考)
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 #include
+#include #include @@ -167,7 +168,9 @@ static inline void prandom_seed_state(struct rnd_state *state, u64 seed)#elsestatic 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) |
F1平台的硬件随机数生成,对应注释到Linux的hwrng子系统下。使用了random模块的两个回调:
add_device_randomness
, 只混写了random模块的input_pool熵池,但未增加熵池的大小,所以对crng初始化无直接帮助;add_hwgenerator_randomness
, 混写了random模块的input_pool熵池且增加了熵池大小,所以对crng初始化有作用;说明:
信任CPU方案的分析如下:
动作 | 效果 | 风险 | 备注 |
---|---|---|---|
使用hw_rng加速crng初始化 | 可以解决问题 | 1. 对无硬随机数外设的SOC无效; 2. hw_rng的可靠性暂无评估(0~1024); 3. 需要修改源码以支持或模块形式加载时附带参数; | 1. F1平台,未实现平台级的随机数获取接口; 2. 厂商的硬件随机数模块未设置quality品质系数; |
结合问题,知道本问题卡顿问题原因为使用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平台 |
硬件熵池收集与扩展器((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加速方案”。
cp /tmp/start.sh /mnt/nfs/T673/
chmod 0777 /mnt/nfs/T673/start.sh
haveged 基于此接口实现; ↩︎
上一篇:一文学完Java集合框架