kernel(二):启动内核

news2024/9/19 10:35:04

        本文主要探讨210内核启动过程。

主Makefile

    定义kernel版本号(2.6.35.7)

    VERSION = 2
    PATCHLEVEL = 6
    SUBLEVEL = 35
    EXTRAVERSION = .7


       指定编译文件生成目录

  make O=/tmp 


     定义交叉编译工具链

CROSS_COMPILE    ?= /root/arm-2009q3/bin/arm-none-linux-gnueabi-

     指定架构

 ARCH        ?= arm

链接脚本

    kernel链接脚本需要条件编译,lds格式不支持条件编译
    kernel链接接脚由汇编文件vmlinux.lds.S编译生成vmlinux.lds(arch/arm/kernel/)
    vmlinux.lds中ENTRY(stext)为链接地址入口
    head.S和head-nommu.S都包含ENTRY(stext),head.S使用用MMU,head-nommu.S未使用mmu


head.S

#define KERNEL_RAM_VADDR    (PAGE_OFFSET + TEXT_OFFSET)
#define KERNEL_RAM_PADDR    (PHYS_OFFSET + TEXT_OFFSET)

         内核运行的虚拟地址(KERNEL_RAM_VADDR)为0xC0008000,内核运行的物理地址(KERNEL_RAM_PADDR)为0x30008000    

(arch/arm/kernel/.head.o.cmd)
    cmd_arch/arm/kernel/head.o := /root/arm-2009q3/bin/arm-none-linux-gnueabi-gcc -Wp,-MD,arch/arm/kernel/.head.o.d  -nostdinc -isystem /root/arm-2009q3/bin/../lib/gcc/arm-none-linux-gnueabi/4.4.1/include -I/root/kernel/arch/arm/include -Iinclude  -include include/generated/autoconf.h -D__KERNEL__ -mlittle-endian -Iarch/arm/mach-s5pv210/include -Iarch/arm/plat-s5p/include -Iarch/arm/plat-samsung/include -D__ASSEMBLY__ -mabi=aapcs-linux -mno-thumb-interwork  -D__LINUX_ARM_ARCH__=7 -march=armv7-a  -include asm/unified.h -msoft-float -gdwarf-2     -DTEXT_OFFSET=0x00008000  -c -o arch/arm/kernel/head.o arch/arm/kernel/head.S

(arch/arm/include/asm/memory.h)#define PAGE_OFFSET        UL(CONFIG_PAGE_OFFSET)
(.config)CONFIG_PAGE_OFFSET=0xC0000000


(arch/arm/mach-s5pv210/include/mach/memory.h)
#if defined(CONFIG_MACH_SMDKV210)
#define PHYS_OFFSET        UL(0x30000000)
#else
#define PHYS_OFFSET        UL(0x30000000)
#endif

(include/generated/autoconf.h)
#define CONFIG_MACH_SMDKV210 1

            uboot启动内核后,内核运行zImage前解压代码解压zImage,再运行内核入口代码
            uboot启动内核(theKernel(0,machid,bd->bi_boot_params))运行时把0写入r0,machid写入r1,bd->bi_boot_params写入r2
            kernel启动时MMU是关闭的,硬件需要的物理地址,zImage是整体不能分散加载,需使用位置无关码(物理地址)

        __HEAD
    ENTRY(stext)
    setmode    PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ ensure svc mode
                        @ and irqs disabled
    mrc    p15, 0, r9, c0, c0        @ get processor id
    bl    __lookup_processor_type        @ r5=procinfo r9=cpuid
    movs    r10, r5                @ invalid processor (r5=0)?
    beq    __error_p            @ yes, error 'p'
    bl    __lookup_machine_type        @ r5=machinfo
    movs    r8, r5                @ invalid machine (r5=0)?
    beq    __error_a            @ yes, error 'a'
    bl    __vet_atags
    bl    __create_page_tables

                     __HEAD定义后面代码属于.head.text段

    __HEAD
    ENTRY(stext)
    (include/linux/init.h)
    #define __HEAD        .section    ".head.text","ax"

            cp15协处理器c0读取出硬件CPU ID号,用于合法性检验,合法则继续启动,不合法则停止启动,转向__error_p启动失败
            内核包含CPU ID号码数组,该函数从硬件中读取的cpu id号码和数组中的对比,相同则不合法

