分类 Linux 相关 下的文章

操作系统获取随机数的进化

最近重新阅读 一书. 之前都是从前往后看, 这次选择从后往前看. 最后一章有一节专门讲 Java 获取随机数的相关类和这些类的对比和使用. 其实内容不仅仅局限于 Java, 而是从整个操作系统层面来看这个问题.

一边阅读, 一边查阅相关资料. 以我的理解它的进化大概有下面几个过程:

  1. 最早想, 无非就是一个随机数吗, 于是就有了一个随机函数, 随机函数怎么增加随机性呢? 那就加一个种子, 种子的值不确定.
  2. 接着发现代码别人是知道的, 所以随机函数就是公开的, 那么唯一的就剩下种子. 但是连续获得多个随机数后, 种子也是可猜测的. 需要找真正的随机数.
  3. 于是发现机器本身能获得一些随机值. 比如: 移动鼠标的性质, 磁盘转动的性质, 网络的性质等. 但是获得这些需要时间, 有时候很慢, 就是要block 的.
  4. 然后发现可以结合真随机值和伪随机数, 两者结合可以就可以做到别人猜测不到. 唯一的问题是有时候要等系统的真随机数还是需要点时间.
  5. 于是就有了获得真随机数的后台持续运行的 deamon 线程, 它可以做到持续的获得, 等到用的时候, 随时给.
  6. 到后来又进化到某些硬件支持.

具体到 Linux 操作系统上面, 就是 /dev/urandom/dev/random 设备和 rngd 守护进程.

为了让理解这个进化更细致, 下面是 Deepseek 的联网回答. 结构很好, 也很有逻辑关系. 唯一的不足感觉就是有点浅.

Linux随机数生成机制的演化与优化历程

在Linux系统中,随机数生成机制经历了从简单伪随机算法到复杂熵源管理、硬件加速的漫长优化过程。这一过程的核心矛盾在于随机性质量性能效率之间的平衡,同时需满足密码学安全的高标准要求。以下是其关键发展阶段及技术动因分析:


一、早期阶段:伪随机数生成器(PRNG)的局限性

技术背景

早期Linux系统依赖伪随机数生成算法(如线性同余法),其核心问题在于种子可预测性。若攻击者获取初始种子,即可推导出完整随机序列。例如,系统时间常被用作默认种子,但时间戳的有限精度(如秒级)导致种子空间狭小,易被暴力破解。

缺陷暴露

  • 安全性不足:SSH、TLS等安全协议依赖随机数生成密钥,伪随机数的可预测性直接威胁加密强度。
  • 确定性缺陷:相同种子生成相同序列,导致测试环境复现问题,但生产环境无法接受此类漏洞。

二、熵源驱动的随机数生成:从理论到实践

熵源的引入

Linux内核通过收集系统事件(如键盘敲击、磁盘I/O延迟、硬件中断)的物理随机性构建熵池(Entropy Pool)。例如:

  • 鼠标移动:用户操作的时间戳和坐标变化具有不可预测性。
  • 磁盘寻道时间:物理磁盘的机械延迟受环境因素影响,产生微小随机波动。
  • 中断计时:硬件中断的到达时间间隔因电路噪声而呈现随机性。

熵池管理机制

  • 熵值计量:内核通过熵估算算法(如Linux内核的rng-core模块)量化熵池的随机性强度,熵值越高,随机数质量越优。
  • 阻塞与非阻塞模式

    • /dev/random:严格依赖熵池,熵不足时阻塞,确保高安全性(如密钥生成)。
    • /dev/urandom:熵不足时切换为伪随机算法(如SHA-256),牺牲部分安全性以提升性能,适用于非安全敏感场景。

技术瓶颈

  • 熵源竞争:多进程并发读取熵池时,可能导致熵值快速耗尽,引发阻塞问题。
  • 启动阶段熵饥饿:系统冷启动时熵源未激活,早期加密操作可能面临低熵风险。

三、算法与架构的优化:从被动收集到主动预测

