Linux之random子系统问题解决分享
创始人
2025-05-30 11:37:26
0

文章目录

    • @[toc]
    • 问题信息
      • 问题描述
    • 问题排查
      • 测试一、Hicore卡顿业务定位
        • 结论
      • 测试二、系统资源信息分析
        • 结论
      • 测试三、random相关分析
        • 结论一
        • 结论二
        • 总结
    • 问题拓展
      • random模块
        • 基本概念
        • 接口说明
        • 使用关注
      • hw_random模块
        • 基本概念
        • 接口说明
        • 使用说明
    • 问题解决
      • crng_ready方案
      • trust_cpu方案
      • hw_random方案
      • random加速方案
      • haveged工具
    • 备注
      • 术语与缩写
      • 调试命令

问题信息

问题描述

T673 V1.1项目在集成测试Build 1期间, 使用T673设备升级系统测试Build1整机包测试启动耗时, 发现设备启动至进入应用用时1分钟左右, 耗时较其他项目(T671B等)时间长, 用户体验差,需要修复。

  1. 与应用同事沟通收集信息:
    1. T673 V1.1项目在今年四月份异常结项,未有关注启动耗时优化;
    2. T673 V1.1项目在今年九月份重新开启,至十月下旬的系统测试Build1期间未有关注启动耗时优化;
    3. 在项目重启至系统测试B1之前有合入以下内容:
      1. 触摸屏新物料支持;
      2. PHY新物料支持;
      3. 韦根驱动从基线同步;
      4. 应用程序支持硬解密;
      5. 其他;

问题排查

测试一、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 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分析文件,截图:
在这里插入图片描述

结论

  • 从数据分析:CPU 95.4% idle, CPU占用少,IO占用低, 中断少。
  • 因为禁用uicore启动后,卡顿问题仍旧存在且时间上没有变化,所以排除掉uicore程序占用CPU高而导致的卡顿问题
  • 如上说明top信息分析:CPU占用低、IO占用低、中断触发少,所以卡顿问题与负载无直接关联,可以关注其他性能分析方向,如:
    1. 锁竞争;
    2. 资源同步;
    3. 进程调度;

测试三、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/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);
    
    1. 使用内核dump_stack实现栈回溯打印;
    2. 在random模块初始化位置,增加打印信息;
    3. 在打印random: hicore: uninitialized urandom read (256 bytes read) 之后,打印栈回溯信息;
    4. 在打印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);
      

      调用条件为:

      1. 用户主动调用ioctl(“/dev/random”, RNDADDTOENTCNT(或RNDADDENTROPY), xxx);
        • RNDADDTOENTCNT, 仅增加熵池数量;
        • RNDADDENTROPY, 向熵池增加若干随机字(单位:字节) 1;
      2. 熵池输入源一:add_timer_randomness
        • input_handle_event, input子系统event事件: 键盘、触摸等输入事件;
      3. 熵池输入源二:add_disk_randomness
        • blk_update_bidi_request, 块设备的的BIO请求:读、写磁盘(包括eMMC)动作;
      4. 熵池输入源三:add_interrupt_randomness
        • handle_irq_event_percpu, 所有中断的必经之路:所有硬中断;
      5. 熵池输入源三:add_hwgenerator_randomness
        • hwrng_fillfn, 内核线程"hwrng": 持续写入,最大延时1s;
      6. 其他:
        1. xfer_secondary_pool, 从主熵池推送到次熵池;
        2. 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);
        

        必要条件

        1. crng_ready(),crng初始化完成;

        调用条件为

        1. crng_reseed, 当入参entropy_store(熵池抽象类),即未指定熵池对象,递归一次
        2. extract_crng, 当入参entropy_store(熵池抽象类)为input_pool,即指定主crng;
          1. crng_backtrack_protect, 规避chacha20-key回溯保护机制;
          2. urandom_read, 用户主动调用read(“/dev/urandom”, …);
          3. 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);
        

        必要条件:

        1. crng_ready(),crng初始化完成;

        调用说明:

        1. 用户主动要求主crng重新填充;

        以上调用条件都基于必要条件:crng_ready()。

结论二

综上,有以下结论:

  1. 因为crng_ready()做为前提条件下,只有credit_entropy_bits可以触发主crng初始化完成;

结合问题,random: crng init done由硬件中断回调中使用add_interrupt_randomness向主crng的熵池中添加随机值而触发了主crng的初始化完成,进而唤醒了在等待对列crng_init_wait中挂起的线程(包含Hicore)。

总结

结合urandom_readcrng_reseed的代码跟读,整理出问题流程时序图表示如下:
在这里插入图片描述