__lookup_processor_type
    (System.map)
    c0008168 t __lookup_processor_type
    c00081a4 T lookup_processor_type


    (arch/arm/kernel/setup.c)
    list = lookup_processor_type(read_cpuid_id());
    if (!list) {
        printk("CPU configuration botched (ID %08x), unable "
               "to continue.\n", read_cpuid_id());
        while (1);
    }

    (arch/arm/kernel/include/asm/cputype.h)
    #define CPUID_ID    0

    static inline unsigned int __attribute_const__ read_cpuid_id(void)
    {
        return read_cpuid(CPUID_ID);
    }

           __lookup_machine_type函数用于校验机器码,类似cpu id校验
           __vet_atags函数校验uboot给内核传参(ATAGS格式) 

            __create_page_tables

                    函数用于建立页表,内核链接在虚拟地址处,kernel需要启动MMU
                    kernel建立页表:kernel先建立段式页表(同uboot建立页相同页,大小为1MB,1MB映射4GB需4096页表项,每个页表项4字节,共需16KB做页表),建立细页表(细页表项大小为4kb)


            __switch_data (arch/arm/kernel/head-common.S)

__switch_data:
     .long    __mmap_switched
    .long    __data_loc            @ r4
    .long    _data                @ r5
    .long    __bss_start            @ r6
    .long    _end                @ r7
    .long    processor_id            @ r4
    .long    __machine_arch_type        @ r5
    .long    __atags_pointer            @ r6
    .long    cr_alignment            @ r7
    .long    init_thread_union + THREAD_START_SP @ sp
__mmap_switched:
    adr    r3, __switch_data + 4

    ldmia    r3!, {r4, r5, r6, r7}
    cmp    r4, r5                @ Copy data segment if needed
1:    cmpne    r5, r6
    ldrne    fp, [r4], #4
    strne    fp, [r5], #4
    bne    1b

    mov    fp, #0                @ Clear BSS (and zero fp)
