【SemiDrive源码分析】【X9芯片启动流程】30 - AP1 Android Kernel 启动流程 start_kernel 函数详细分析(一)

news/2024/10/22 17:17:52/

【SemiDrive源码分析】【X9芯片启动流程】30 - AP1 Android Kernel 启动流程 start_kernel 函数详细分析(一)

  • 一、Android Kernel 启动流程分析
    • 1.1 入口汇编代码 arch\arm64\kernel\head.S : 跳转start_kernel() 入口函数
    • 1.2 入口函数 start_kernel()
    • 1.3 一号进程 init_task 结构体


本 SemiDrive源码分析 之 Yocto源码分析 系列文章汇总如下:

  1. 《【SemiDrive源码分析】【Yocto源码分析】01 - yocto/base目录源码分析(编译环境初始化流程)》
  2. 《【SemiDrive源码分析】【Yocto源码分析】02 - yocto/meta-openembedded目录源码分析》
  3. 《【SemiDrive源码分析】【Yocto源码分析】03 - yocto/meta-semidrive目录及Yocto Kernel编译过程分析(上)》
  4. 《【SemiDrive源码分析】【Yocto源码分析】04 - yocto/meta-semidrive目录及Yocto Kernel编译过程分析(下)》
  5. 《【SemiDrive源码分析】【Yocto源码分析】05 - 找一找Yocto Kernel编译过程中所有Task的源码在哪定义的呢?》
  6. 《【SemiDrive源码分析】【Yocto源码分析】06 - Kernel编译生成的Image.bin、Image_nobt.dtb、modules.tgz 这三个文件分别是如何生成的?》
  7. 《【SemiDrive源码分析】【Yocto源码分析】07 - core-image-base-x9h_ref_serdes.rootfs.ext4 文件系统是如何生成的》
  8. 《【SemiDrive源码分析】【X9芯片启动流程】08 - X9平台 lk 目录源码分析 之 目录介绍》
  9. 《【SemiDrive源码分析】【X9芯片启动流程】09 - X9平台系统启动流程分析》
  10. 《【SemiDrive源码分析】【X9芯片启动流程】10 - BareMetal_Suite目录R5 DIL.bin 引导程序源代码分析》
  11. 《【SemiDrive源码分析】【X9芯片启动流程】11 - freertos_safetyos目录Cortex-R5 DIL2.bin 引导程序源代码分析》
  12. 《【SemiDrive源码分析】【X9芯片启动流程】12 - freertos_safetyos目录Cortex-R5 DIL2.bin 之 sdm_display_init 显示初始化源码分析》
  13. 《【SemiDrive源码分析】【驱动BringUp】13 - GPIO 配置方法》
  14. 《【SemiDrive源码分析】【X9芯片启动流程】14 - freertos_safetyos目录Cortex-R5 SafetyOS/RTOS工作流程分析》
  15. 《【SemiDrive源码分析】【X9芯片启动流程】15 - freertos_safetyos目录 R5 SafetyOS 之 tcpip_init() 代码流程分析》
  16. 《【SemiDrive源码分析】【X9 Audio音频模块分析】16 - 音频模块框图及硬件原理图分析》
  17. 《【SemiDrive源码分析】【X9芯片启动流程】17 - R5 SafetyOS 之 LK_INIT_LEVEL_PLATFORM 阶段代码流程分析(上)dcf_init 核间通信初始化》
  18. 《【SemiDrive源码分析】【X9芯片启动流程】18 - R5 SafetyOS 之 LK_INIT_LEVEL_PLATFORM 阶段代码流程(下)启动QNX、Android》
  19. 《【SemiDrive源码分析】【X9芯片启动流程】19 - MailBox 核间通信机制介绍(理论篇)》
  20. 《【SemiDrive源码分析】【X9芯片启动流程】20 - MailBox 核间通信机制介绍(代码分析篇)之 MailBox for RTOS 篇》
  21. 《【SemiDrive源码分析】【X9芯片启动流程】21 - MailBox 核间通信机制介绍(代码分析篇)之 Mailbox for Linux 篇》
  22. 《【SemiDrive源码分析】【X9芯片启动流程】22 - MailBox 核间通信机制介绍(代码分析篇)之 RPMSG-VIRTIO Kernel 篇》
  23. 《【SemiDrive源码分析】【X9芯片启动流程】23 - MailBox 核间通信机制介绍(代码分析篇)之 RPMSG-IPCC Kernel 篇》
  24. 《【SemiDrive源码分析】【X9芯片启动流程】24 - MailBox 核间通信机制相关寄存器介绍》
  25. 《【SemiDrive源码分析】【X9芯片启动流程】25 - MailBox 核间通信机制介绍(代码分析篇)之 RPMSG-IPCC RTOS & QNX篇》
  26. 《【SemiDrive源码分析】【X9芯片启动流程】26 - R5 SafetyOS 之 LK_INIT_LEVEL_TARGET 阶段代码流程分析(TP Drvier、Audio Server初始化)》
  27. 《【SemiDrive源码分析】【X9芯片启动流程】27 - AP1 Android Preloader启动流程分析(加载atf、tos、bootloader镜像后进入BL31环境)》
  28. 《【SemiDrive源码分析】【X9芯片启动流程】28 - AP1 Android SMC 指令进入 EL3 环境执行 ATF 镜像(加载并跳转 bootloader)》
  29. 《【SemiDrive源码分析】【X9芯片启动流程】29 - AP1 Android Bootloader启动流程分析(加载并跳转kernel)》
  30. 《【SemiDrive源码分析】【X9芯片启动流程】30 - AP1 Android Kernel 启动流程 start_kernel 函数详细分析(一)》
  31. 《【SemiDrive源码分析】【X9芯片启动流程】31 - AP1 Android Kernel 启动流程 start_kernel 函数详细分析(二)》
  32. 《【SemiDrive源码分析】【Display模块】32 - RTOS侧 Serdes屏驱动硬件原理及代码配置步骤》
  33. 《【SemiDrive源码分析】【Display模块】33 - 相关概念解析》
  34. 《【SemiDrive源码分析】【Display模块】34 - RTOS侧 sdm_display_init 显示初始化源码分析》
  35. 《【SemiDrive源码分析】【Display模块】35 - RTOS侧 sdm_display_init 显示初始化源码分析 之 MIPI DSI、LVDS屏驱动探测初始化流程》
  36. 《【SemiDrive源码分析】【Display模块】36 - Android侧 DRM代码分析》
  37. 《【SemiDrive源码分析】【驱动BringUp】37 - LCM 驱动 Bringup 流程》
  38. 《【SemiDrive源码分析】【驱动BringUp】38 - NorFlash & eMMC分区配置》

待写:
28. 《【SemiDrive源码分析】【X9芯片启动流程】28 - MailBox 核间通信机制介绍(代码分析篇)之 Property篇》
29. 《【SemiDrive源码分析】【X9芯片启动流程】29 - MailBox 核间通信机制介绍(代码分析篇)之 RPCall篇》
30. 《【SemiDrive源码分析】【X9芯片启动流程】30 - MailBox 核间通信机制介绍(代码分析篇)之 Notify篇》
31. 《【SemiDrive源码分析】【X9芯片启动流程】31 - MailBox 核间通信机制介绍(代码分析篇)之 Socket篇》
32. 《【SemiDrive源码分析】【X9芯片启动流程】32 - MailBox 核间通信机制介绍(代码分析篇)之 /dev/vircan篇》
33. 《【SemiDrive源码分析】【Display模块】36 - RTOS侧 LVGL 开源GUI库分析》




边看边总结,不知不觉已经1个多月了,已经写到第30篇文章了,
主要目的,还是支持芯片国产化,加上之前调高通、MTK时,自已带了这么多个项目,却没有想过去写一套完整的总结出来,有点小遗憾,
干脆就从芯驰开始补足这个遗憾,每天抽时间出来总结,希望整理一套完整的总结出来,尽量做到从入门到入土(哈哈,开玩笑的)。
希望的话,这个系列的文章,我能坚持维护到项目量产,
内容争取 涵盖 启动流程代码分析、BSP模块代码移植、各模块硬件工作原理、各模块代码框架从底层到上层的分析、以及项目中遇到的问题分析过程及总结等等,毕竟是私人总结,视精力而定,能写多少写多少,有啥写啥,能共享就共享,不能共享就设私密,这个没啥好说的。

主要目标还是:
芯驰X9HP平台全套总结文档,函盖如下:

  1. 启动流程代码分析
  2. BSP模块代码移植
  3. 各模块硬件工作原理
  4. 各模块代码框架分析
  5. 项目驱动调试过程
  6. 驱动调试过程实战问题分析

本专栏我会持续维护直到项目进入量产,项目中遇到的所有有意义的问题,均会记录分析思路,争取做到,看着本文就能做到


前面刚开始写的文章分析的相对详细,加上平台大差不差,结合之前高通MTK调试经验比较丰富,所以后面对芯驰也越来越熟悉,
主要是时间紧急,一些简单的 或者 对项目意义不大的代码,我也适当的做了省略。
主要目的,还是通过对启动流程代码的分析,从而对整个芯驰X9平台启动流程中有一定的了解,
没必要太深,简单来说,知道哪些时间做了哪些事就够了,
熟悉启动流程最直接的受益就是回板后的 Bringup,比如回板后开不了机,从log就能看到死在哪个阶段了,结合这个段阶要做的事,就很好分析回板不开机的问题,或者说后续项目中要定制一些开机过程中的需求,清楚开机流程就知道需求代码加在哪会相对更加合适,等等。

