Linux 内核启动分析

Linux 内核启动分析-BugMan-ChinaUnix博客

通过《Linux应用程序elf描述》,我们了解到一个应用程序编译后,最终会按照指定方式进行链接,而我们通过ld --verbose可以查看对应应用的默认链接方式。那么对于Linux内核呢?毫无疑问, Linux内核也是按照指定格式进行链接的,只是Linux的链接方式是由arch/arm64/kernel/vmlinux.lds.S指定的(gcc可以在链接的时候指定自定义链接脚本-T)。本章基于Linux内核Linux-4.19.73来作为例子说明的。

首先,我们看看vxlinux.lds.S为何物,如下图:

上图是一个删减了很多其他暂时不关心段的vmlinux.ld.s脚本文件,vmlinux.ld.s脚本文件的语法,这里不作出介绍, 关注的可以去查看相关文档。通过vmlinux.ld.s我们可以看到,bin文件的入口被设置为ENTRY(_text),  因此,_text即为入口函数地址,那么_text又在那里呢?那么接着看这个脚本文件,我们发现在灰色框里面有一个_text = .; 这说明_text的地址就是.head.text的首地址。那么哪些数据被链接到.head.text段了呢?通过搜索发现

点击(此处)折叠或打开

  1. #define HEAD_TEXT  KEEP(*(.head.text))
  2. #define __HEAD .section ".head.text","ax"
  3. $ grep __HEAD arch/arm64/ -r
  4. include/linux/init.h:#define __HEAD             .section        ".head.text","ax"
  5. $grep __HEAD arch/arm64/ -r
  6. arch/arm64/kernel/head.S:       __HEAD

因此, 目前只有kernel/head.S有代码被放置在了.head.text段,我们进一步看看arch/arm64/kernel/head.S文件,如下:
点击(此处)折叠或打开

  1. #define __PHYS_OFFSET   (KERNEL_START - TEXT_OFFSET) // 内核物理地址起始位置
  2. __HEAD
  3. _head:
  4.     b stext // branch to kernel start, magic
  5.     .long 0 // reserved
  6.     le64sym _kernel_offset_le // Image load offset from start of RAM, little-endian
  7.     le64sym _kernel_size_le // Effective size of kernel image, little-endian
  8.     le64sym _kernel_flags_le // Informative flags, little-endian
  9.     .quad 0 // reserved
  10.     .quad 0 // reserved
  11.     .quad 0 // reserved
  12.     .ascii "ARM\x64" // Magic number
  13.     .long 0 // reserved
  14. __INIT
  15. ENTRY(stext)
  16.     bl  preserve_boot_args
  17.     bl  el2_setup           // Drop to EL1, w0=cpu_boot_mode
  18.     adrp    x23, __PHYS_OFFSET // 物理地址偏移
  19.     and x23, x23, MIN_KIMG_ALIGN - 1    // KASLR offset, defaults to 0,一种内核安全机制,通过物理地址起始位置计算出偏移大小,偏移大小保存在X23寄存器
  20.     bl  set_cpu_boot_mode_flag
  21.     bl  __create_page_tables
  22.     bl  __cpu_setup         // initialise processor
  23.     b   __primary_switch
  24. ENDPROC(stext)
  25. $ grep __INIT include/ -r
  26. include/linux/init.h:#define __INIT             .section        ".init.text","ax"
  27. $

从上面代码可以看出,只有_head被放在了.head.text段,而下面的stext是放在.init.text段的。因此,当前版本的Linux kernel的入口函数就是_head函数, 而_head函数就只有一条跳转指令:b stext;因此内核启动后, 最终去stext函数运行。而stext主要调用了几个函数,他们的作用如下:

1、preserve_boot_args: 将uboot传入的参数 保存到bootargs[4] 全局变量里面。

2、el2_setup :判断启动的模式是el2还是el1并进行相关级别的系统配置(armv8中el2是hypervisor模式,el1是标准的内核模式,具体的参考手册),  然后返回启动模式