1:    cmp    r6, r7
    strcc    fp, [r6],#4
    bcc    1b

 ARM(    ldmia    r3, {r4, r5, r6, r7, sp})
 THUMB(    ldmia    r3, {r4, r5, r6, r7}    )
 THUMB(    ldr    sp, [r3, #16]        )
    str    r9, [r4]            @ Save processor ID
    str    r1, [r5]            @ Save machine type
    str    r2, [r6]            @ Save atags pointer
    bic    r4, r0, #CR_A            @ Clear 'A' bit
    stmia    r7, {r0, r4}            @ Save control register values
    b    start_kernel
ENDPROC(__mmap_switched)

             建立段式页表后执行__switch_data,__switch_data中执行__mmap_switched函数
            __mmap_switched函数复制数据段、清除bss段(构建C环境),保存cpu id、机器码、tag首地址
           start_kernel跳转到C阶段

内核启动的C阶段(start_kernel,init/main.c)


        smp_setup_processor_id()设置对称多处理器(多核CPU)
        lockdep_init内核调试模块处理内核自旋锁死锁
        cgroup_init_early处理进程组

        printk(KERN_NOTICE "%s", linux_banner);

const char linux_banner[] =
    "Linux version " UTS_RELEASE " (" LINUX_COMPILE_BY "@"
    LINUX_COMPILE_HOST ") (" LINUX_COMPILER ") " UTS_VERSION "\n";
(include/generated/utsrelease.h)
#define UTS_RELEASE "2.6.35.7"

(include/generated/compile.h)
#define UTS_VERSION "#1 PREEMPT Tue Dec 12 11:46:41 CST 2023"
#define LINUX_COMPILE_BY "root"
#define LINUX_COMPILE_HOST "kax-virtual-machine"
#define LINUX_COMPILER "gcc version 4.4.1 (Sourcery G++ Lite 2009q3-67) "

include/generated/下的文件为编译过程中生成的头文件


                printk定义打印级别(0-7)来过滤显示机制

#define    KERN_EMERG    "<0>"    /* system is unusable            */
#define    KERN_ALERT    "<1>"    /* action must be taken immediately    */
#define    KERN_CRIT    "<2>"    /* critical conditions            */
#define    KERN_ERR    "<3>"    /* error conditions            */
#define    KERN_WARNING    "<4>"    /* warning conditions            */
#define    KERN_NOTICE    "<5>"    /* normal but significant condition    */
#define    KERN_INFO    "<6>"    /* informational            */
#define    KERN_DEBUG    "<7>"    /* debug-level messages            */


          setup_arch

                用来确定内核机器的arch和machine

void __init setup_arch(char **cmdline_p)
{
    struct tag *tags = (struct tag *)&init_tags;
    struct machine_desc *mdesc;
    char *from = default_command_line;

    unwind_init();

    setup_processor();
    mdesc = setup_machine(machine_arch_type);
    machine_name = mdesc->name;

    if (mdesc->soft_reboot)
        reboot_setup("s");

    if (__atags_pointer)
    {
        tags = phys_to_virt(__atags_pointer);
        printk("@@@@@@@ atags_pointer not null\n");
        
    }
    else if (mdesc->boot_params)
    {
        tags = phys_to_virt(mdesc->boot_params);
        printk("@@@@@@@ boot params not null\n");
    }
    printk("@@@@@@@linter#####boot_params:%p,mdesc->boot_params:%p\n",tags);
    /*
     * If we have the old style parameters, convert them to
     * a tag list.
     */
    if (tags->hdr.tag != ATAG_CORE)
        convert_to_tag_list(tags);
    if (tags->hdr.tag != ATAG_CORE)
        tags = (struct tag *)&init_tags;

    if (mdesc->fixup)
        mdesc->fixup(mdesc, tags, &from, &meminfo);

    if (tags->hdr.tag == ATAG_CORE) {
        if (meminfo.nr_banks != 0)
            squash_mem_tags(tags);
        save_atags(tags);
        parse_tags(tags);
    }

    init_mm.start_code = (unsigned long) _text;
    init_mm.end_code   = (unsigned long) _etext;
    init_mm.end_data   = (unsigned long) _edata;
    init_mm.brk       = (unsigned long) _end;

    /* parse_early_param needs a boot_command_line */
    strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);

    /* populate cmd_line too for later use, preserving boot_command_line */
    strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
    *cmdline_p = cmd_line;

    printk("$$$$$$$$$cmdline:%s\n",cmd_line);
    parse_early_param();

    paging_init(mdesc);
    request_standard_resources(&meminfo, mdesc);

#ifdef CONFIG_SMP
    smp_init_cpus();
#endif

    cpu_init();
    tcm_init();

    /*
     * Set up various architecture-specific pointers
     */
    init_arch_irq = mdesc->init_irq;
    system_timer = mdesc->timer;
    init_machine = mdesc->init_machine;

#ifdef CONFIG_VT
#if defined(CONFIG_VGA_CONSOLE)
    conswitchp = &vga_con;
#elif defined(CONFIG_DUMMY_CONSOLE)
    conswitchp = &dummy_con;
#endif
#endif
    early_trap_init();
}
setup_processor();

                  setup_processor用来查找CPU信息并打印信息

static void __init setup_processor(void)
{
    struct proc_info_list *list;

    /*
     * locate processor in the list of supported processor
     * types.  The linker builds this table for us from the
     * entries in arch/arm/mm/proc-*.S
     */
    list = lookup_processor_type(read_cpuid_id());
    if (!list) {
        printk("CPU configuration botched (ID %08x), unable "
               "to continue.\n", read_cpuid_id());
        while (1);
    }

    cpu_name = list->cpu_name;

#ifdef MULTI_CPU
    processor = *list->proc;
#endif
#ifdef MULTI_TLB
    cpu_tlb = *list->tlb;
#endif
#ifdef MULTI_USER
    cpu_user = *list->user;
#endif
#ifdef MULTI_CACHE
    cpu_cache = *list->cache;
#endif

    printk("CPU: %s [%08x] revision %d (ARMv%s), cr=%08lx\n",
           cpu_name, read_cpuid_id(), read_cpuid_id() & 15,
           proc_arch[cpu_architecture()], cr_alignment);

    sprintf(init_utsname()->machine, "%s%c", list->arch_name, ENDIANNESS);
    sprintf(elf_platform, "%s%c", list->elf_name, ENDIANNESS);
    elf_hwcap = list->elf_hwcap;
#ifndef CONFIG_ARM_THUMB
    elf_hwcap &= ~HWCAP_THUMB;
#endif

    cacheid_init();
    cpu_proc_init();
}

               setup_machine的参数machine_arch_type是机器码编号(2456)

mdesc = setup_machine(machine_arch_type);
machine_name = mdesc->name;
(include/generated/mach-types.h)
#define MACH_TYPE_SMDKV210             2456

#ifdef CONFIG_MACH_SMDKV210
# ifdef machine_arch_type
#  undef machine_arch_type
#  define machine_arch_type    __machine_arch_type
# else
#  define machine_arch_type    MACH_TYPE_SMDKV210
# endif
# define machine_is_smdkv210()    (machine_arch_type == MACH_TYPE_SMDKV210)
#else
# define machine_is_smdkv210()    (0)
#endif
(arch/arm/kernel/setup.c)
struct machine_desc *mdesc;

(arch/arm/include/asm/mach/arch.h)
struct machine_desc {
    /*
     * Note! The first four elements are used
     * by assembler code in head.S, head-common.S
     */
    unsigned int        nr;        /* architecture number    */
    unsigned int        phys_io;    /* start of physical io    */
    unsigned int        io_pg_offst;    /* byte offset for io 
                         * page tabe entry    */

    const char        *name;        /* architecture name    */
    unsigned long        boot_params;    /* tagged list        */

    unsigned int        video_start;    /* start of video RAM    */
    unsigned int        video_end;    /* end of video RAM    */

    unsigned int        reserve_lp0 :1;    /* never has lp0    */
    unsigned int        reserve_lp1 :1;    /* never has lp1    */
    unsigned int        reserve_lp2 :1;    /* never has lp2    */
    unsigned int        soft_reboot :1;    /* soft reboot        */
    void            (*fixup)(struct machine_desc *,
                     struct tag *, char **,
                     struct meminfo *);
    void            (*map_io)(void);/* IO mapping function    */
    void            (*init_irq)(void);
    struct sys_timer    *timer;        /* system tick timer    */
    void            (*init_machine)(void);
};

 

ENTRY(lookup_machine_type)
    stmfd    sp!, {r4 - r6, lr}
    mov    r1, r0
    bl    __lookup_machine_type
    mov    r0, r5
    ldmfd    sp!, {r4 - r6, pc}
ENDPROC(lookup_machine_type)

                 setup_machine调用lookup_machine_type调用__lookup_machine_type
                 内核把各种CPU架构信息组成machine_desc实例并赋予段属性(.arch.info.init)保证链接在一起,__lookup_machine_type遍历各描述符

                setup_arch函数处理cmdline(初步bootargs参数)

char *from = default_command_line;

/* parse_early_param needs a boot_command_line */
    strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);

    /* populate cmd_line too for later use, preserving boot_command_line */
    strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
    *cmdline_p = cmd_line;

    printk("$$$$$$$$$cmdline:%s\n",cmd_line);
    parse_early_param();

                default_command_line(CONFIG_CMDLINE)是默认命令行参数
                uboot通过tag给kernel传递cmdline为空则使用kernel默认命令行参数