结合图示,复述问题过程:

  1. 启动启动中,系统通过中断的方式不断向input_pool熵池填充状态随机数,而blocking_pool为空。

  2. 应用程序Hicore调用系统接口getrandom()获取256个随机数而陷入内核态

  3. 此时input_pool熵池未收到阈值128 bits的随机数而处于处于未初始化状态(crng_init值为1),即crng_read()为假(crng_int值为2时,crng_read()为真)。进而random驱动将Hicore挂起至crng_init_wait等待对列,等待crng初始化完成;

  4. 20s后,终于有"通用中断不断向input_pool熵池填充随机数到达了初始化阈值(128 bit),crng_ready()为真”的条件满足;

  5. 应用程序Hicore从等待对列恢复运行,通过urandom_read读取到256 Bytes随机数后,最终返回到用户状完成"读取随机数"工作。

    其中有步骤4消耗了20s时间,于是有使用上可以感受到的卡顿效果。

简单总结问题原因:应用程序Hicore调用系统接口getrandom尝试获取256 Byte随机数,但因crng未初始化完成,于是Hicore在内核态时被挂起,直至20s后crng初始化完成,Hicore恢复运行。

问题拓展

random模块

基本概念

  1. random模块自持有三个熵池:fast_pool、input_pool、blocking_pool和一个一致性随机数生成器:primary_crng。其中fast_pool较次要,不做说明;
    在这里插入图片描述
    1. 熵池的大小:entropy_count, 计数单位为1/8 bit,所以有计算熵池大小宏ENTROPY_BITS®,定义如下:

      #define ENTROPY_SHIFT 3
      #define ENTROPY_BITS(r) ((r)->entropy_count >> ENTROPY_SHIFT)
      
    2. 随机数导出接口

      • extract_entropy,对应熵池:input_pool、blocking_pool;
      • extract_crng,对应熵池:primary_crng;
    3. 熵池变化:

      • credit_entropy_bits,增加,单位:bit;
      • account,消耗,单位:byte;
  • 熵池输入(四个输入源)、输出(三个输出接口)的关系
    在这里插入图片描述

接口说明

  1. random、urandom、getrandom三种方式差异

    • /dev/random,read
      在这里插入图片描述

    • /dev/urandom,read
      在这里插入图片描述

    • getrandom
      在这里插入图片描述

    以下对接口归纳说明如下:

    接口IO类型备注
    /dev/urandom,read1. 同步非阻塞
    2. 异步
    /dev/random,read1. IO多路复用
    2. 异步
    真随机数
    getrandom1. 同步阻塞集成random与urandom,取决于flags

使用关注

  1. 熵池更新策略与实现?

    • input_pool 更新策略:
      1. 由随机数熵池章节可知,input_pool熵池不断由4个输入源更新;
      2. 用户主动调用ioctl(RNDADDTOENTCNT/RNDADDENTROPY,…)更新;
    • blocking_pool更新策略:
      1. 每次“真随机数”读取前更新;
      2. input_pool 水位阈值触发时更新;
    • primary_state更新策略:
      1. 每次随机数读取后更新;
      2. 用户调用ioctl(RNDRESEEDCRNG,…)更新;
  2. 消耗熵池生成随机数?

    从读取随机数的入口而言,仅有两个熵池与随机数生成有关联:blocking_pool与primary_crng;

    1. blocking_pool熵池与随机数生成;
    2. primary_crng熵池与随机数生成;
  3. crng_ready()控制什么状态?

    从crng_ready()定义分析

    static int crng_init = 0;
    #define crng_ready() (likely(crng_init > 1))
    

    可知,crng_ready()关联的是primary_crng熵池,且crng_init变量只会单调增长。其值分别代表如下意义

    说明备注
    0初始值random驱动加载初始化状态
    1primary_crng从fast_pool第一次填充完成fast_pool熵池,已经收集64次数据
    日志打印:random: fast init done
    每个CPU都私有一个fast_pool,每次中断处理都混写一次,64次之后认为fast初始化完成,
    2primary_crng从input_pool第一次填充完成input_pool熵池,已经收集128 bit(16字节)数据;
    日志打印:random: crng init done
    从input_pool熵池导出16字节简单处理后给到crng,完成crng的初始化;
  4. 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,read1. 同步阻塞真随机数