算法升级

  • Yarrow算法:Linux内核早期采用Yarrow架构,结合熵池与密码学算法(如HMAC-SHA1)生成随机数,平衡安全性与性能。
  • ChaCha20替代:为应对SHA-1碰撞攻击,Linux内核逐步迁移到ChaCha20算法,提升抗侧信道攻击能力。

熵池结构优化

内核将熵池细分为多个子池(如input_poolblocking_pool),分别处理不同类型熵源,并通过动态权重分配优化熵混合效率。

硬件随机数生成器(TRNG)支持

现代CPU和主板集成硬件熵源(如Intel RDRAND指令、TPM芯片),Linux通过/dev/hwrng直接接入硬件随机数,显著提升生成速度与质量。


四、系统级抽象与守护进程的引入

统一接口:getrandom()系统调用

2014年Linux 3.17引入getrandom(),统一用户态接口:

  • 自动选择最优熵源(优先硬件TRNG,其次熵池)。
  • 阻塞行为可控:通过标志位选择是否等待熵池初始化完成。

守护进程优化

  • rngd后台服务:持续监控熵池状态,通过混合熵源(如网络抖动、环境传感器数据)维持熵池活跃度,避免低熵场景。
  • 自适应速率控制:根据熵池负载动态调整伪随机算法的调用频率,平衡性能与随机性质量。

五、现代挑战与前沿方向

量子计算威胁

传统随机数生成算法可能被量子计算机高效破解,Linux社区正探索抗量子算法(如基于格的随机数生成方案)。

容器化环境适配

容器共享宿主机熵池导致随机性竞争,解决方案包括:

  • 容器内嵌轻量级熵源(如基于CPU时间戳抖动)。
  • 增强宿主机熵源分发机制,通过cgroup隔离熵资源。

标准化与审计

Linux内核引入随机数生成器的形式化验证(如FIPS 140-2认证),并定期公开熵源统计报告,增强透明度和可信度。


总结:从“伪随机”到“真随机”的演进逻辑

Linux随机数生成机制的迭代本质上是安全性需求驱动下的工程妥协

  1. 早期:以性能优先的伪随机算法为主,牺牲部分安全性。
  2. 中期:引入熵池和硬件熵源,构建混合生成模式。
  3. 现代:通过算法优化、守护进程和硬件加速,实现安全与性能的动态平衡。

未来,随着量子计算和容器技术的普及,Linux随机数生成机制将更注重抗量子攻击、轻量化与跨平台兼容性。

移动光猫探索

移动光猫 H2-2

解锁 telnet 教程: https://www.cnblogs.com/dingshaohua/p/17388270.html

查看开启端口和服务:

$ nmap -O -sV 192.168.1.1
Starting Nmap 7.80 ( https://nmap.org ) at 2025-01-15 10:33 GMT
Nmap scan report for 192.168.1.1 (192.168.1.1)
Host is up (0.0031s latency).
Not shown: 996 closed ports
PORT     STATE SERVICE
23/tcp   open  telnet     BusyBox telnetd 1.00-pre7 - 1.14.0
80/tcp   open  http       Mini web server 1.0 (ZTE ZXV10 W300 ADSL router http config)
5080/tcp open  onscreen?
8080/tcp open  http-proxy
17998/tcp open  unknown
17999/tcp open  unknown

Service Info: OS: Linux 2.4.17; Device: broadband router; CPE: cpe:/h:zte:zxv10_w300, cpe:/o:montavista:linux_kernel:2.4.17
$ uname -a
Linux zxic 4.1.25 #3 SMP PREEMPT Fri Oct 13 16:33:00 CST 2023 armv7l GNU/Linux

#cpu
/ # cat /proc/cpuinfo
processor       : 0
model name      : ARMv7 Processor rev 1 (v7l)
BogoMIPS        : 1987.37
Features        : half thumb fastmult edsp tls
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x4
CPU part        : 0xc09
CPU revision    : 1

processor       : 1
model name      : ARMv7 Processor rev 1 (v7l)
BogoMIPS        : 1993.93
Features        : half thumb fastmult edsp tls
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x4
CPU part        : 0xc09
CPU revision    : 1

Hardware        : ZTE ZX279128S (Device Tree)
Revision        : 0000
Serial          : 0000000000000000

# 内存
/ # free -m
             total         used         free       shared      buffers
Mem:           450          243          207            6            0
-/+ buffers:                243          207
Swap:            0            0            0

谁打开的 80, 5080, 8080?

/proc/1214 # netstat -tlnp | grep 80
tcp        0      0 192.168.1.1:80          0.0.0.0:*               LISTEN      1214/httpd
tcp        0      0 :::8080                 :::*                    LISTEN      1750/java
tcp        0      0 fe80::1:80              :::*                    LISTEN      1214/httpd
tcp        0      0 ::ffff:192.168.1.1:5080 :::*                    LISTEN      1750/java

关于 80 端口, 也就是管理页面, 使用的是 apache http server.

bash 里 I have no name!

通常情况下, 我们启动 Docker 都是以 root 为当前用户启动 container 的. 但是我们也可以设置以特定的用户来运行 container 里面的主进程.

  1. 加上 --user 参数

    # 这里指定一个不存在的 user id 1000 来执行. 
    docker run --name noNameTest --user 1000:1000 neo4j:latest
  2. 在 Dockerfile 里面设置用户

    USER <user>[:<group>] # 用户名:组名
    USER UID[:GID] # 用户ID:组ID

但是如果你登录进入该 contianer 却看到你的用户名是: I have no name!, 那说明你的 Shell (Bash 或其它) 再向你抱怨, 它无法找到你当前的用户名. 如下面:

I have no name!@my_container:$ 

I have no name!@my_container:$ id
uid=1000 gid=1000 groups=1000
I have no name!@my_container:$ whoami
whoami: cannot find name for user ID 1000

这是因为 Shell 通常去 /etc/passwd 去找这个用户ID, 却找不到.

在 Kubernetes 里面这样以非 root 用户去运行, 可以最小化权限, 防止特权升级, 提高安全性和隔离性.当容器以不存在的用户 ID 和组 ID 运行时,通常这些用户没有在容器内配置 sudo 或 su 的权限,也没有相应的配置文件(如 /etc/sudoers)来允许权限提升.

在 Dockerfile 中使用 USER 指令指定非 root 用户。
在 Kubernetes Pod 的安全上下文(SecurityContext)中指定 runAsUser 和 runAsGroup 来配置非 root 用户运行。

汇编指令把数据从内存一块区域复制到另外一块区域

最近再次阅读 CSAPP 里面的有关汇编的一节, 它说到了 mov 指令, mov 可以把常量, 寄存器的值, 内存的值移动(move)到寄存器, 内存. 但是不能直接把某个地址内存的值移动到另外一个内存地址. 为什么会有这个限制?

如果考虑到当前的值要做计算, 那么这个值从内存加载到CPU, 然后做计算, 之后再保存到内存. 看上去没啥性能损失, 但是假如只是把某个内存的数据复制(移动)到另外一个内存地址, 那么这个性能损失就很大.

为什么会这么想, 这是因为在 这本书里提到这么一个有关延迟度量的对比:
latency scale.png

可以看到从内存加载, 然后再保存到内存需要的相对时间确实很长, 如果有一个方案直接告诉内存控制器, 把某地址开始的多少数据直接复制到另外一块区域, 那将是飞速提升, 因为只要CPU发出一个内存控制器的指令就可以. 但是这看上去很美, 因为内存控制器并不知道进程虚拟内存这些东西, 它只知道硬件物理内存. 虚拟内存要写的2个连续字节可能在物理内存上是相隔很远的两块地方.

如果一个内存数据并不需要做算术逻辑运算, 那么可以使用专门的字符处理指令 movs, 它有对应不同宽度的: movsb, movsw, movsd, movsq. 它们的使用会涉及到 RSI, RDI, RCX 寄存器, 以及 DF flag, 并且结合 REP 指令来实现连续复制.

但是这里我更关心的是, 这些被移动的字节是不是要到CPU绕一圈? 也就是说这些字节是不是需要占用系统总线(system bus). 根据我互联网上查到的数据, 这些字节通常情况下都需要到 CPU 绕一圈, 涉及到加载(load)和保存(save). 也就是避免不了性能的延迟. 但是如果要移动的数据已经在缓存 (L1, L2, L3), 那么将不需要走系统总线加载. 同时这些加载可能都是成块加载, 所以通常连续的内存区域都会同时被一次加载, 提高了性能. 另外从缓存刷新到内存也是成块(页)刷新到, 那么它也会提高性能.

go语言把部分CPU从 AVX 迁移到 REP movsb - https://github.com/golang/go/issues/66958

有关 memcpy

Linux内核中的memcpy函数是一个用于内存复制的函数,它的主要功能是将一块内存中的数据原封不动地复制到另一块内存中。这个函数在内核编程中非常重要,因为它直接操作内存,效率要求非常高。

声明在 string.h 中
void *memcpy(void *dest, const void *src, size_t n);

为了提高效率,Linux内核中的memcpy通常实现为汇编语言,特别是对于小数据块的复制,会有特别的优化。对于更大的数据块,它可能会利用CPU的特定指令(如SSE/AVX指令集)来提高复制速度。

memcpy 可以根据CPU 的指令集选用 movs 系列的指令, 也可以选择 AVX 或 SIMD 来提高在该架构上的性能.

summary

从内存到内存的复制, 还是需要占用系统总线的.

Linux source 命令是 bash 的 builtin

今天执行一个 shell 脚本, 发现 shell 里面报错: source: not found. 可是去执行 source 命令, 发现又有. 到底怎么回事呢?

原来 source 是 bash 的 builtin 命令, 而我运行这个脚本使用的是 sh script.sh, 它其实使用的是 /usr/bin/sh, 然而在这个 Ubuntu 上面 /usr/bin/sh 其实是指向了 /usr/bin/dash. 然而 source 这个命令在 dash 没有.

supra@suprabox:~$ which sh
/usr/bin/sh
supra@suprabox:~$ which bash
/usr/bin/bash

# 没有找到 source 命令
supra@suprabox:~$ which source

# source 的类型
supra@suprabox:~$ type source
source is a shell builtin

# source 命令存在, 当前我的默认shell 是bash
supra@suprabox:~$ source -h
-bash: source: -h: invalid option
source: usage: source filename [arguments]

supra@suprabox:~$ ls -lah /usr/bin/sh
lrwxrwxrwx 1 root root 4 Mar 31  2024 /usr/bin/sh -> dash
supra@suprabox:~$ ls -lah /usr/bin/bash
-rwxr-xr-x 1 root root 1.4M Mar 31  2024 /usr/bin/bash
supra@suprabox:~$ ls -lah /usr/bin/dash
-rwxr-xr-x 1 root root 127K Mar 31  2024 /usr/bin/dash

Bash 内置的命令执行不会启动另外一个进程, 在当前进程执行.

enable -a 启用所有内置命令, 即查看所有内置命令.

supra@suprabox:~$ enable -a
enable .
enable :
enable [
enable alias
enable bg
enable bind
enable break
enable builtin
enable caller
enable cd
enable command
enable compgen
enable complete
enable compopt
enable continue
enable declare
enable dirs
enable disown
enable echo
enable enable
enable eval
enable exec
enable exit
enable export
enable false
enable fc
enable fg
enable getopts
enable hash
enable help
enable history
enable jobs
enable kill
enable let
enable local
enable logout
enable mapfile
enable popd
enable printf
enable pushd
enable pwd
enable read
enable readarray
enable readonly
enable return
enable set
enable shift
enable shopt
enable source
enable suspend
enable test
enable times
enable trap
enable true
enable type
enable typeset
enable ulimit
enable umask
enable unalias
enable unset
enable wait