static char default_command_line[COMMAND_LINE_SIZE] __initdata = CONFIG_CMDLINE;

(include/generated/autoconf.h)
#define CONFIG_CMDLINE "console=ttySAC2,115200"


                setup_command_line(cmdline解析,kernel/init/init)

cmdline:console=ttySAC2,115200 root=/dev/mmcblk0p2 rw init=/linuxrc rootfstype=ext3


                        解析为
                                console=ttySAC2,115200  
                                root=/dev/mmcblk0p2 rw 
                                init=/linuxrc             
                                rootfstype=ext3    

                        cmdline参数

                                root=/dev/xxx指定根文件系统位置
                                rootfstype=xxx指定根文件系统的文件系统类型
                                console=指定控制台信息(串口,波特率)
                                mem=xxx指定内核系统内存大小
                                init=指定init进程        

        start_kernel其他函数
                trap_init                    设置异常向量表
                mm_init                        内存管理模块初始化
                scheduler_init                    内核调度系统初始化
                early_irq_init&init_IRQ        中断初始化
                console_init                控制台初始化


        rest_init(init文件)
                start_kernel调用许多xx_init函数为初始化内核模块函数,rest_init用于串联已初始化的模块

static noinline void __init_refok rest_init(void)
    __releases(kernel_lock)
{
    int pid;

    rcu_scheduler_starting();
    /*
     * We need to spawn init first so that it obtains pid 1, however
     * the init task will end up wanting to create kthreads, which, if
     * we schedule it before we create kthreadd, will OOPS.
     */
    kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
    numa_default_policy();
    pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
    rcu_read_lock();
    kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
    rcu_read_unlock();
    complete(&kthreadd_done);
    unlock_kernel();

    /*
     * The boot idle thread must execute schedule()
     * at least once to get things moving:
     */
    init_idle_bootup_task(current);
    preempt_enable_no_resched();
    schedule();
    preempt_disable();

    /* Call into cpu_idle with preempt disabled */
    cpu_idle();
}

                调用kernel_thread启动2个内核线程:kernel_init和kthreadd
                调用schedule函数开启内核调度系统,linux系统运行
                调用cpu_idle函数结束内核启动进入死循环
                内核调度系统里的进程需要运行,调度系统终止cpu_idle死循环进程(空闲进程),执行该进程
                进程0:cpu_idle()函数,idle进程(死循环)
                进程1:kernel_init函数为init进程
                进程2:kthreadd函数是进程2,是linux内核守护进程,保证linux内核正常工作的
                init进程挂载根文件系统并运行根文件系统下的init程序转变为用户态应用程序,脱离内核态,转为用户态

                init进程

                        init进程构建交互界面,创建其他进程(login、命令行、shell...),shell进程启动其他用户进程,shell工作后就可运行其他命令和程序

                       打开控制台

                             打开/dev/console文件(210)并复制2次文件描述符,三个文件描述符分别是0(标准输入)、1(标准输出)、2(标准错误)
                             进程1打开三个文件描述符,进程1的子进程都有3个三描述符

                       挂载根文件系统
                              prepare_namespace函数中挂载根文件系统(init/do_mounts.c)
                              uboot传参中root=/dev/mmcblk0p2 rw定义根文件系统位置和权限
                              uboot传参中rootfstype=ext3定义rootfs类型。


                       执行用户态下(init进程)
                               uboot传参init=/linuxrc指定rootfs中的init程序
                               其他备用init进程:/sbin/init,/etc/init,sbin/init,/bin/sh

        start_kernel函数打印了内核初始化信息、内核模块初始化(譬如内存管理、调度系统、异常处理···)
        setup_arch:机器码架构查找,架构相关硬件初始化,uboot传参cmdline给内核
        rest_init:构建进程0,1,2,挂载根文件系统,进程1转为用户态进程