3、set_cpu_boot_mode_flag: 将启动模式保存到全局变量

4、__create_page_tables: 创建内存映射表,一共两张,一张存放在swapper_pg_dir(线性映射),一张存放在idmap_pg_dir(一对一映射)。

5、__cpu_setup : 初始化处理器相关的代码,配置访问权限,内存地址划分等。

6、__primary_switch :开启MMU, 准备0号进程和内核栈,然后跳转到start_kernel运行

首先,我们说说preserve_boot_args函数, 它的实现如下:

点击(此处)折叠或打开

  1. preserve_boot_args:
  2.     mov x21, x0 // 默认x0是uboot传入的第一个参数,通常是fdt的基地址,这里给x21寄存器保存
  3.     adr_l x0, boot_args //adr指令读取boot_args变量的当前地址,而不是链接地址(因为此时还没没有创建映射表,链接地址占时还不能用),boot_args是一个全局变量,默认地址是链接地址。
  4.     stp x21, x1, [x0] // 将uboot传入的第一个参数和第二个参数保存到boot_args的[0],[1]里面,表示地址和大小
  5.     stp x2, x3, [x0, #16] // 将uboot传入的第三个核第四个参数保存到boot_args的[2],[3]变量里面
  6.     dmb sy // 数据存储器栅栏,具体作用参考汇编手册
  7.     mov x1, #0x20 // boot_args有四个变量,每个变量8字节大小,因此 x1存入boot_args的长度(x0是boot_args的地址),然后调用_inval_dcache_area无效这段地址的缓存
  8.     b __inval_dcache_area // 无效x0和x1指定区域的缓存
  9. ENDPROC(preserve_boot_args)

其次是el2_setup 函数, 它的实现如下:

点击(此处)折叠或打开

  1. ENTRY(el2_setup)
  2.     msr SPsel, #1 // 设置SP的使用方式,是各用各的 还是共用一个,这里设置的是各用各的(armv8的栈使用)
  3.     mrs x0, CurrentEL // 读取当前的EL模式
  4.     cmp x0, #CurrentEL_EL2 // 判断当前的模式是不是el2,是 就跳转到el2的处理代码
  5.     b.eq 1f
  6.     mov_q x0, (SCTLR_EL1_RES1 | ENDIAN_SET_EL1) // 配置el1模式
  7.     msr sctlr_el1, x0
  8.     mov w0, #BOOT_CPU_MODE_EL1 // 返回值设置成 el1模式启动,注:w0是32位寄存器,通常x0/w0作为函数返回值使用
  9.     isb
  10.     ret
  11. // 下面是el2,即hypervisor模式的处理代码,这里不介绍,在hypervisor会有介绍
  12. 1: mov_q x0, (SCTLR_EL2_RES1 | ENDIAN_SET_EL2)
  13.     msr sctlr_el2, x0
  14.     .............
  15.     eret
  16. ENDPROC(el2_setup)

然后set_cpu_boot_mode_flag函数用于保存启动模式,该函数实现如下:

点击(此处)折叠或打开

  1. set_cpu_boot_mode_flag:
  2.     adr_l x1, __boot_cpu_mode //将_boot_cpu_mode的物理地址读取到x1寄存器
  3.     cmp w0, #BOOT_CPU_MODE_EL2 // w0是el2_setup返回的值,即模式
  4.     b.ne 1f
  5.     add x1, x1, #4
  6. 1: str w0, [x1] // This CPU has booted in EL1,将模式保存到_boot_cpu_mode变量
  7.     dmb sy
  8.     dc ivac, x1 // Invalidate potentially stale cache line
  9.     ret
  10. ENDPROC(set_cpu_boot_mode_flag)

对于__create_page_tables,则主要是创建内存映射表(这里只是简单的映射,只把内核代码段映射进来,用于开启MMU),后期还会做出二次映射。在映射函数中,有两种映射,一个是直接映射(即va=pa, 用于处理开启mmu那一瞬间不会出现异常),一个是线性映射(va = pa + offset)。具体函数如下:

点击(此处)折叠或打开

  1. __create_page_tables:
  2.     mov x28, lr
  3.     // 无效 idmap_pg_dir和swpper_pg_end直接的数据缓存
  4.     adrp x0, idmap_pg_dir
  5.     adrp x1, swapper_pg_end
  6.     sub x1, x1, x0
  7.     bl __inval_dcache_area
  8.     // 清楚idmap和swapper映射表里的脏数据
  9.     adrp x0, idmap_pg_dir
  10.     adrp x1, swapper_pg_end
  11.     sub x1, x1, x0
  12. 1: stp xzr, xzr, [x0], #16
  13.     stp xzr, xzr, [x0], #16
  14.     stp xzr, xzr, [x0], #16
  15.     stp xzr, xzr, [x0], #16
  16.     subs x1, x1, #64
  17.     b.ne 1b
  18.     // mmu也属性标记
  19.     mov x7, SWAPPER_MM_MMUFLAGS
  20.     //创建直接映射 idmap,从idmap_text_start到idmap_text_end
  21.     adrp x0, idmap_pg_dir
  22.     adrp x3, __idmap_text_start // __pa(__idmap_text_start)
  23.     adrp x5, __idmap_text_end
  24.     clz x5, x5
  25.     cmp x5, TCR_T0SZ(VA_BITS) // default T0SZ small enough?
  26.     b.ge 1f // .. then skip VA range extension
  27.     adr_l x6, idmap_t0sz
  28.     str x5, [x6]
  29.     dmb sy
  30.     dc ivac, x6 // Invalidate potentially stale cache line
  31.     mov x4, #1 << (PHYS_MASK_SHIFT - PGDIR_SHIFT)
  32.     // VA_BITS = 48bit
  33.     str_l   x4, idmap_ptrs_per_pgd, x5
  34.     ldr_l   x4, idmap_ptrs_per_pgd
  35.     mov x5, x3              // __pa(__idmap_text_start)
  36.     adr_l   x6, __idmap_text_end        // __pa(__idmap_text_end)
  37.     // map_memory用于映射, 具体怎么写映射表, 参考armv8体系结构
  38.     map_memory x0, x1, x3, x6, x7, x3, x4, x10, x11, x12, x13, x14
  39.     // 线性映射内核代码段, 存放到swapper_pg_dir, 从_text段开始到_end之间的数据
  40.     adrp    x0, swapper_pg_dir
  41.     mov_q   x5, KIMAGE_VADDR + TEXT_OFFSET  // compile time __va(_text)
  42.     add x5, x5, x23         // add KASLR displacement
  43.     mov x4, PTRS_PER_PGD
  44.     adrp    x6, _end            // runtime __pa(_end)
  45.     adrp    x3, _text           // runtime __pa(_text)
  46.     sub x6, x6, x3          // _end - _text
  47.     add x6, x6, x5          // runtime __va(_end)
  48.     // map_memory用于映射, 具体怎么写映射表, 参考armv8体系结构
  49.     map_memory x0, x1, x5, x6, x7, x3, x4, x10, x11, x12, x13, x14
  50.     // 无效映射表对应的缓存
  51.     adrp    x0, idmap_pg_dir
  52.     adrp    x1, swapper_pg_end
  53.     sub x1, x1, x0
  54.     dmb sy
  55.     bl  __inval_dcache_area
  56.     ret x28
  57. ENDPROC(__create_page_tables)

注:__idmap_text_start到__idmap_text_end的数据,其实就是启用mmu前后,需调用的那几个函数(因为CPU有加速指令处理的关系, 有些指令是乱序执行,防止开启mmu后,因为地址空间切换,导致的代码混乱的问题),因为有一段是va=pa因此, 之后即使还有code在用老的物理地址,也是不会出问题的。

__cpu_setup主要设置一些访问属性和内存划分等 ,具体函数如下:

点击(此处)折叠或打开

  1. ENTRY(__cpu_setup)
  2.     tlbi vmalle1   // 无效TLB
  3.     dsb nsh
  4.     mov x0, #3 << 20
  5.     msr cpacr_el1, x0 // 使能FP/ASIMD
  6.     mov x0, #1 << 12
  7.     msr mdscr_el1, x0 // 允许EL0访问DCC
  8.     isb 
  9.     reset_pmuserenr_el0 x0 // 设置EL0禁止PMU访问
  10.     /*
  11.      * LPAE内存属性:
  12.      *
  13.      * n = AttrIndx[2:0]
  14.      * n MAIR
  15.      * DEVICE_nGnRnE 000 00000000
  16.      * DEVICE_nGnRE 001 00000100
  17.      * DEVICE_GRE 010 00001100
  18.      * NORMAL_NC 011 01000100
  19.      * NORMAL 100 11111111
  20.      * NORMAL_WT 101 10111011
  21.      */
  22.     ldr x5, =MAIR(0x00, MT_DEVICE_nGnRnE) | \
  23.              MAIR(0x04, MT_DEVICE_nGnRE) | \
  24.              MAIR(0x0c, MT_DEVICE_GRE) | \
  25.              MAIR(0x44, MT_NORMAL_NC) | \
  26.              MAIR(0xff, MT_NORMAL) | \
  27.              MAIR(0xbb, MT_NORMAL_WT)
  28.     msr mair_el1, x5
  29.     mov_q x0, SCTLR_EL1_SET
  30.     /*
  31.      * 设置 TCR and TTBR. 用户和内核采用512GB (39-bit) 地址
  32.      */
  33.     ldr x10, =TCR_TxSZ(VA_BITS) | TCR_CACHE_FLAGS | TCR_SMP_FLAGS | \
  34.             TCR_TG_FLAGS | TCR_KASLR_FLAGS | TCR_ASID16 | \
  35.             TCR_TBI0 | TCR_A1
  36.     tcr_set_idmap_t0sz x10, x9
  37.     /*
  38.      * Set the IPS bits in TCR_EL1.
  39.      */
  40.     tcr_compute_pa_size x10, #TCR_IPS_SHIFT, x5, x6
  41.     msr tcr_el1, x10
  42.     ret
  43. ENDPROC(__cpu_setup)

最后__primary_switch准备好0号进程栈,然后切换到start_kernel运行,具体代码实现如下:

点击(此处)折叠或打开

  1. __primary_switch:
  2. #ifdef CONFIG_RANDOMIZE_BASE
  3.     mov x19, x0 // preserve new SCTLR_EL1 value
  4.     mrs x20, sctlr_el1 // preserve old SCTLR_EL1 value
  5. #endif
  6.     bl __enable_mmu   // 开启mmu ,就是只是配置一些MMU寄存器
  7. #ifdef CONFIG_RELOCATABLE
  8.     ......  // 这里省略掉 内核代码重定位代码,这个主要用于gdb调试驱动
  9. #endif
  10.     ldr x8, =__primary_switched  // 将内核的物理地址起始地址作为参数1,调用_primary_switched函数
  11.     adrp x0, __PHYS_OFFSET
  12.     br x8
  13. ENDPROC(__primary_switch)

  1. union thread_union {
  2.      unsigned long stack[THREAD_SIZE/sizeof(long)];
  3. } init_thread_union;

  1. __primary_switched:
  2.     adrp    x4, init_thread_union  // 读取0号进程的thread_union地址
  3.     add sp, x4, #THREAD_SIZE       // 将init_thread_union  +THREAD_SIZE作为内核线程的栈顶地址
  4.     adr_l   x5, init_task          // 读取0号进程的task_struct结构
  5.     msr sp_el0, x5                 // 在内核空间中,将当前task_sturct给sp_el0保存
  6.     adr_l   x8, vectors         // 设置中断向量表,vector在中断章节说明
  7.     msr vbar_el1, x8            // 系统寄存器vector table address
  8.     isb
  9.     str_l   x21, __fdt_pointer, x5      // X21存放的是fdt指针, 这里将fdt保存到__fdt_pointer
  10.     // 下面是保存虚拟地址和物理地址之差到kimg_voffset变量
  11.     ldr_l   x4, kimage_vaddr  // 获取到内核虚拟起始地址
  12.     sub x4, x4, x0    // x0是传参传入的 内核物理起始地址
  13.     str_l   x4, kimage_voffset, x5
  14.     // 将内核BSS段 请0
  15.     adr_l   x0, __bss_start
  16.     mov x1, xzr
  17.     adr_l   x2, __bss_stop
  18.     sub x2, x2, x0
  19.     bl  __pi_memset
  20.     dsb ishst               // Make zero page visible to PTW
  21. #ifdef CONFIG_KASAN
  22.     bl  kasan_early_init  // 一种内存调试手段初始化
  23. #endif
  24.     mov x30, #0  // x30是Lr寄存器, 这里赋值成NULL,不需要返回,返回即异常
  25.     b   start_kernel // 跳转到start_kernel运行
  26. ENDPROC(__primary_switched)

最后Linux内核进入C代码空间,start_kernel。

注: PAGE_OFFSET是内核虚拟地址起始地址, PAGE_SHIFT是页大小位数, TEXT_OFFSET是内核代码起始位置到内核起始地址的偏移。
注:在32位CPU中, 内核通常会保留开始的32k(0x8000)地址,前16k(0x4000)保存bootargs参数,后16k用于保存pgd,因此可以看到内核的代码地址基本都是0x8000开始,如0xC0008000.
注:vectors向量表位于:"arch/arm64/kernel/entry.S"文件中,实现如下:
点击(此处)折叠或打开

  1. ENTRY(vectors)
  2.     kernel_ventry 1, sync_invalid // Synchronous EL1t
  3.     kernel_ventry 1, irq_invalid // IRQ EL1t
  4.     kernel_ventry 1, fiq_invalid // FIQ EL1t
  5.     kernel_ventry 1, error_invalid // Error EL1t
  6.     kernel_ventry 1, sync // Synchronous EL1h
  7.     kernel_ventry 1, irq // IRQ EL1h
  8.     kernel_ventry 1, fiq_invalid // FIQ EL1h
  9.     kernel_ventry 1, error // Error EL1h
  10.     kernel_ventry 0, sync // Synchronous 64-bit EL0
  11.     kernel_ventry 0, irq // IRQ 64-bit EL0
  12.     kernel_ventry 0, fiq_invalid // FIQ 64-bit EL0
  13.     kernel_ventry 0, error // Error 64-bit EL0
  14. #ifdef CONFIG_COMPAT
  15.     kernel_ventry 0, sync_compat, 32 // Synchronous 32-bit EL0
  16.     kernel_ventry 0, irq_compat, 32 // IRQ 32-bit EL0
  17.     kernel_ventry 0, fiq_invalid_compat, 32 // FIQ 32-bit EL0
  18.     kernel_ventry 0, error_compat, 32 // Error 32-bit EL0
  19. #else
  20.     kernel_ventry 0, sync_invalid, 32 // Synchronous 32-bit EL0
  21.     kernel_ventry 0, irq_invalid, 32 // IRQ 32-bit EL0
  22.     kernel_ventry 0, fiq_invalid, 32 // FIQ 32-bit EL0
  23.     kernel_ventry 0, error_invalid, 32 // Error 32-bit EL0
  24. #endif
  25. END(vectors)

注:armv8中,每个异常的 向量地址不再是4字节,而是0x80字节,可以放更多的代码在向量表里面。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/162904.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

工程监测仪器振弦传感器信号转换器在桥梁安全监测中的重要性

工程监测仪器振弦传感器信号转换器在桥梁安全监测中的重要性 桥梁是人类社会建设过程中最重要的交通基础设施之一&#xff0c;对于保障人民出行、促进经济发展具有极其重要的作用。由于桥梁结构在长期使用过程中受到环境因素和负荷的影响&#xff0c;会逐渐发生变形和损伤&…

计算未来:微软眼中的人工智能

计算未来 :人工智能及其社会角色&#xff08;The Future Computed. Artificial Intelligence and its role in society &#xff09;这本书于2018年09月由北京大学出版社出版。 书籍的作者是&#xff1a;沈向洋&#xff08;微软全球执行副总裁&#xff09;,&#xff08;美&…

单片机TDL的功能、应用与技术特点 | 百能云芯

在现代电子领域中&#xff0c;单片机&#xff08;Microcontroller&#xff09;是一种至关重要的电子元件&#xff0c;广泛应用于各种应用中。TDL&#xff08;Time Division Multiplexing&#xff0c;时分多路复用&#xff09;是一种数据传输技术&#xff0c;结合单片机的应用&a…

通配符SSL证书价格贵吗

通配符SSL证书是一种SSL证书&#xff0c;用于保护基于相同域名但具有不同子域名的多个网站。 它使用通配符字符 "*" 来代表所有可能的二级子域名。JoySSL注册码230609领取免费使用通配符证书 通配符SSL证书的作用是使您可以使用单个证书来保护主域名和所有其下的子域…

关于我对 jeecg-boot 的项目理解、使用心得和改进建议

一句话总结&#xff1a; JeecgBoot帮助我提升了后端技术水平&#xff0c;入门了前端&#xff0c;让我在公司内部慢慢能够成长为全栈开发。 一、项目理解 JeecgBoot 项目的核心理念是快速开发、低代码、易扩展。它采用了前后端分离的架构&#xff0c;后端使用Spring Boot Myba…

小程序入门及案例展示

目录 一、小程序简介 1.1 为什么要使用小程序 1.2 小程序可以干什么 二、前期准备 2.1 申请账号 2.2 开发工具下载与安装 三、电商案例演示 四、入门案例 4.1 项目结构解析 4.2 基础操作及语法 4.3 模拟器 4.4 案例演示 4.4.1 新建页面 4.4.2 头部样式设置 4.4.…

github: kex_exchange_identification: Connection closed by remote host

问题描述 (base) ➜ test git:(dev) git pull kex_exchange_identification: Connection closed by remote host Connection closed by 192.30.255.113 port 22 致命错误&#xff1a;无法读取远程仓库。解决方案 参照下边文档 https://docs.github.com/en/authentication/tr…

【C++初阶(二)】缺省参数函数重载

目录 前言 1. 缺省参数 1.1 什么是缺省参数 1.2 缺省参数的分类 1.2.1 全缺省参数 1.2.2 半缺省参数 2. 函数重载 2.1 什么是函数重载 2.2 缺省参数与函数重载 2.3 函数重载的使用 3. C支持函数重载的原因 总结 前言 在学习C语言时我们就会发现&#xff0c;C语言中存在的许多…

Flutter视图原理之三棵树的建立过程

目录 三棵树的关系树的构建过程1.updateChild函数&#xff08;element的复用&#xff09;2.inflateWidget函数3.mount函数3.1 componentElement的实现3.2 RenderObjectElement的实现3.2.1 attachRenderObject函数 4.performRebuild函数 总结三棵树创建流程 三棵树的关系 Flutt…

超声波清洗机靠谱吗?实用性比较高的超声波清洗机推荐

超声波清洗机是否靠谱&#xff0c;这是一个有争议的问题&#xff01;但是先不妨先来了解了解超声波清洗机。 超声波清洗机通过高频振动波来清洁物品表面&#xff0c;这些振动波会在水中形成微小的气泡。气泡不断崩裂&#xff0c;产生强大的冲击力&#xff0c;从而将物品表面的…

【ArcGIS微课1000例】0075:将AutoCAD(Dwg、Dxf)文件转换为shp、KML(kml、kmz)文件

文章目录 1. 加载DWG2. 导出为shp3. 投影变换4. 转为kml1. 加载DWG 打开ArcMap,点击添加符号: 选择地形图dwg数据,全选图层,也可以选择需要的图层。 提示位置的空间参考,点击确定即可。 加载效果。 2. 导出为shp 接下来我们演示将面状数据转为shp,选择Polygon图层,右键…

vue3+koa+axios实现前后端通信

vue3koaaxios实现前后端通信 写了一个小demo来实现前后端通信,涉及跨域问题&#xff0c;非常简单可以给大家平时开发的时候参考 服务端&#xff1a; 目录结构如下&#xff1a; router index.js // router的入口文件 // 引入路由 const Router require("koa-router&quo…

QT的QStringList的使用

初始 化 默认构造函数创建一个空列表。可以使用初始值设定项列表构造函数创建包含元素的列表&#xff1a; QStringList fonts { "Arial", "Helvetica", "Times" }; 添加字符串 可以使用insert 、append&#xff08;&#xff09; 和 operator…

armbian安装gcc、g++

文章目录 安装GCC安装G 安装GCC 打开终端&#xff0c;更新软件包列表&#xff1a; sudo apt update安装GCC&#xff1a; sudo apt install gcc如果需要安装特定版本的GCC&#xff0c;可以使用以下命令&#xff1a; sudo apt install gcc-<version> # sudo apt install g…

phpstudy_2016-2018_rce_backdoor 漏洞复现

phpstudy_2016-2018_rce_backdoor 漏洞复现 Remote Command Execute 打开 bp 打开代理浏览器 访问 php 页面 回到 bp 查看 http 历史&#xff0c;找到刚刚访问的 php 页面 发送到 Repeater 转到 Repeater php 页面请求内容加 Accept-Charset: 修改 Accept-Encodi…

Leetcode 142 环形链表II(链表:快2慢1指针相遇即有环)

Leetcode 142 环形链表II&#xff08;链表&#xff1a;快2慢1指针相遇即有环&#xff09; 解法1 https://leetcode.cn/problems/linked-list-cycle-ii/description/ 解法1 &#x1f534;1.【有无环】快慢指针&#xff0c;快指针每次走两步&#xff0c;慢指针每次走一步&#xf…

3、Flowable任务分配和流程变量

任务分配和流程变量 1.任务分配 1.1 固定分配 固定分配就是我们前面介绍的&#xff0c;在绘制流程图或者直接在流程文件中通过Assignee来指定的方式 1.2 表达式分配 Flowable使用UEL进行表达式解析。UEL代表Unified Expression Language&#xff0c;是EE6规范的一部分.Flo…

K-Means算法

c^(i)&#xff1a;xi分配到第i个簇 μ&#xff1a;质心 μci&#xff1a;即第xi个样本分配到的簇的质心 Step 1.从样本中随机选取K个点作为簇质心 2.每个点都指向离它最近的簇质心 3.遍历结束后&#xff0c;重新计算K值&#xff0c;即计算K个簇的平均值作为新的质心 重复23直…

MATLAB中ss2tf函数用法

目录 语法 说明 示例 质点-弹簧系统 双体振荡器 ss2tf函数的功能是将状态空间表示形式转换为传递函数。 语法 [b,a] ss2tf(A,B,C,D) [b,a] ss2tf(A,B,C,D,ni) 说明 [b,a] ss2tf(A,B,C,D) 将方程组的状态空间表示形式转换为等同的传递函数。ss2tf 返回连续时间方程组…

lark 发送图片消息

1. 需求 2. 实现 2.1 获取数据源 # -*- coding: utf-8 -*- import os import json import requests import pandas as pd from pathlib import PurePath, Path import plotly.express as px from requests_toolbelt import MultipartEncoderdef get_data():dt [2023-10-01, …