一些项目中 BSP 会涉及的模块,我也做了省略,如 开机过程中Display我就没怎么分析,
因为我后续会单独起文章来深入分析,主要时间不允许,现在也就没必要看这么深,
这些模块我们后续会从硬件工作原理、代码如何移植、参数如何分析、模块系统架构这几个方面深入分析,敬请期待吧。

从本文开始,我们正式进入安卓分析 Kernel 启动流程,按照计划如下:

  1. Kernel 启动流程,主要分析看看 start_kernel 和 之前调试的高通平台有什么差别。
  2. Android System 启动流程:比如 init.rc 解析等。

因为QNX暂时我不用关心,所以QNX710 源码不急,等后面有时间再分析吧,
等分析完kernel + android System 后,那启动流程这块也算看完了,也该准备项目相关模块了,

预计20号开始准备项目,预留20多天,差不多,这20多天,主要是跟各供应商要到相关的代码、资料及FAE联系方式,
同时提前开始做项目配置,移植驱动代码到项目代码中,争取回板前能出一版全功能的镜像出来
(代码全功能就行,至行实际功能通不通,不知道 ,也无所谓,回板后再分析嘛)

部分计划如下:

  1. 分区表配置:检查配置Flash 相关的参数、根据项目需求配置分区表及分区大小
  2. 检查默认的GPIO配置:主要是根据硬件原理图,配置好RTOSKernel 中的默认GPIO状态
  3. 显示模块 代码移植 + 参数解析 (含 LVDSTouchScreen
  4. DPDC Layer 图层相关原理分析
  5. 摄像头模块 代码移植 + 参数解析(含 CVDS

以上这些都是暂定的学习计划,没写在里面的也并不代表不分析,后面看啥写啥, 有啥写啥,做啥写啥,加油。

由于前面分析的是基线代码,这些代码只要做芯驰的程序员都能看到,这些不涉及机密 ,
等项目启动后,有些可能会涉及项目内容的文章,我写好后,不敢共享,就会设为私密文章,只能我一个人看得到,
所以后面兄弟们如果碰到文章断层,那就可能设了私密,断层部分就是私密文章,还请见谅,哈哈。

最后,购买了本专栏的兄弟,如果遇到问题可以在我博客留言,不能说百分之百解决,
但在我精力允许的范围内,我可以协助一起分析,给出分析思路,
毕竟三个臭皮匠顶一个诸葛亮,多一个人一起分析,总归对问题有帮助。
(视精力而定吧,能帮忙就帮忙,但如果我本人确实工作太忙的话,那我也会直接明说)


前面废话了一大堆,我们正式进入主题吧,加油 ^_^

一、Android Kernel 启动流程分析

1.1 入口汇编代码 arch\arm64\kernel\head.S : 跳转start_kernel() 入口函数

初始化 CPU、页表、MMU 等,最后跳转 start_kernel 函数

# buildsystem\android\kernel\arch\arm64\kernel\head.S
/* Kernel startup entry point.* ---------------------------* The requirements are:*   MMU = off, D-cache = off, I-cache = on or off,*   x0 = physical address to the FDT blob.* This code is mostly position independent so you call this at __pa(PAGE_OFFSET + TEXT_OFFSET).** Note that the callee-saved registers are used for storing variables that are useful before the MMU is enabled. * The allocations are described in the entry routines.  */__HEAD
_head:/* DO NOT MODIFY. Image header expected by Linux boot-loaders. */b	stext				// branch to kernel start, magic.long	0				// reservedle64sym	_kernel_offset_le		// Image load offset from start of RAM, little-endianle64sym	_kernel_size_le			// Effective size of kernel image, little-endianle64sym	_kernel_flags_le		// Informative flags, little-endian.quad	0				// reserved.quad	0				// reserved.quad	0				// reserved.ascii	"ARM\x64"			// Magic number.long	0				// reserved__INIT/** The following callee saved general purpose registers are used on the primary lowlevel boot path:*  Register   Scope                      Purpose*  x21        stext() .. start_kernel()  FDT pointer passed at boot in x0*  x23        stext() .. start_kernel()  physical misalignment/KASLR offset*  x28        __create_page_tables()     callee preserved temp register*  x19/x20    __primary_switch()         callee preserved temp registers     */
ENTRY(stext)bl	preserve_boot_args===============================> //# buildsystem\android\kernel\arch\arm64\kernel\head.S+		// 1. 将dtb_p地址存放在 x21寄存器中,在 boot kernel过程中, arg0=dtb_p,arg1=0+		mov	x21, x0				// x21=FDT+		// 2. 将 boot_args 的偏移地址保存在 x0 中+		adr_l	x0, boot_args			// record the contents of	+		// 3. 将x21, x1, x2, x3保存到[x0]指向的地址,也就是boot_args数组中+		stp	x21, x1, [x0] // x0 .. x3 at kernel entry +		stp	x2, x3, [x0, #16]+		// 4. 将写入的数据同步到内存中, 类似 fs_sync 的功能+		dmb	sy				// needed before dc ivac with+							// MMU off+		// 5. 将 0x20 写入 x1 寄存器,此时 x0=&boot_args[], x1=0x20+		mov	x1, #0x20			// 4 x 8 bytes+		b __inval_dcache_area		// tail call<===============================// 6. 初始化 EL2 环境,配置 cpu boot mode,初始化页表bl	el2_setup			// Drop to EL1, w0=cpu_boot_modeadrp	x23, __PHYS_OFFSETand	x23, x23, MIN_KIMG_ALIGN - 1	// KASLR offset, defaults to 0bl	set_cpu_boot_mode_flagbl	__create_page_tables	/* The following calls CPU setup code, see arch/arm64/mm/proc.S for details.* On return, the CPU will be ready for the MMU to be turned on and the TCR will have been set. */// 7. 初始化CPUbl	__cpu_setup			// initialise processor// 8. 使能MMU,重定位kernel 地址,将kernel 偏移放入 X0 寄存器中,跳转到__primary_switched 函数b	__primary_switch
ENDPROC(stext)__primary_switch:bl	__enable_mmubl	__relocate_kernelldr	x8, =__primary_switchedadrp	x0, __PHYS_OFFSETblr	x8/* The following fragment of code is executed with the MMU enabled.* x0 = __PHYS_OFFSET    */  
__primary_switched:adrp	x4, init_thread_union	// 将 init_thread_union 的地址保存在 X4 中	add	sp, x4, #THREAD_SIZE		// 设置堆栈指针SP的值,就是内核栈的栈底+THREAD_SIZE的大小adr_l	x5, init_task			// 将 init_task 地址保存在 x5 中msr	sp_el0, x5					// Save thread_infoadr_l	x8, vectors				// load VBAR_EL1 with virtualmsr	vbar_el1, x8				// vector table addressisb								// 指令同步隔离, 等待将前面处于指令流水线中的所有指令运行完stp	xzr, x30, [sp, #-16]!mov	x29, spstr_l	x21, __fdt_pointer, x5		// Save FDT pointerldr_l	x4, kimage_vaddr			// Save the offset betweensub	x4, x4, x0						// the kernel virtual andstr_l	x4, kimage_voffset, x5		// physical mappings// Clear BSSadr_l	x0, __bss_start				// 清空 BSS 栈mov	x1, xzradr_l	x2, __bss_stopsub	x2, x2, x0bl	__pi_memset// 确保屏障前面的存储指令执行完毕,dsb是数据同步屏障,ishst中ish表示共享域是内部共享,st表示存储 ,ishst表示数据同步屏障指令对所有核的存储指令起作用dsb	ishst				// Make zero page visible to PTWadd	sp, sp, #16mov	x29, #0mov	x30, #0b	start_kernel		// 正式跳转 start_kernel 函数
ENDPROC(__primary_switched)

1.2 入口函数 start_kernel()

入口函数 start_kernel() 主要工作如下:

  1. 设置内核 1号任务init_task的栈最低地址,使得init_task->stack向栈底最后一个long的地址,主要作用是栈溢出检测配置

  2. 触发CPU中断来获取ID,返回当前正在执行初始化的处理器ID,默认为CPU0, 会打印Booting Linux on physical CPU 0x0

  3. 初始化hash buckets,最大1<<14 = 16384个对象 ,将static object poll ( obj_static_pool[i] ) 中的所有元素到链表中,最大为1024个元素,链表表头为 obj_pool

  4. 初始化cgroupcgroup是一种将进程按组管理的机制,其根节点为 struct cgroup_root cgrp_dfl_root
    cgroup 可以把 linux 系统中所有进程组织成树的形式,每颗树都包含系统中所有的进程,
    树的每一个节点就是一个进程组,每颗树又和一个或多个 subsystem 相关联。

  5. 屏蔽当前CPU0上的所有中断,此时送到当前CPU0上的所有中断信号都会被忽略,配置全局变量early_boot_irqs_disabled=true

  6. 更新当前CPU0 相关状态变量的值,使能为 online、active、present、possibletrue

  7. 初始化高端内存,主要是初始化page_address_htable 链表,每当要申请映射一块 Highmem空间时,就会的相应的信息添加到 page_address_htable 链表中

  8. 打印 Linux Version 系统版本号等信息

  9. 为解析cmdline做准备,初始化ioremapmemlockpaging分页等内存映射相关的代码,将 command_line 指向 boot_command_line对应的内存; 调用cpu_prepare 初始化所有可用的 cpu 核心,注意此时还是单核 CPU0 在运行

  10. 这个没看懂里面的 input_pool 的作用是啥

  11. 调用 get_random_bytes获取一个内核随机数,赋值给canary,保存在 init_task->stack_canary 中,主要目的还是防止栈溢出。
    按我的理解是,代码会把这个canary值写在栈的最后一个long整形位置了,由于它是一个随机数,理论上通过读取它的值是否改变就能知道是否发现了栈溢出问题

  12. 初始化清除cpumask中的所有CPU

  13. 调用__alloc_bootmembootmem中申请 saved_command_lineinitcall_command_linestatic_command_line三块空间,将 boot_command_line的内容拷贝到 saved_command_line,将 setup_arch()kernel 中动态生成的 command_line 内容,保存在 static_command_line

  14. nr_cpu_ids是指具有当前系统能够使用的CPU数,默认值为NR_CPUS=1值,它使用的是bit位的形式表示,类型是unsigned int32位,所以当前kernel最大支持32CPU同时使用

  15. 系统为多核 CPUpercpu 空间申请了对应的内存用于保存每个CPU 的私有数据,这块内存只有对应的CPU能够读写,其他CPU无权限读写。代码中通过__per_cpu_offset 数组来记录每个 CPUpercpu 区域中私有数据地址距离 __per_cpu_start 起始地址的偏移量。我们访问每 CPU 变量就要依靠 __per_cpu_offset + __per_cpu_start 来确定访问地址。

  16. 配置 CPU0->state = CPUHP_ONLINE

  17. 配置当前 CPU0percpu域地址偏移,将当前CPUcpuinfo_arm64结构体信息写入percpu对应的内存中,同时写放当前CPU的运行环境(是否是运行在HYP mode),同时更新CPU负载能力等信息

  18. 启动Boot pageset table页面集表,初始化所有可用的页表数 保存在 vm_total_pages
    每个cpu一个Boot pageset table,用于所有区域和所有节点。设置参数的方式是,将列表中的项目立即移交给好友列表。
    这是安全的,因为页面集操作是在禁用中断的情况下完成的。
    即使在未使用的处理器和/或区域的引导完成后,也必须保留boot_pagesets 页面集,它们确实在引导热插拔处理器方面发挥了作用。

  19. 页表分配初始化,为系统设置一个page_alloc_cpu_notify回调函数,该函数用来实现CPU的关闭与使能。
    在一个MPP结构的处理器系统或者大型服务器中有大量的CPU,该函数可以临时打开或者关闭某些Core或者CPU,此时Linux系统会调用page_alloc_cpu_notify函数。

  20. 打印 bootloader 传入的command_line 内容

  21. 解析command_line中的 early_param参数,调用 parse_args来解析 command_line字符串,解析后如果遇到console/earlycon相关的信息,则调用其setup_func(), 这个函数是 early_param结构体中自带的

  22. 解析所有 static_command_line 中的信息,这些起信息是通过 setup_arch(&command_line) 来生成的,最终会通过 params[i].ops->set(val, &params[i]) 来将其设置在params[i]

  23. 配置 static_key_initialized = true

  24. 初始化 kernel log buffer,同时把 kernel启动log拷贝进来
    之前项目中,经常因为kernel logbuff 太少抓不到完整的kernel log时,就会把 CONFIG_LOG_BUF_SHIFT值改大,
    它作用的地方就是__log_buf,
    __log_buf[]是一个静态数组,数组大小就是 1 << CONFIG_LOG_BUF_SHIFT,这块内存使用的是在静态变量区的内存

  25. 初始化进程 pidhash 双向链表表头 pid_hash
    pid哈希表根据机器中的内存量进行缩放。从最少16个插槽到4096个插槽,每GB或更多。
    进程有自己单独的双向链表叫进程链表,而当我们要知道某个进程的状态时,每个进程描述符是通过PID来区分的,因为遍历查找效率太低了,且浪费CPU,此时就是通过pidhash来实现高效的快速查找

  26. Linux文件子系统的 vfs caches早期初始化,初始化 dentry cacheinode cache
    初始化 目录项高速缓存dhash_entriesdentry_hashtable 和 文件索引节点缓存 ihash_entriesinode_hashtable,其中entries用于保存数的居, hastable用于快速索引。
    Linux为了提高目录项对象的处理效率,设计与实现了目录项高速缓存(dentry cache,简称dcache),
    目录项高速缓存dcache是索引节点缓存icache的主控器(master),也即dcache中的dentry对象控制着icache中的inode对象的生命期转换。
    dentryinode是多对一的,每个inode可能有多个dentry,如硬链接的dentry不一样,inode却一样
    因此无论何时,只要一个目录项对象存在于dcache中(非 negative状态),则相应的inode就将总是存在,因为inode的引用计数i_count总是大于0,当dcache中的一个dentry被释放时,针对相应 inode对象的 iput()方法就会被调用。

  27. 整理内核异常向量表 exception table,分别保存在__start___ex_table__stop___ex_table中,它们保存在__ex_table段中

  28. oopskernel异常BUG处理函数的初始化,保存在bug_break_hook.fn 中,
    在处理函数中如果是BUG Type, 则调用oops_enter()console_verbose()等打印或保存相关的环境现场,
    然后调用panic()触发一个Fata exception 中断

  29. 内存管理相关初始化:
    (1)page_ext_init_flatmem() 分配page_ext所需的内存空间,page_ext主要是用于保存调用栈信息,每个page页都会在页初始化时存储好相关的调用栈信息

    (2)mem_init():标记内存映射中的可用区域,并告诉我们有多少内存可用
    (2.1)free_unused_memma():释放所有未使用的内存映射
    (2.2)free_all_bootmem()bootmem是开机过程中建立的一个非常简单的临时内存管理系统,
    所以当快开机结束时,它也就没有存在的必要了,因为后续会初始化更高级更完善的内存管理系统来接管它,
    所以free_all_bootmem释放的不是已经申请的内存,而是bootmem没分配出去的内存,
    调用free_all_bootmem以后bootmem就把自身也释放掉了,不可用了,之后会用更高级的内存管理系统,
    (2.3)kexec_reserve_crashkres_pages():初始化 kdump所需要的pages内存,即基于kexec的崩溃转储机制所需要的pages内存
    (2.4)mem_init_print_info(NULL):统计各内存信息,最后打印当前系统所有的Virtual kernel memory layout

    (3)kmem_cache_init():初始化slab 内存分配器, 初始化 slab分配器的 slab_caches全局链表
    申请一些 kmem_cache内存保存在 kemem_cache->list链表上,内存大小取决于nr_node_ids & nr_cpu_ids
    然后将 kemem_cache->list链表添加到 slab_caches全局链表中,
    然后创建 kmem_cache_node数组,用于管理 kmem_cache链表上的内存

    (4)pgtable_init():页表缓存 Page Cache初始化
    Page Cache 的主要作用还是加速内存的访问,省去访问重复内容时频繁进行 IO操作,这个效率太慢了。
    因为,如果CPU如果要访问外部磁盘上的文件,需要首先将这些文件的内容拷贝到内存中,由于硬件的限制,从磁盘到内存的数据传输速度是很慢的,但如果,把这些page的内容提前加载到 cache内存中的话,CPU访问起来就会快速很多。
    CPU 查找内存时,首先会在 page cache上找,如果 hit 命中了,也就是cache上刚好有所需要的内容,就直接读取,
    如果未命中的话,再启动 IO 操作,将所需要的内容归属的一个page,全部加载到 page cache中,下次如果还要访问这块数据,就不需要再进行 IO操作了,直接从page cache上读取就好了。
    同理,因为 DDR物理内存有限,不可能无止境的加载 page,同样也会有相应的淘汰机制,
    一般 page cache淘汰规则:是结合 最久未访问和访问次数来综合考量的,一般来说越久未被访问的page越容易被淘汰,同样的时间,访问次数越少的 page也会相对越容易被淘汰。
    Linux系统使用的是最近最少使用(LRU)页面的衰老算法。

    (5)vmalloc_init():虚拟内存管理初始化
    vmalloc用于分配虚拟地址连续(物理地址不连续)的内存空间,vzmalloc相对于vmalloc多了个清零初始化步骤;
    vmalloc/vzmalloc分配的虚拟地址范围在VMALLOC_START/VMALLOC_END之间,属于堆内存;
    内核vmalloc区具体地址空间的管理是通过vmap_area管理的,该结构体记录整个区间的起始和结束;
    vmalloc是内核出于自身的目的使用高端内存页的少数情形之一
    vmalloc 在申请内存时逐页分配,确保在物理内存有严重碎片的情况下,vmalloc仍然可以工作

    vmalloc_init() 函数主要工作为:
    遍历每个CPUvmap_block_queuevfree_deferred
    vmap_block_queue是非连续内存块队列管理结构,主要是队列以及对应的保护锁
    vfree_deferredvmalloc的内存延迟释放管理
    接着将挂接在 vmlist链表的各项通过 __insert_vmap_area()输入到非连续内存块的管理中
    最终配置全局静态变量 vmap_area_pcpu_hole = VMALLOC_END 标志其末尾地址

    虚拟内存技术主要目的还是解决 如何在有限的内存空间运行较大的应用程序的问题
    实践和研究都证明:一个应用程序总是逐段被运行的,而且在一段时间内会稳定运行在某一段程序里。
    这就给虚拟内存技术的应用提供了理论基础。
    为了让程序顺利运行,最简单的就把要运行的那一段程序复制到内存中来运行,而其他暂时不运行的程序段就让它仍然留在磁盘上待命。
    在计算机技术中,把内存中的程序段复制回磁盘 的做法叫做“换出”,而把磁盘中程序段映射到内存的做法叫做“换入”。
    经过不断有目的的换入和换出,处理器就可以运行一个大于实际物理内存的应用程序了。
    或者说,处理器似乎是拥有了一个大于实际物理内存的内存空间。
    于是,这个存储空间叫做虚拟内存空间,而把真正的内存叫做实际物理内存,或简称为物理内存。

    一个系统采用了虚拟内存技术,那么它就存在着两个内存空间:虚拟内存空间和物理内存空间。
    虚拟内存空间中的地址叫做“虚拟地址”;而实际物理内存空间中的地址叫做“实际物理地址”或“物理地址”。
    处理器运算器和应用程序设计人员看到的只是虚拟内存空间和虚拟地址,而处理器片外的地址总线看到的只是物理地址空间和物理地址。

    由于存在两个内存地址,因此一个应用程序从编写到被执行,需要进行两次映射。
    第一次是映射到虚拟内存空间,第二次时映射到物理内存空间。
    在计算机系统中,这二次映射的工作是由硬件和软件共同来完成的。承担这个任务的硬件部分叫做存储管理单元MMU,软件部分就是操作系统的内存管理模块了。
    在映射工作中,为了记录程序段占用物理内存的情况,操作系统的内存管理模块需要建立一个表格,该表格以虚拟地址为索引,记录了程序段所占用的物理内存的物理地址。这个虚拟地址/物理地址记录表便是存储管理单元MMU把虚拟地址转化为实际物理地址的依据。
    虚拟内存技术的实现,是建立在应用程序可以分成段,并且具有“在任何时候正在使用的信息总是所有存储信息的一小部分”的局部特性基础上的。它是通过用辅存空间模拟RAM来实现的一种使机器的作业地址空间大于实际内存的技术。

    以存储单元为单位来管理显然不现实,因此Linux把虚存空间分成若干个大小相等的存储分区,Linux把这样的分区叫做页。
    为了换入、换出的方便,物理内存也就按也得大小分成若干个块。
    由于物理内存中的块空间是用来容纳虚存页的容器,所以物理内存中的块叫做页框。页与页框是Linux实现虚拟内存技术的基础。

    (6)ioremap_huge_init()IO 地址空间大页面映射初始化
    ioremap 的主要作用是将一个IO地址空间映射到内核的虚拟地址空间上去,这样直接使用虚拟地址就能够对其访问 ,便于编程访问。
    如果使能了CONFIG_ARM64_4K_PAGES,则配置ioremap_pud_capable = 1,标志可以进行4K IO大内存的映射分配
    ARM64默认支持 ioremap_pmd_capable = 1

    有关PUDPMD的概念:
    Linux系统使用了三级页表结构:页目录(Page DirectoryPGD)、中间页目录(Page Middle DirectoryPMD)、页表(Page TablePTE),其中 PGD中包含若干PUD的地址,PUD中包含若干PMD的地址,PMD中又包含若干PT的地址,每一个页表项指向一个页框,页框就是真正的物理内存页。

    (7)init_espfix_bsp(): 将espfix pud安装到内核页面目录中, 主要作用通过page_random的方式增加linux的安全
    其中:init_espfix_random() 主目创建page_random 数。

    地址空间配置随机加载(ASLR)是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,
    通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的的一种技术。
    Linux下的 ASLR总共有 3 个级别,0、1、2
    0:就是关闭ASLR,没有随机化,堆栈基地址每次都相同,而且libc.so每次的地址也相同。
    1:是普通的ASLRmmap基地址、栈基地址、.so加载基地址都将被随机化,但是堆没用随机化
    2:是增强的ASLR,增加了堆随机化

    (8)pti_init():初始化KPTI (Kernel PageTable Isolation) 内核页表隔离
    KPTI是由KAISER补丁修改而来。之前,进程地址空间被分成了内核地址空间和用户地址空间。
    其中内核地址空间映射到了整个物理地址空间,而用户地址空间只能映射到指定的物理地址空间。
    内核地址空间和用户地址空间共用一个页全局目录表(PGD表示进程的整个地址空间),
    meltdown漏洞就恰恰利用了这一点。攻击者在非法访问内核地址和CPU处理异常的时间窗口,通过访存微指令获取内核数据。
    为了彻底防止用户程序获取内核数据,可以令内核地址空间和用户地址空间使用两组页表集(也就是使用两个PGD)。

    KPTI 实现原理为:
    1、在运行应用程序的时候,将kernel mapping 减少到最少,只保留必须的userkernelexception entry mapping. 其他的kernel mapping 在运行user application时都去掉,变成无效mapping,这样的话,如果user访问kernel data, 在MMU地址转换的时候就会被挡掉(因为无效mapping).
    2、设计一个trampolinekernel PGD给运行user时用。Trampoline kernel mapping PGD只包含exception entry必需的mapping.
    3、当user通过系统调用,或是timer或其他异常进入kernel是首先用trampolinemapping, 接下来tramponlinevector处理会将kernel mapping 换成正常的kernel mapping(SWAPPER_PGD_DIR), 并直接跳转到kernel原来的vector entry, 继续正常处理。我们把上述过程称之为map kernel mapping.
    4、当从kernel返回到user时,正常的kernel_exit会调用trampolineexittramp_exit会重新将kernel mapping 换成是trampoline. 这个过程叫unmap kernel mapping.

  30. ftrace 功能基础初始化
    ftraceFunction Trace的意思,最开始主要用于记录内核函数运行轨迹;随着功能的逐渐增加,演变成一个跟踪框架。
    主要分为两种类型:
    (1)静态探测点,是在内核代码中调用ftrace提供的相应接口实现,称之为静态是因为,是在内核代码中写死的,静态编译到内核代码中的,在内核编译后,就不能再动态修改。在开启ftrace相关的内核配置选项后,内核中已经在一些关键的地方设置了静态探测点,需要使用时,即可查看到相应的信息。
    (2)动态探测点,基本原理为:利用mcount机制,在内核编译时,在每个函数入口保留数个字节,然后在使用ftrace时,将保留的字节替换为需要的指令,比如跳转到需要的执行探测操作的代码。

  31. 使能trace_printk 功能,并且分配相关的tracing_buffer

  32. 初始化调度器 sched_init(),当前 kernel 目前支持 FAIR schedRT sched两种调度方式:
    (1)有五种调度类,优先级从高到低分别为:
    a. stop_sched_class: 优先级最⾼的调度类,它与 idle_sched_class⼀样,是⼀个专⽤的调度类型(除了 migration 线程之外,其他的 task 都是不能或者说不应该被设置为 stop 调度类)。该调度类专⽤于实现类似 active balancestop machine 等依赖于 migration 线程执⾏的“紧急”任务。
    b. dl_sched_class: deadline 调度类的优先级仅次于 stop 调度类,它是⼀种基于 EDL 算法实现的实时调度器(或者说调度策略)。
    c. rt_sched_class: rt 调度类的优先级要低于 dl 调度类,是⼀种基于优先级实现的实时调度器。
    d. fair_sched_class: CFS 调度器的优先级要低于上⾯的三个调度类,它是基于公平调度思想⽽设计的调度类型,是Linux 内核的默认调度类。
    e. idle_sched_class: idle 调度类型是 swapper 线程,主要是让 swapper 线程接管 CPU,通过 cpuidle/nohz 等框架让 CPU 进⼊节能状态。

    (2)接着初始化每个CPU调度时所需要的内存、锁、队列等资源。
    (3)根据sched load_weight 来制定调度策略 sched policy,同时配置 idle_polic weight = WEIGHT_IDLEPRIO,其他的任务则是根据sched_prio_to_wmult数组结合优先及来制定调试策略

    (4)初始化当前处理器的空闲进程,实际上真正的 idle 进程是 init 进程,也就是 init_task 静态定义的进程,
    init 进程是所有用户空间进程的祖先进程,而 idle 进程是所有进程的祖先进程, 每个 cpu 都有一个 idle 进程,
    (5)bootcpu 进入idle 进程的流程为:
    init 进程创建完成后,该进程还会创建 kthreadd 进程,然后调用 rest_init->cpu_startup_entry,最后进入do_idle,让cpu 进入idle状态
    (6)剩下的cpu的进入idle的流程为:secondary_startup –> __secondary_switched –> secondary_start_kernel –> cpu_startup_entry->do_idle
    (7)初始化 fair_sched_class
    (8)初始化调度状态
    (9)初始化 psi,主要目的是实现周期性的对CPUMemoryIO等资源的监控与管理,以实现更大的资源利用率
    Pressure Stall Information 提供了一种评估系统资源压力的方法。
    系统有三个基础资源:CPUMemoryIO,无论这些资源配置如何增加,似乎永远无法满足软件的需求。
    一旦产生资源竞争,就有可能带来延迟增大,使用户体验到卡顿。
    如果没有一种相对准确的方法检测系统的资源压力程度,有两种后果。
    一种是资源使用者过度克制,没有充分使用系统资源; 另一种是经常产生资源竞争,过度使用资源导致等待延迟过大。
    准确的检测方法可以帮忙资源使用者确定合适的工作量,同时也可以帮助系统制定高效的资源调度策略,最大化利用系统资源,最大化改善用户体验。

  33. 禁止内核抢占,因为在初始化还未完成时,有些资源还没准备好,直接进行进程调试容易出来,这个最好是cpu进入idle时再开启会安全些

  34. radix树初始化:
    Radix-TreeLinux内核中有着⼴泛的应⽤,如page cache,swap cache通过Radix-Tree来管理虚拟地址到page cache之间的映射关系.
    Radix-Tree的特点是可以通过整数作为index来找到对应的数据结构,⽽⽆需像数组⼀样需要事先定义好整数index的范围,
    也就是Index可以是离散的,查找速度相较数组也不会逊⾊太多,在空间和时间上取得⼀个均衡.
    linux内存管理是通过radix树跟踪绑定到地址映射上的核心页,该radix树允许内存管理代码快速查找标识为dirtywritebackpage

  35. 初始化每个cpuworker_pool,初始化一些系统默认的workqueue
    每个执行 work 的线程叫做 worker,一组 worker 的集合叫做 worker_pool
    每个 worker 对应一个 kernel thread内核线程,一个 worker_pool 对应一个或者多个 worker
    多个 worker 从同一个链表中 worker_pool->worklist 获取 work 进行处理。

    workqueue 就是存放一组 work 的集合,基本可以分为两类:一类系统创建的 workqueue,一类是用户自己创建的 workqueue
    内核默认创建了一些工作队列(用户也可以创建):
    system_wq:如果work item执行时间较短,使用本队列,调用schedule_work()接口就是添加到本队列中;
    system_highpri_wq:高优先级工作队列,以nice-20 来运行;
    system_long_wq:如果work item执行时间较长,使用本队列;
    system_unbound_wq:该工作队列的内核线程不绑定到特定的处理器上;
    system_freezable_wq:该工作队列用于在Suspend时可冻结的work item
    system_power_efficient_wq:该工作队列用于节能目的而选择牺牲性能的work item
    system_freezable_power_efficient_wq:该工作队列用于节能或Suspend时可冻结目的的work item

  36. 考虑到篇幅问题,后续代码分析见:
    《【SemiDrive源码分析】【X9芯片启动流程】31 - AP1 Android Kernel 启动流程 start_kernel 函数详细分析(二)》

源码如下:

# buildsystem\android\kernel\init\main.c
asmlinkage __visible void __init start_kernel(void)
{char *command_line;char *after_dashes;// 1. 栈溢出检测配置,设置 init_task任务的栈最低地址,使得init_task->stack指向栈底最后一个long的地址set_task_stack_end_magic(&init_task);=================>+	unsigned long *stackend;+	stackend = end_of_stack(tsk);	// 返回 init_task->stack地址,然后 +	*stackend = STACK_END_MAGIC;	// 配置init_task->stack = STACK_END_MAGIC = 0x57AC6E9D 为栈度Magic,主要目的是用于栈溢出检测  +	-------------------->+	-		union thread_union {+	-			struct thread_info thread_info;+	-			unsigned long stack[THREAD_SIZE/sizeof(long)];+	-		};+	<--------------------<=================// 2. 触发CPU中断来获取ID,返回当前正在执行初始化的处理器ID,默认为CPU0, 会打印log: Booting Linux on physical CPU 0x0smp_setup_processor_id();// 3. 初始化hash buckets,最大16384个对象 ,将static object poll(obj_static_pool[i])的所有元素到链表中,最大为1024个元素,表头为 obj_pooldebug_objects_early_init();// 4. 初始化cgroup,cgroup是一种将进程按组管理的机制,其根节点为 struct cgroup_root cgrp_dfl_rootcgroup_init_early();// 5. 屏蔽当前CPU上的所有中断,此时送到当前CPU0上的所有中断信号都会被忽略,配置全局变量early_boot_irqs_disabled=truelocal_irq_disable();early_boot_irqs_disabled = true;/* Interrupts are still disabled. Do necessary setups, then enable them. */// 6. 更新当前CPU0 相关状态变量的值,使能为online、active、present、possible 为trueboot_cpu_init();// 7. 初始化高端内存,主要是初始化page_address_htable 链表,每当要申请映射一块Highmem空间时,就会的相应的信息添加到page_address_htable 链表中。page_address_init();// 8. 打印 Linux Version 系统版本号等信息pr_notice("%s", linux_banner);// 9. 初始化ioremap、memlock、paging分页等内存映射相关的代码,将 command_line 指向 boot_command_line对应的内存; 调用cpu_prepare 初始化所有可用的cpu核心,注意此时还是单核CPU0在运行。setup_arch(&command_line);/* Set up the the initial canary and entropy after arch and after adding latent and command line entropy.*/add_latent_entropy(); // 宏没定义,空函数// 10. 这个没看懂里面的input_pool 的作用是啥add_device_randomness(command_line, strlen(command_line));+// 11. 调用 get_random_bytes获取一个内核随机数,赋值给canary,保存在 init_task->stack_canary 中,主要目的还是防止栈溢出。// 按我的理解是,代码会把这个canary值写在栈的最后一个long整形位置了,// 由于它是一个随机数,理论上通过读取它的值是否改变就能知道是否发现了栈溢出问题boot_init_stack_canary();// 12. 初始化清除cpumask中的所有CPUmm_init_cpumask(&init_mm);// 13. 调用__alloc_bootmem 从bootmem中申请 saved_command_line、initcall_command_line、static_command_line三块空间,将 boot_command_line的内容拷贝到 saved_command_line,将setup_arch()在kernel中动态生成的command_line内容,保存在static_command_line中setup_command_line(command_line);=============================>+	saved_command_line = memblock_virt_alloc(strlen(boot_command_line) + 1, 0);+	initcall_command_line = memblock_virt_alloc(strlen(boot_command_line) + 1, 0);+	static_command_line = memblock_virt_alloc(strlen(command_line) + 1, 0);+	strcpy(saved_command_line, boot_command_line);+	strcpy(static_command_line, command_line);<=============================// 14. nr_cpu_ids具有当前系统能够使用的CPU数,默认值为NR_CPUS=1值,它使用的是bit位的形式表示,类型是unsigned int,所以当前kernel最大支持32个CPU同时使用setup_nr_cpu_ids();// 15. 系统为多核CPU在percpu空间申请了对应的内存用于保存每个CPU的私有数据,这块内存只有对应的CPU能够读写,其他CPU无权限读写。代码中通过__per_cpu_offset数组来记录每个CPU在percpu区域中私有数据地址距离__per_cpu_start起始地址的偏移量。我们访问每CPU变量就要依靠__per_cpu_offset + __per_cpu_start 来确定访问地址。setup_per_cpu_areas();===============================>+	unsigned long __per_cpu_offset[NR_CPUS] __read_mostly;+	delta = (unsigned long)pcpu_base_addr - (unsigned long)__per_cpu_start;+	for_each_possible_cpu(cpu)+		__per_cpu_offset[cpu] = delta + pcpu_unit_offsets[cpu];<==============================// 16. 配置CPU0->state = CPUHP_ONLINEboot_cpu_state_init();// 17. 配置当前CPU0的percpu域地址偏移,将当前CPU的 cpuinfo_arm64结构体信息写入percpu对应的内存中,同时写放当前CPU的运行环境(是否是运行在HYP mode),同时更新CPU负载能力等信息smp_prepare_boot_cpu();	/* arch-specific boot-cpu hooks */// 18. 启动Boot pageset table页面集表,初始化所有可用的页表数 保存在 vm_total_pages 中// 每个cpu一个Boot pageset table,用于所有区域和所有节点。设置参数的方式是,将列表中的项目立即移交给好友列表。// 这是安全的,因为页面集操作是在禁用中断的情况下完成的。// 即使在未使用的处理器和/或区域的引导完成后,也必须保留boot_pagesets 页面集,它们确实在引导热插拔处理器方面发挥了作用。build_all_zonelists(NULL);// 19.  页表分配初始化,为系统设置一个page_alloc_cpu_notify回调函数,该函数用来实现CPU的关闭与使能。// 在一个MPP结构的处理器系统或者大型服务器中有大量的CPU,该函数可以临时打开或者关闭某些Core或者CPU,此时Linux系统会调用page_alloc_cpu_notify函数。page_alloc_init();// 20. 打印 bootloader 传入的command_line内容。pr_notice("Kernel command line: %s\n", boot_command_line);// 21. 解析command_line中的early_param参数,调用parse_args来解析command_line字符串,解析后如果遇到console/earlycon相关的信息,则调用其setup_func(),  这个函数是 early_param结构体中自带的parse_early_param();===============================>+	for (p = __setup_start; p < __setup_end; p++) {+		if ((p->early && parameq(param, p->str)) ||(strcmp(param, "console") == 0 && strcmp(p->str, "earlycon") == 0)) {+			if (p->setup_func(val) != 0)	pr_warn("Malformed early option '%s'\n", param);+		}+	}+	// early_param参数 结构体定义如下: buildsystem\android\kernel\arch\um\include\shared\init.h+	struct uml_param {+	        const char *str;+	        int (*setup_func)(char *, int *);+	};<===============================// 22. 解析所有 static_command_line 中的信息,这些起信息是通过 setup_arch(&command_line) 来生成的,最终会通过 err = params[i].ops->set(val, &params[i]); 来将其设置在params[i]中after_dashes = parse_args("Booting kernel", static_command_line, __start___param,   __stop___param - __start___param, -1, -1, NULL, &unknown_bootoption);if (!IS_ERR_OR_NULL(after_dashes))parse_args("Setting init args", after_dashes, NULL, 0, -1, -1, NULL, set_init_arg);// 23. 配置 `static_key_initialized = true`jump_label_init();/* These use large bootmem allocations and must precede kmem_cache_init()*/ // 24. 初始化 kernel 的log buffer,同时把 kernel启动log拷贝进来// 之前项目中,经常因为kernel logbuff 太少抓不到完整的kernel log时,就会把 CONFIG_LOG_BUF_SHIFT值改大,它作用的地方就是__log_buf,  __log_buf是一个静态数组,数组大小就是 `1 << CONFIG_LOG_BUF_SHIFT`,这块内存使用的是在静态变量区的内存setup_log_buf(0);=======================>+	new_log_buf = memblock_virt_alloc(new_log_buf_len, LOG_ALIGN);+	log_buf = new_log_buf;+	memcpy(log_buf, __log_buf, __LOG_BUF_LEN);++	// buildsystem\android\kernel\kernel\printk\printk.c+	#define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT)+	static char __log_buf[__LOG_BUF_LEN] __aligned(LOG_ALIGN);<=======================// 25. 初始化进程 pidhash 双向链表表头pid_hash// pid哈希表根据机器中的内存量进行缩放。从最少16个插槽到4096个插槽,每GB或更多。// 进程有自己单独的双向链表叫进程链表,而当我们要知道某个进程的状态时,每个进程描述符是通过PID来区分的,因为遍历查找效率太低了,且浪费CPU,此时就是通过pidhash来实现快速查找pidhash_init();// 26. Linux文件子系统的 vfs caches早期初始化// 初始化 目录项高速缓存dhash_entries、dentry_hashtable 和 ihash_entries、inode_hashtable,其中entries用于保存数的居, hastable用于快速索引// Linux为了提高目录项对象的处理效率,设计与实现了目录项高速缓存(dentry cache,简称dcache)// 目录项高速缓存dcache是索引节点缓存icache的主控器(master),也即dcache中的dentry对象控制着icache中的inode对象的生命期转换。// dentry与inode是多对一的,每个inode可能有多个dentry,如硬链接的dentry不一样,inode却一样// 因此无论何时,只要一个目录项对象存在于dcache中(非 negative状态),则相应的inode就将总是存在,因为inode的引用计数i_count总是大于0。// 当dcache中的一个dentry被释放时,针对相应inode对象的iput()方法就会被调用。vfs_caches_init_early();//  27. 整理内核异常向量表 exception table,分别保存在__start___ex_table 和 __stop___ex_table中,它们保存在__ex_table段中sort_main_extable();========================>+		. = ALIGN(4);+			__ex_table : AT(ADDR(__ex_table) - LOAD_OFFSET) {+				__start___ex_table = .;+		#ifdef CONFIG_MMU+				*(__ex_table)+		#endif+				__stop___ex_table = .;+			}<========================// 28. oops等kernel异常bug处理函数的初始化,保存在bug_break_hook.fn 中,// 在处理函数中如果是BUG Type,   则调用oops_enter()、console_verbose()等打或保存相关的环境现场,然后调用panic触发一个Fata  exception中断trap_init();// 29. 内存管理相关初始化:(注意,此时只是释放及统计内存,并未初始化更高级的内存管理系统)mm_init();================================>+	static void __init mm_init(void)+	{+		/* page_ext requires contiguous pages, bigger than MAX_ORDER unless SPARSEMEM. */+		// 分配page_ext所需的内存空间,page_ext主要是用于保存调用栈信息,每个page页都会在页初始化时存储好相关的调用栈信息+		page_ext_init_flatmem();++		// `mem_init()`:标记内存映射中的可用区域,并告诉我们有多少内存可用,+		//  `bootmem`是开机过程中建立的一个非常简单的临时内存管理系统,所以当快开机结束时,它也就没有存在的必要了,+		// 因为后续会初始化更高级更完善的内存管理系统来接管它,+		// 所以`free_all_bootmem`释放的不是已经申请的内存,而是`bootmem`没分配出去的内存,+		// 调用`free_all_bootmem`以后`bootmem`就把自身也释放掉了,不可用了,之后会用更高级的内存管理系统,+		// 初始化 kdump,即基于kexec的崩溃转储机制所需要的pages内存+		// 然后统计各内存信息,最后打印当前系统所有的`Virtual kernel memory layout`+		mem_init();+			================================>+				+ 	free_unused_memmap();		// 释放所有未使用的内存映射+				+ 	free_all_bootmem();			// 释放开机过程中建立的简易临时内存管理系统相关的资源+				+ 	kexec_reserve_crashkres_pages();   // 初始化 kdump,即基于kexec的崩溃转储机制所需要的pages内存+				+	mem_init_print_info(NULL);+				+	pr_notice("Virtual kernel memory layout:\n");+				+	pr_notice("    kasan   : 0x%16lx - 0x%16lx   (%6ld GB)\n", MLG(KASAN_SHADOW_START, KASAN_SHADOW_END));+				+	pr_notice("    modules : 0x%16lx - 0x%16lx   (%6ld MB)\n", MLM(MODULES_VADDR, MODULES_END));+				+	pr_notice("    vmalloc : 0x%16lx - 0x%16lx   (%6ld GB)\n", MLG(VMALLOC_START, VMALLOC_END));+				+	pr_notice("      .text : 0x%p" " - 0x%p" "   (%6ld KB)\n", MLK_ROUNDUP(_text, _etext));+				+	pr_notice("    .rodata : 0x%p" " - 0x%p" "   (%6ld KB)\n", MLK_ROUNDUP(__start_rodata, __init_begin));+				+	pr_notice("      .init : 0x%p" " - 0x%p" "   (%6ld KB)\n", MLK_ROUNDUP(__init_begin, __init_end));+				+	pr_notice("      .data : 0x%p" " - 0x%p" "   (%6ld KB)\n", MLK_ROUNDUP(_sdata, _edata));+				+	pr_notice("       .bss : 0x%p" " - 0x%p" "   (%6ld KB)\n", MLK_ROUNDUP(__bss_start, __bss_stop));+				+	pr_notice("    fixed   : 0x%16lx - 0x%16lx   (%6ld KB)\n", MLK(FIXADDR_START, FIXADDR_TOP));+				+	pr_notice("    PCI I/O : 0x%16lx - 0x%16lx   (%6ld MB)\n", MLM(PCI_IO_START, PCI_IO_END));+				+	pr_notice("    vmemmap : 0x%16lx - 0x%16lx   (%6ld GB maximum)\n", MLG(VMEMMAP_START, VMEMMAP_START + VMEMMAP_SIZE));+				+	pr_notice("              0x%16lx - 0x%16lx   (%6ld MB actual)\n", MLM((unsigned long)phys_to_page(memblock_start_of_DRAM()), (unsigned long)virt_to_page(high_memory)));+				+	pr_notice("    memory  : 0x%16lx - 0x%16lx   (%6ld MB)\n", MLM(__phys_to_virt(memblock_start_of_DRAM()),(unsigned long)high_memory));+			<================================+		+		// 初始化slab内存分配器, 初始化slab分配器的slab_caches全局链表+		// 申请一些 kmem_cache内存保存在kemem_cache->list链表上,内存大小取决于nr_node_ids & nr_cpu_ids,+		// 然后将kemem_cache->list链表添加到slab_caches全局链表中,+		// 然后创建kmem_cache_node数组,用于管理kmem_cache链表上的内存+		kmem_cache_init();++		// 页表缓存 Page Cache初始化+		// Page Cache 的主要作用还是加速内存的访问,+		// 因为,如果CPU如果要访问外部磁盘上的文件,需要首先将这些文件的内容拷贝到内存中,由于硬件的限制,从磁盘到内存的数据传输速度是很慢的,但如果,把这些page的内容提前加载到cache内存中的话,CPU访问起来就会快速很多。+		// CPU查找内存时,首先会在page cache上找,如果命中了,也就是cache上刚好有所需要的内容,就直接读取,+		// 如果未命中的话,再启动IO操作,将所需要的内容归属的一个page,全部加载到page cache中,下次如果还要访问这块数据,就不需要再进行IO操作了,直接从page cache上读取就好了。+		// 同理为了DDR物理内存有限,不可能无止境的加载page内存,同样也会有相应的淘汰机制,+		// 一般page cache淘汰规则:是结合 最久未访问和访问次数来综合考量的,一般来说越久未被访问越容易被淘汰,同样的时间,访问次数更少的page也会相对更容易被淘汰。+		// `Linux`系统使用的是最近最少使用(`LRU`)页面的衰老算法。+		pgtable_init();++		// vmalloc用于分配虚拟地址连续(物理地址不连续)的内存空间,vzmalloc相对于vmalloc多了个0初始化;+		// vmalloc/vzmalloc分配的虚拟地址范围在VMALLOC_START/VMALLOC_END之间,属于堆内存;+		// 内核vmalloc区具体地址空间的管理是通过vmap_area管理的,该结构体记录整个区间的起始和结束;+		// vmalloc是内核出于自身的目的使用高端内存页的少数情形之一+		// vmalloc在申请内存时逐页分配,确保在物理内存有严重碎片的情况下,vmalloc仍然可以工作+		// 主要工作为:+		// 遍历每个CPU的vmap_block_queue 和 vfree_deferred,+		// vmap_block_queue是非连续内存块队列管理结构,主要是队列以及对应的保护锁 +		// vfree_deferred是vmalloc的内存延迟释放管理+		// 接着将挂接在vmlist链表的各项通过__insert_vmap_area()输入到非连续内存块的管理中+		// 最终配置全局静态变量 vmap_area_pcpu_hole = VMALLOC_END 标志vmalloc 末尾地址+		vmalloc_init();++		// `ioremap` 的主要作用是将一个`IO`地址空间映射到内核的虚拟地址空间上去,这样直接使用虚拟地址就能够对其访问 ,便于编程访问+		// 如果使能了CONFIG_ARM64_4K_PAGES,则配置ioremap_pud_capable = 1,标志可以进行4K大内存的映射分配+		// ARM64默认支持 ioremap_pmd_capable = 1+		// 有关PUD和PMD的概念:+		// PGD中包含若干PUD的地址,PUD中包含若干PMD的地址,PMD中又包含若干PT的地址。每一个页表项指向一个页框,页框就是真正的物理内存页+		ioremap_huge_init();		// IO 地址空间大页面映射初始化+		/* Should be run before the first non-init thread is created */+		+		// 将espfix pud安装到内核页面目录中, 主要作用通过page_random的方式增加linux的安全.+		// 其中:init_espfix_random() 主目创建page_random 数。+		// 地址空间配置随机加载(ASLR)是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,+		// 通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的的一种技术。+		// Linux下的ASLR总共有3个级别,0、1、2+		// 0就是关闭ASLR,没有随机化,堆栈基地址每次都相同,而且libc.so每次的地址也相同。+		// 1是普通的ASLR。mmap基地址、栈基地址、.so加载基地址都将被随机化,但是堆没用随机化+		// 2是增强的ASLR,增加了堆随机化+		init_espfix_bsp();+		+		/* Should be run after espfix64 is set up. */+		// 初始化KPTI(Kernel PageTable Isolation)内核页表隔离+		// KPTI是由KAISER补丁修改而来。之前,进程地址空间被分成了内核地址空间和用户地址空间。+		// 其中内核地址空间映射到了整个物理地址空间,而用户地址空间只能映射到指定的物理地址空间。+		// 内核地址空间和用户地址空间共用一个页全局目录表(PGD表示进程的整个地址空间),+		// meltdown漏洞就恰恰利用了这一点。攻击者在非法访问内核地址和CPU处理异常的时间窗口,通过访存微指令获取内核数据。+		// 为了彻底防止用户程序获取内核数据,可以令内核地址空间和用户地址空间使用两组页表集(也就是使用两个PGD)。++		// KPTI实现原理为:+		// 1、在运行应用程序的时候,将kernel mapping 减少到最少,只保留必须的user到kernel的exception entry mapping. +		// 		其他的kernel mapping 在运行user application时都去掉,变成无效mapping,+		// 		这样的话,如果user访问kernel data, 在MMU地址转换的时候就会被挡掉(因为无效mapping).+		// 2、设计一个trampoline 的kernel PGD给运行user时用。Trampoline kernel mapping PGD只包含exception entry必需的mapping.+		// 3、当user通过系统调用,或是timer或其他异常进入kernel是首先用trampoline的mapping,+		// 		接下来tramponline的vector处理会将kernel mapping 换成正常的kernel mapping(SWAPPER_PGD_DIR), +		// 		并直接跳转到kernel原来的vector entry, 继续正常处理。我们把上述过程称之为map kernel mapping.+		// 4、当从kernel返回到user时,正常的kernel_exit会调用trampoline的exit,+		// 		tramp_exit会重新将kernel mapping 换成是trampoline. 这个过程叫unmap kernel mapping.+		pti_init();+		=====================>+		+		pti_clone_user_shared();+		+		pti_clone_entry_text();+		+		pti_setup_espfix64();+		+		pti_setup_vsyscall();+		<=====================+	}================================>// 30. ftrace 功能基础初始化// ftrace是Function Trace的意思,最开始主要用于记录内核函数运行轨迹;随着功能的逐渐增加,演变成一个跟踪框架。// 静态探测点,是在内核代码中调用ftrace提供的相应接口实现,称之为静态是因为,是在内核代码中写死的,静态编译到内核代码中的,在内核编译后,就不能再动态修改。在开启ftrace相关的内核配置选项后,内核中已经在一些关键的地方设置了静态探测点,需要使用时,即可查看到相应的信息。// 动态探测点,基本原理为:利用mcount机制,在内核编译时,在每个函数入口保留数个字节,然后在使用ftrace时,将保留的字节替换为需要的指令,比如跳转到需要的执行探测操作的代码。ftrace_init();/* trace_printk can be enabled here */// 31. 使能trace_printk 功能,并且分配相关的tracing_bufferearly_trace_init();/* Set up the scheduler prior starting any interrupts (such as the timer interrupt). Full topology setup happens at smp_init() time - but meanwhile we still have a functioning scheduler.  */// 32. 初始化调度器sched_init();===========================>+	void __init sched_init(void)+	{+		sched_clock_init();		// 启动调度器时钟,配置 sched_clock_running=1+		wait_bit_init();+		+		// 当前kernel 目前支持 FAIR sched和 RT sched两种调度方式:+		// 有五种调度类,优先级从高到低分别为:+		// (1) stop_sched_class: 优先级最⾼的调度类,它与 idle_sched_class⼀样,是⼀个专⽤的调度类型(除了 migration 线程之外,其他的 task 都是不能或者说不应该被设置为 stop 调度类)。该调度类专⽤于实现类似 active balance 或 stop machine 等依赖于 migration 线程执⾏的“紧急”任务。+		// (2) dl_sched_class: deadline 调度类的优先级仅次于 stop 调度类,它是⼀种基于 EDL 算法实现的实时调度器(或者说调度策略)。+		// (3) rt_sched_class: rt 调度类的优先级要低于 dl 调度类,是⼀种基于优先级实现的实时调度器。+		// (4) fair_sched_class: CFS 调度器的优先级要低于上⾯的三个调度类,它是基于公平调度思想⽽设计的调度类型,是Linux 内核的默认调度类。+		// (5) idle_sched_class: idle 调度类型是 swapper 线程,主要是让 swapper 线程接管 CPU,通过 cpuidle/nohz 等框架让 CPU 进⼊节能状态。+	#ifdef CONFIG_FAIR_GROUP_SCHED+		alloc_size += 2 * nr_cpu_ids * sizeof(void **);+	#endif+	#ifdef CONFIG_RT_GROUP_SCHED+		alloc_size += 2 * nr_cpu_ids * sizeof(void **);+	#endif+		// 这部分代码主要是初始化每个CPU调度时所需要的内存、锁、队列等资源。+		// ......省略一部分代码......+		+		// 根据sched load_weight 来制定调度策略 sched policy,同时配置 idle_polic weight = WEIGHT_IDLEPRIO,其他的任务则是根据sched_prio_to_wmult数组结合优先及来制定调试策略+		set_load_weight(&init_task);+		+		// 初始化当前处理器的空闲进程,实际上真正的idle进程是init进程,也就是init_task静态定义的进程,+		// init进程是所有用户空间进程的祖先进程,而idle进程是所有进程的祖先进程, 每个cpu 都有一个idle进程, +		// bootcpu 进入idle进程的流程为:+		// 在init进程创建完成后,该进程还会创建kthreadd进程,然后调用rest_init->cpu_startup_entry,最后进入do_idle,让cpu 进入idle状态+		// 剩下的cpu的进入idle的流程为:+		// secondary_startup –> __secondary_switched –> secondary_start_kernel –> cpu_startup_entry->do_idle++		/** Make us the idle thread. Technically, schedule() should not be called from this thread, +		 * however somewhere below it might be,  but because we are the idle thread, +		 * we just pick up running again when this runqueue becomes "idle".  */+		init_idle(current, smp_processor_id());++		// 初始化 fair_sched_class+		init_sched_fair_class();+		+		// 初始化调度状态+		init_schedstats();+		// 初始化psi,主要目的是实现周期性的对CPU、Memory、IO等资源的监控与管理,以实现更大的资源利用率+		// Pressure Stall Information 提供了一种评估系统资源压力的方法。+		// 系统有三个基础资源:CPU、Memory 和 IO,无论这些资源配置如何增加,似乎永远无法满足软件的需求。+		// 一旦产生资源竞争,就有可能带来延迟增大,使用户体验到卡顿。+		// 如果没有一种相对准确的方法检测系统的资源压力程度,有两种后果。+		// 一种是资源使用者过度克制,没有充分使用系统资源;+		// 另一种是经常产生资源竞争,过度使用资源导致等待延迟过大。+		// 准确的检测方法可以帮忙资源使用者确定合适的工作量,同时也可以帮助系统制定高效的资源调度策略,最大化利用系统资源,最大化改善用户体验。+		psi_init();+		scheduler_running = 1;+	}<===========================// 33. 禁止内核抢占,因为在初始化还未完成时,有些资源还没准备好,直接进行进程调试容易出来,这个最好是cpu进入idle时再开启会安全些/* Disable preemption - early bootup scheduling is extremely fragile until we cpu_idle() for the first time.  */preempt_disable();// 34. radix树初始化:// Radix-Tree在Linux内核中有着⼴泛的应⽤,如page cache,swap cache通过Radix-Tree来管理虚拟地址到page cache之间的映射关系.// Radix-Tree的特点是可以通过整数作为index来找到对应的数据结构,⽽⽆需像数组⼀样需要事先定义好整数index的范围,// 也就是Index可以是离散的,查找速度相较数组也不会逊⾊太多,在空间和时间上取得⼀个均衡.// linux内存管理是通过radix树跟踪绑定到地址映射上的核心页,该radix树允许内存管理代码快速查找标识为dirty或writeback的page页radix_tree_init();/* Allow workqueue creation and work item queueing/cancelling early.  Work item execution depends on kthreads and starts after workqueue_init(). */// 35. 初始化每个cpu的worker_pool, // 每个执行 work 的线程叫做 worker,一组 worker 的集合叫做 worker_pool。// 每个 worker 对应一个 kernel thread内核线程,一个 worker_pool 对应一个或者多个 worker。// 多个 worker 从同一个链表中 worker_pool->worklist 获取 work 进行处理。// workqueue 就是存放一组 work 的集合,基本可以分为两类:一类系统创建的 workqueue,一类是用户自己创建的 workqueue。// 内核默认创建了一些工作队列(用户也可以创建):// system_wq:如果work item执行时间较短,使用本队列,调用schedule_work()接口就是添加到本队列中;// system_highpri_wq:高优先级工作队列,以nice值-20来运行;// system_long_wq:如果work item执行时间较长,使用本队列;// system_unbound_wq:该工作队列的内核线程不绑定到特定的处理器上;// system_freezable_wq:该工作队列用于在Suspend时可冻结的work item;// system_power_efficient_wq:该工作队列用于节能目的而选择牺牲性能的work item;// system_freezable_power_efficient_wq:该工作队列用于节能或Suspend时可冻结目的的work item;workqueue_init_early();rcu_init();/* Trace events are available after this */trace_init();context_tracking_init();/* init some links before init_ISA_irqs() */early_irq_init();init_IRQ();tick_init();rcu_init_nohz();init_timers();hrtimers_init();softirq_init();timekeeping_init();time_init();sched_clock_postinit();printk_safe_init();perf_event_init();profile_init();call_function_init();WARN(!irqs_disabled(), "Interrupts were enabled early\n");early_boot_irqs_disabled = false;local_irq_enable();kmem_cache_init_late();/** HACK ALERT! This is early. We're enabling the console before* we've done PCI setups etc, and console_init() must be aware of* this. But we do want output early, in case something goes wrong.*/console_init();if (panic_later)panic("Too many boot %s vars at `%s'", panic_later,panic_param);lockdep_info();/** Need to run this when irqs are enabled, because it wants* to self-test [hard/soft]-irqs on/off lock inversion bugs* too:*/locking_selftest();/** This needs to be called before any devices perform DMA* operations that might use the SWIOTLB bounce buffers. It will* mark the bounce buffers as decrypted so that their usage will* not cause "plain-text" data to be decrypted when accessed.*/mem_encrypt_init();#ifdef CONFIG_BLK_DEV_INITRDif (initrd_start && !initrd_below_start_ok &&page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {pr_crit("initrd overwritten (0x%08lx < 0x%08lx) - disabling it.\n",page_to_pfn(virt_to_page((void *)initrd_start)),min_low_pfn);initrd_start = 0;}
#endifpage_ext_init();kmemleak_init();debug_objects_mem_init();setup_per_cpu_pageset();numa_policy_init();if (late_time_init)late_time_init();calibrate_delay();pidmap_init();anon_vma_init();acpi_early_init();
#ifdef CONFIG_X86if (efi_enabled(EFI_RUNTIME_SERVICES))efi_enter_virtual_mode();
#endifthread_stack_cache_init();cred_init();fork_init();proc_caches_init();buffer_init();key_init();security_init();dbg_late_init();vfs_caches_init();pagecache_init();signals_init();proc_root_init();nsfs_init();cpuset_init();cgroup_init();taskstats_init_early();delayacct_init();check_bugs();acpi_subsystem_init();arch_post_acpi_subsys_init();sfi_init_late();if (efi_enabled(EFI_RUNTIME_SERVICES)) {efi_free_boot_services();}/* Do the rest non-__init'ed, we're now alive */rest_init();
}

1.3 一号进程 init_task 结构体

# buildsystem\android\kernel\include\linux\init_task.h
#define INIT_TASK(tsk)	\
{									\INIT_TASK_TI(tsk)						\.state		= 0,						\.stack		= init_stack,					\.usage		= ATOMIC_INIT(2),				\.flags		= PF_KTHREAD,					\.prio		= MAX_PRIO-20,					\.static_prio	= MAX_PRIO-20,					\.normal_prio	= MAX_PRIO-20,					\.policy		= SCHED_NORMAL,					\.cpus_allowed	= CPU_MASK_ALL,					\.nr_cpus_allowed= NR_CPUS,					\.mm		= NULL,						\.active_mm	= &init_mm,					\.restart_block = {						\.fn = do_no_restart_syscall,				\},								\.se		= {						\.group_node 	= LIST_HEAD_INIT(tsk.se.group_node),	\},								\.rt		= {						\.run_list	= LIST_HEAD_INIT(tsk.rt.run_list),	\.time_slice	= RR_TIMESLICE,				\},								\.tasks		= LIST_HEAD_INIT(tsk.tasks),			\INIT_PUSHABLE_TASKS(tsk)					\INIT_CGROUP_SCHED(tsk)						\.ptraced	= LIST_HEAD_INIT(tsk.ptraced),			\.ptrace_entry	= LIST_HEAD_INIT(tsk.ptrace_entry),		\.real_parent	= &tsk,						\.parent		= &tsk,						\.children	= LIST_HEAD_INIT(tsk.children),			\.sibling	= LIST_HEAD_INIT(tsk.sibling),			\.group_leader	= &tsk,						\RCU_POINTER_INITIALIZER(real_cred, &init_cred),			\RCU_POINTER_INITIALIZER(cred, &init_cred),			\.comm		= INIT_TASK_COMM,				\.thread		= INIT_THREAD,					\.fs		= &init_fs,					\.files		= &init_files,					\.signal		= &init_signals,				\.sighand	= &init_sighand,				\.nsproxy	= &init_nsproxy,				\.pending	= {						\.list = LIST_HEAD_INIT(tsk.pending.list),		\.signal = {{0}}},					\.blocked	= {{0}},					\.alloc_lock	= __SPIN_LOCK_UNLOCKED(tsk.alloc_lock),		\.journal_info	= NULL,						\INIT_CPU_TIMERS(tsk)						\.pi_lock	= __RAW_SPIN_LOCK_UNLOCKED(tsk.pi_lock),	\.timer_slack_ns = 50000, /* 50 usec default slack */		\.pids = {							\[PIDTYPE_PID]  = INIT_PID_LINK(PIDTYPE_PID),		\[PIDTYPE_PGID] = INIT_PID_LINK(PIDTYPE_PGID),		\[PIDTYPE_SID]  = INIT_PID_LINK(PIDTYPE_SID),		\},								\.thread_group	= LIST_HEAD_INIT(tsk.thread_group),		\.thread_node	= LIST_HEAD_INIT(init_signals.thread_head),	\INIT_IDS							\INIT_PERF_EVENTS(tsk)						\INIT_TRACE_IRQFLAGS						\INIT_LOCKDEP							\INIT_FTRACE_GRAPH						\INIT_TRACE_RECURSION						\INIT_TASK_RCU_PREEMPT(tsk)					\INIT_TASK_RCU_TASKS(tsk)					\INIT_CPUSET_SEQ(tsk)						\INIT_RT_MUTEXES(tsk)						\INIT_PREV_CPUTIME(tsk)						\INIT_VTIME(tsk)							\INIT_NUMA_BALANCING(tsk)					\INIT_KASAN(tsk)							\INIT_LIVEPATCH(tsk)						\INIT_TASK_SECURITY						\
}




  1. 《android\kernel\Documentation\translations\zh_CN\arm64》
  2. 《Linux的虚拟内存详解(MMU、页表结构)》
  3. 《纯干货,PSI 原理解析与应用》

http://www.ppmy.cn/news/235546.html

相关文章

mysql 字符串值不正确,不正确的字符串值:“ \ xF0 \ x9F \ x8E \ xB6 \ xF0 \ x9F…” MySQL...

我正在尝试在我的MYSQL表中存储一条推文。的鸣叫是&#xff1a; quiero que me escuches&#xff0c;no te burles no te rias&#xff0c;anoche tuve unsueoque te fuiste de mi vida&#x1f3b6;&#x1f3b6; tweet_text我表格中的字段编码为utf8mb4。但是&#xff0c;当我…

【SemiDrive源码分析】【X9芯片启动流程】11 - freertos_safetyos目录Cortex-R5 DIL2.bin 引导程序源代码分析

【SemiDrive源码分析】【X9芯片启动流程】11 - freertos_safetyos目录Cortex-R5 DIL2.bin 引导程序源代码分析 一、freertos_safetyos目录结构分析二、DIL2 抓取编译log三、DIL2 代码流程分析3.1 start.S 入口汇编代码,初始化环境后跳转lk_main()3.2 lk_main() 创建并执行 boo…

EndNote X9 快捷键 官方大全

EndNote X9 快捷键 大全 没有baidu到靠谱的EndNote快捷键&#xff0c;故找到官方手册并节选出来以飨读者。 本文摘自EndNote X9官方用户手册 P463-468 将就看看呗&#xff0c;当中只有加号的地方省略了 ctrl Pages from EndNote Keyboard Commands Editing Keyboard Commands…

c语言程序设计数字电位器,X9C103数字电位器中文.pdf

==2000:09:11== P&S武汉力源电子股份有限公 司 == 10-1== X9C102 103 104 503 2 TM E POT 非易失性数控电位器,端电压5V ,100个抽头 一、概述 1.1 一般说明 X9C102/ 103/ 104/503是固态非易失性电位器,把它用作数字控制的微调电阻器是理想的。它的功能 方框图如图1所示。…

VIVO X9PLUS 移动版改全网通

渤海海域&#xff0c;联通信号好&#xff0c;办了一个联通的卡&#xff0c;但是放在自己的VIVO X9 PLUS 手机的时候&#xff0c;网络信号只能选一个要么是移动要么是联通&#xff0c;选了一个&#xff0c;另外一个SIM卡就要关掉&#xff0c;真是郁闷。 百度了一下&#xff0c;才…

福岛核电站方圆20公里内居民撤离工作完毕

共同社报道&#xff0c;日本警察厅15日下午宣布&#xff0c;福岛第一核电站方圆20公里范围内的居民和住院患者的撤离工作已经结束。另据警察厅和防卫省的消息&#xff0c;福岛第一核电站10公里范围内的福岛县大熊町医院患者也已经被陆上自卫队安全转移。 共同社表示&#xff0…

土耳其发生强震 中国为此提供紧急援助

土耳其当地时间2月6日凌晨&#xff0c;在其南部靠近叙边境地区发生7.8级强烈地震&#xff0c;并且在此期间还伴随着不同程度上大大小小的余震&#xff0c;截止8日已导致土耳其和叙利亚两国超过8000人死亡、受伤人数逾3.6万人。中国作为一个大国&#xff0c;秉承着人道主义精神&…

美客机水面迫降,乘客冷静撤离的感触

美客机水面迫降&#xff0c;乘客冷静撤离&#xff0c;美国客机在纽约坠河机上155人全部获救&#xff0c;一位成功脱险的男性乘客上岸后接受媒体采访时表示&#xff0c;飞机坠河后&#xff0c;乘客们都十分镇静&#xff0c;并让妇幼先行,迅速地从机舱内撤离。 文章大家自己看&a…