内核框架图

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网络安全公司梳理,看F5如何实现安全基因扩增

应用无处不在的当下&#xff0c;从传统应用到现代应用再到边缘、多云、多中心的安全防护&#xff0c;安全已成为企业数字化转型中的首要挑战。根据IDC2023年《全球网络安全支出指南》&#xff0c;2022年度中国网络安全支出规模137.6亿美元&#xff0c;增速位列全球第一。有专家…

「PPT 下载」Google DevFest Keynote | 复杂的海外网络环境下,如何提升连接质量

&#xff08;全网都在找的《社交泛娱乐出海作战地图》&#xff0c;点击获取&#x1f446;&#xff09; 12 月 10 日&#xff0c;“Google DevFest 2023 上海站”大会如期在上海市东方万国宴会中心举办。延续过往的技术交流碰撞、前沿技术学习基调传统&#xff0c;本届大会聚焦行…

CS110L 系统编程安全 笔记

用户向程序输入数据&#xff0c;程序分析数据&#xff0c;但是当用户的输入大于缓冲区长度时&#xff0c;数据会溢出&#xff0c;覆盖掉内存中其他内容&#xff0c;比如函数返回地址&#xff0c;从而可能导致程序返回到错误的地址执行了不安全的程序&#xff08;远程代码执行&a…