使用说明

  1. hw_random与random两个模块之间联系?

    random对外有两个随机数更新接口,hw_random使用这个两个接口实现了不同的目的:

    1. add_device_randomness, 只混写input_pool熵池,但不增加熵池数量,用于前期初始化;
    2. add_hwgenerator_randomness,混写且增加input_pool熵池数据,用于定期更新熵池;
  2. 如何使用hw_random快速填充random的input_pool完成crng初始化?

    由硬件随机数模块的注册函数可知:当新rng注册到hw_random时,会检查目标rng的品质参数quality,再决定是否启动定期更新input_pool的内核线程。

    quality即随机数的品质系数,等级范围:0~1024。这个系数决定了两个关键信息:

    1. 决定是否启动内核线程hwrng_fill,在线程运行期间向input_pool熵池增加熵值;
    2. 决定随机数输入到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的初始化过程,其中需要具备以下两个前提:

  1. 开启内核配置宏CONFIG_RANDOM_TRUST_CPU,该宏由Kconfig读法决定。当该宏的信赖未开启时无法配置;
  2. 需要实现平台随机数实现接口: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 #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)

hw_random方案

F1平台的硬件随机数生成,对应注释到Linux的hwrng子系统下。使用了random模块的两个回调:

  1. add_device_randomness, 只混写了random模块的input_pool熵池,但未增加熵池的大小,所以对crng初始化无直接帮助
  2. add_hwgenerator_randomness, 混写了random模块的input_pool熵池且增加了熵池大小,所以对crng初始化有作用

说明:

  1. nvt_rng,未设置质量评估值quarity,需要修改;
  2. 启动阶段预设置才有效;

信任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加速方案”。

备注

术语与缩写

  1. CRNG(congruent random number generator, 一致性随机数生成器);

调试命令

cp /tmp/start.sh /mnt/nfs/T673/
chmod 0777 /mnt/nfs/T673/start.sh
  1. add_hwgenerator_randomness, 增加硬件随机数发生器;

  1. haveged 基于此接口实现; ↩︎

相关内容

热门资讯

Javascript学习笔记—... 文章目录数据类型通过控制台颜色判断数据类型数字类型特殊数据类型字符串转义字符字符串的不可变性不同数据...
5自由度串联机械臂实现搬运物品... 1. 功能描述     本文提供的示例所实现的功能为:实现5自由度串联机械臂搬运物品的...
Application使用内核... 环境 $ cat /etc/os-release PRETTY_NAME="Ubuntu ...
监督年度工作计划 监督年度工作计划汇总10篇  时间的脚步是无声的,它在不经意间流逝,我们又将迎来新的喜悦、新的收获,...
平安建设工作计划 精选平安建设工作计划(通用9篇)  时间过得可真快,从来都不等人,成绩已属于过去,新一轮的工作即将来...
小学美术教学工作计划 小学美术教学工作计划兴隆小学美术第四册教学工作计划兴隆小学:曾素梅又一期来临,我们将继续以课堂教学研...
C++构造函数详解 在C++中,有一种特殊的成员函数,它的名字和类名相同&#...
windows安装jenkin... 官网 安装jdk环境 需要jdk11或者jdk17 下载jdk17 下载解压设置环境变量 JAV...
远程教育工作计划 精选远程教育工作计划范文集合5篇  时间就如同白驹过隙般的流逝,成绩已属于过去,新一轮的工作即将来临...
秋季初中德育工作计划 秋季初中德育工作计划(精选3篇)  日子在弹指一挥间就毫无声息的流逝,迎接我们的将是新的生活,新的挑...
驻村工作计划 驻村工作计划集合六篇  时光在流逝,从不停歇,我们的工作又进入新的阶段,为了今后更好的工作发展,该为...
MatBox—基于PyQt快速... MatBox—基于PyQt快速入门matplotlib的教程库 __ __ ...
公司职员个人年度工作计划 公司职员个人年度工作计划  日子如同白驹过隙,迎接我们的将是新的生活,新的挑战,写一份工作计划,为接...
XICE-HUAWEI-超级完... XICE-HUAWEI-超级完整的BGP-2 上一章的总结 BGP-大型路由协议,即可...
小学教育教学工作计划 小学教育教学工作计划范文(精选3篇)  时间流逝得如此之快,我们又将奔赴下一阶段的教学,该好好计划一...
春季新学期团委工作计划 2020年春季新学期团委工作计划  时光飞逝,时间在慢慢推演,成绩已属于过去,新一轮的工作即将来临,...
快手发布38大促战报,快手电商... 1.快手电商开展直播间“虚假承诺福利”专项治理近日,快手电商发布公告称,...
C语言--指针 目录1、指针1.1 什么是指针?1.2 指针的NULL1.3 指针的比较 > < &#...
全网最详细,Jmeter性能测... 目录:导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目...
刑释解教人员安置帮教工作计划 刑释解教人员安置帮教工作计划  为切实做好刑满解教人员的安置帮教工作,预防和减少重新犯罪,降低恶性案...