每日一练2023.12.6——Left-pad【PTA】

题目链接&#xff1a;L1-032 Left-pad 题目要求&#xff1a; 根据新浪微博上的消息&#xff0c;有一位开发者不满NPM&#xff08;Node Package Manager&#xff09;的做法&#xff0c;收回了自己的开源代码&#xff0c;其中包括一个叫left-pad的模块&#xff0c;就是这个模块…

C++STL库的 deque、stack、queue、list、set/multiset、map/multimap

deque 容器 Vector 容器是单向开口的连续内存空间&#xff0c; deque 则是一种双向开口的连续线性空 间。所谓的双向开口&#xff0c;意思是可以在头尾两端分别做元素的插入和删除操作&#xff0c;当然&#xff0c; vector 容器也可以在头尾两端插入元素&#xff0c;但是在其…

bug-ku--计算器

F12 maxlength"1" 限制的是你能输入几位数 改成3就行 来那个数相相加就能输入了 flag{464f5f406e7e182014500fc49f7aedfc}

Mybatis核心配置文件加载流程详解

Mybatis核心配置文件加载流程详解 本文将介绍MyBatis在配置文件加载的过程中&#xff0c;如何加载核心配置文件、如何解析映射文件中的SQL语句以及每条SQL语句如何与映射接口的方法进行关联。 映射配置文件 在介绍核心配置文件加载流程前&#xff0c;先给出一个简单的MyBati…

geolife 笔记:将所有轨迹放入一个DataFrame

单条轨迹的处理&#xff1a;geolife笔记&#xff1a;整理处理单条轨迹-CSDN博客 1 加载数据 import pandas as pd import numpy as np import datetime as dt import osdata_dir Geolife Trajectories 1.3/Data/ 1.1 列出所有文件夹 dirlist os.listdir(data_dir) dirlist…

golang游戏服务器 - tgf系列课程06

游戏配置 使用框架提供的游戏配置工具,只要两步,开箱即用需求描述 沿用上一节课的案例, 创建道具表,通过道具id在道具服中获取配置中道具的名称Excel 创建配置表根据项目文档中进阶教程目录下ExcelToJson的教程文档,创建指定格式的Excel文件. 脚本 生成脚本 func main() {//…

luceda ipkiss教程 48:求线路中波导的总长度

当线路中有多条波导时&#xff0c;可以一次输出所有波导的总长度&#xff1a; 如&#xff1a; 代码如下&#xff1a; from si_fab import all as pdk from ipkiss3 import all as i3class MZI_Lattice(i3.Circuit):mmi i3.ChildCellProperty()mmi_spacing i3.PositiveNumb…

C++ 对象的初始化和清理:构造函数和析构函数

目录 构造函数和析构函数 构造函数 析构函数 构造函数的分类及调用 括号法 显示法 隐式转换法 拷贝构造函数的调用时机 使用一个已经创建完毕的对象来初始化一个新对象 值传递的方式给函数参数传值 以值方式返回局部对象 构造函数调用规则 初始化列表 类对象作…

JAVA:深入了解Java中的Synchronized关键字

1、简述 在Java中&#xff0c;多线程编程是一项常见的任务&#xff0c;然而&#xff0c;它也伴随着一系列潜在的问题&#xff0c;比如竞态条件&#xff08;Race Condition&#xff09;和数据不一致性。为了解决这些问题&#xff0c;Java提供了一种同步机制&#xff0c;即synch…

项目一:IIC读写EEPROM AT24C02

回头想了想在工作中调过的EEPROM还挺多的&#xff0c;有M24M02 、M28010 、AT24C02等&#xff0c;今天讲一下AT24C02吧 一、AT24C02简介 1.1 特点 文档已经上传了&#xff0c;需要的同学可以自行下载哈&#xff0c;晚点我会把下载链接附上来。 我大概照着文档翻译了一下&am…

总线一:I2C简介(介绍看这一篇就够啦)

本节主要介绍以下内容&#xff1a; I2C协议简介 STM32的I2C特性及架构 I2C初始化结构体详解 一、I2C协议简介 I2C 通讯协议(Inter&#xff0d;Integrated Circuit)是由Phiilps公司开发的&#xff0c;由于它引脚少&#xff0c;硬件实现简单&#xff0c;可扩展性强&#xff…

2000-2021年全国各省环境规制水平数据

2000-2021年全国各省环境规制水平数据 1、时间&#xff1a;2000-2021年 2、范围&#xff1a;30省市 3、指标&#xff1a;工业污染治理完成投资、工业增加值、环境规制强度 4、计算说明&#xff1a;环境规制工业污染治理完成投资/工业增加值 5、来源&#xff1a;国家统计局…

LLM之RAG理论(一)| CoN:腾讯提出笔记链(CHAIN-OF-NOTE)来提高检索增强模型(RAG)的透明度

论文地址&#xff1a;https://arxiv.org/pdf/2311.09210.pdf 检索增强语言模型&#xff08;RALM&#xff09;已成为自然语言处理中一种强大的新范式。通过将大型预训练语言模型与外部知识检索相结合&#xff0c;RALM可以减少事实错误和幻觉&#xff0c;同时注入最新知识。然而&…

Linux - 进程间通信(中)- 管道的应用场景

前言 在上篇博客当中&#xff0c;对Linux 当中的进程通信&#xff0c;做了详细阐述&#xff0c;主要是针对父子进程的通信来阐述的同时&#xff0c;也进行了模拟实现。 对于管道也有了初步了解&#xff0c;但是这仅仅是 进程间通信的一部分&#xff0c;Linux 当中关于进程间通…

散点图,盒须图,折线图混放在一个echarts

散点图&#xff0c;何须图&#xff0c;折线图混放在一个echarts option {tooltip: {trigger: axis,axisPointer: {type: cross,crossStyle: {color: #999}}},legend: {data:[盒须图1,盒须图2,折线图,散点图]},xAxis: [{type: category,data: [周一,周二,周三,周四,周五,周六…

智能优化算法应用:基于萤火虫算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于萤火虫算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于萤火虫算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.萤火虫算法4.实验参数设定5.算法结果6.参考文…

官宣 | HelpLook已入驻企业微信应用市场

HelpLook正式入驻企业微信第三方应用市场。 HelpLook支持自定义域名与AI站内搜索&#xff0c;能够帮助企业微信用户搭建所见即所得的企业知识库、产品帮助中心、用户手册、企业博客。 | 怎么找到HelpLook并开始使用 在企业微信的第三方应用就可直接搜索HelpLook&#xff0c;添…