uboot的环境变量相关源码分析

2023-05-16

一、uboot的环境变量基础

1.1、环境变量的作用

(1)让我们可以不用修改uboot的源代码,而是通过修改环境变量就可以影响uboot运行时的一些特性。譬如说修改bootdelay环境变量就可以更改系统开机自动启动时倒计时的秒数。

1.2、环境变量的优先级

环境变量的优先级高于程序中的全局变量,因此是可以覆盖我们程序中的全局变量的,通过这样的设计使得修改环境变量可以影响我们程序的运行。如果环境变量为空则使用程序中全局变量的值;如果环境变量不为空则优先使用环境变量对应的值。
譬如machid(机器码)。uboot中在x210_sd.h中以硬编码的形式定义了一个机器码2456。如果要修改uboot中配置的机器码,可以直接去修改x210_sd.h中的机器码,但是每次修改源代码后都需要重新配置编译烧录,非常麻烦;
比较简单的方法就是使用环境变量machid。我们在uboot命令行底下输入set machid 0x999类似这样然后保存,SD卡就有了machid环境变量,重启uboot后uboot会优先使用machid对应的环境变量,这就是优先级问题。

1.3、环境变量在uboot中工作方式

(1)环境变量模板,在uboot/common/env_common.c中default_environment,这是一个字符数组,大小为CFG_ENV_SIZE(0x4000 = 16kb),里面内容就是很多个环境变量连续分布组成的,每个环境变量最末端以’\0’结束。
在这里插入图片描述
(2)SD卡中环境变量分区,即uboot的env分区。存储时其实是把DDR中的环境变量整体的写入SD卡中分区里。当我们使用saveenv命令时其实所有的环境变量都被保存了一遍,而不是只保存更改了的。
(3)DDR中环境变量,有两份,一份是环境变量模板default_environment字符数组,default_environment是一个全局变量,存于data段中,重定位uboot时就被重定位到DDR中一个内存地址处了;在uboot的第二阶段调用了env_relocate之后,在堆区申请了一份内存,用env_ptr指向这个内存,并且将环境变量模板memcpy到这里,所以DDR中会有两份环境变量。

env_ptr = (env_t *)malloc (CFG_ENV_SIZE);给env_ptr申请一片16KB的内存,即堆区
调用env_relocate_spec
		void env_relocate_spec_movinand(void)
		{
			#if !defined(ENV_IS_EMBEDDED)
			#if defined(CONFIG_CMD_MOVINAND)
				uint *magic = (uint*)(PHYS_SDRAM_1);//0x3000 0000

				if ((0x24564236 != magic[0]) || (0x20764316 != magic[1])) {
						movi_read_env(virt_to_phys((ulong)env_ptr));
						//由于我save保存了环境变量,下次上电会走这
				}	
				if (crc32(0, env_ptr->data, ENV_SIZE) != env_ptr->crc)
						return use_default();//初次上电,SD中没有env,则走这里
			#endif /* CONFIG_CMD_MOVINAND */
			#endif /* ! ENV_IS_EMBEDDED */
		}
gd->env_addr = (ulong)&(env_ptr->data);

总结:刚烧录的系统中环境变量分区是空白的,uboot第一次运行时内存中有一份环境变量模板(default_environment数组),是uboot代码自带的;在第二阶段调用env_relocate时将这份环境变量模板default_environment复制(memcpy)到env_ptr指向的内存(堆区),之后使用的也是堆区这份。
直到我们在命令行底下执行saveenv命令,saveenv实际调用了movi_write,将内存中env_ptr指向的这一份环境变量复制到SD卡env分区中;
然后第二次上电,SD卡中就有环境变量了,在第二阶段调用env_relocate时,通过if else语句判断,得知SD卡中有环境变量,接下来就会执行movi_read函数将SD卡中的环境变量加载到DDR中的这份堆区。
在这里插入图片描述
在这里插入图片描述
修改代码,真的走了下面的函数体,而非上面。

我们在saveenv时DDR中的环境变量会被更新到SD卡中的环境变量中,就可以被保存下来,下次开机会在环境变量relocate时会将SD卡中的环境变量会被加载到DDR中去。

二、环境变量相关命令源码解析

2.1、printenv

U_BOOT_CMD(
    printenv, CFG_MAXARGS, 1,    do_printenv,
    "printenv- print environment variables\n",
    "\n    - print values of all environment variables\n"
    "printenv name ...\n"
    "    - print value of environment variable 'name'\n"
);

与printfenv绑定的函数是do_printenv,在uboot\common\cmd_nvedit.c(85~136行)
(1)这个命令有2种使用方法。第一种打印所有的环境变量:print
;第二种是选择性的打印出环境变量:print name1 name2…
在这里插入图片描述
(2)do_printenv函数首先区分参数个数argc=1还是不等于1的情况,
若argc=1那么就循环打印所有的环境变量出来;
若argc不等于1,则后面的参数就是要打印的环境变量,给哪个就打印哪个。

//env_common.c(185~206行)
uchar env_get_char_memory (int index)
{
    if (gd->env_valid) {//代表重定位了,使用SD卡复制到内存的环境变量
        return ( *((uchar *)(gd->env_addr + index)) );
    //实际uboot执行走了这一条路    
    } else {//代表还未重定位,使用默认环境变量
        return ( default_environment[index] );
    }
}


uchar env_get_char (int index)
{
    uchar c;

    /* if relocated to RAM */
    if (gd->flags & GD_FLG_RELOC)
        c = env_get_char_memory(index);    //实际uboot执行走了这一条路
    else
        c = env_get_char_init(index);    //说明我们内存中没有环境变量
    return (c);
}

//uboot\common\cmd_nvedit.c(85~136行)
int do_printenv (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
    int i, j, k, nxt;
    int rcode = 0;

    if (argc == 1) {        /* Print all env variables    */
                 //env_get_char函数是获取内存中环境变量的值,i是相对首地址的偏移量
        for (i=0; env_get_char(i) != '\0'; i=nxt+1) {//i指向每个环境变量的第一个元素
            for (nxt=i; env_get_char(nxt) != '\0'; ++nxt)//nxt等于每个环境变量的长度
                ;
            for (k=i; k<nxt; ++k)    //把每个环境变量都打印出来,一次循环结束只打印一个环境变量
                putc(env_get_char(k));
            putc  ('\n');

            if (ctrlc()) {            //printenv命令执行过程是打印所有环境变量,期间支持crtl+c打断
                puts ("\n ** Abort\n");
                return 1;
            }
        }

        printf("\nEnvironment size: %d/%ld bytes\n",
            i, (ulong)ENV_SIZE);//ENV_SIZE = 0x4000 - 4

        return 0;
    }

    for (i=1; i<argc; ++i) {    /* print single env variables    */
        char *name = argv[i];

        k = -1;

        for (j=0; env_get_char(j) != '\0'; j=nxt+1) {

            for (nxt=j; env_get_char(nxt) != '\0'; ++nxt)
                ;
            k = envmatch((uchar *)name, j);//将printenv后的参数和每个环境变量进行比较,然后打印
            if (k < 0) {
                continue;
            }
            puts (name);
            putc ('=');
            while (k < nxt)
                putc(env_get_char(k++));
            putc ('\n');
            break;
        }
        if (k < 0) {
            printf ("## Error: \"%s\" not defined\n", name);
            rcode ++;
        }
    }
    return rcode;
}

(3)argc=1时用双重for循环来依次处理所有的环境变量的打印。第一重for循环就是处理所有环境变量,有多少个环境变量就循环多少次。第二重for循环就是单独打印出一个环境变量中的所有字符
(4)要看懂这个函数,首先要明白整个环境变量在内存中如何存储的问题。即以字符数组default_environment为格式存储
,default_environment复制给SD卡env分区,SD卡env分区复制给env_ptr指针指向的内存,他们三者的格式都是一样的。
(5)关键点:要明白环境变量在内存中存储的方式;

2.2、setenv

与setenv绑定的函数是do_setenv,do_setenv实际工作调用_do_setenv

int _do_setenv (int flag, int argc, char *argv[])
{
    int   i, len, oldval;
    int   console = -1;
    uchar *env, *nxt = NULL;
    char *name;
    bd_t *bd = gd->bd;

    uchar *env_data = env_get_addr(0);//获取内存中环境变量数组的首地址

    if (!env_data)    /* need copy in RAM */
        return 1;                        //说明内存中没有环境变量

    name = argv[1];                //参数1

    if (strchr(name, '=')) { //提示设置时不要加等号 譬如set a=1,正确使用应该是 set a 1
        printf ("## Error: illegal character '=' in variable name \"%s\"\n", name);
        return 1;
    }

    /*
     * search if variable with this name already exists
     */
    oldval = -1;
    for (env=env_data; *env; env=nxt+1) {//搜索此参数名称的环境变量是否已经存在 
        for (nxt=env; *nxt; ++nxt)
            ;
        if ((oldval = envmatch((uchar *)name, env-env_data)) >= 0)
            //oldval>=0,说明存在,并且此时env这个指针指向那个找到的环境变量的存储区域
            break;
    }

    /*
     * Delete any existing definition
     */
    if (oldval >= 0) {
        //如果原来就有先清空环境变量的存储区域再覆盖
        }
                //如果原来没有则在最后创建一个环境变量。
     return 0;
 }

1)setenv的思路就是:先去DDR中的环境变量处寻找原来有没有这个环境变量,如果原来就有则需要覆盖原来的环境变量,如果原来没有则在最后新增一个环境变量即可。

(2)本来setenv做完上面的就完了,但是还要考虑一些附加问题。
问题一:环境变量太多导致DDR中的字符数组溢出的解决方法。
问题二:有些环境变量如baudrate、ipaddr等,在gd中有对应的全局变量。这种环境变量在set更新的时候要同时去更新对应的全局变量,否则就会出现在本次运行中环境变量和全局变量不一致的情况。

2.3、saveenv

与saveenv绑定的函数是do_saveenv,实现环境变量的保存操作主要是saveenv函数,在uboot/common/cmd_nvedit.c中
在这里插入图片描述
在这里插入图片描述
(1)从uboot实际执行saveenv命令的输出,以及do_saveenv函数的内容,可以分析出:uboot实际使用的是env_auto.c文件中的相关内容,并且可以从x210_sd.h中的配置宏(#define CFG_ENV_IS_IN_AUTO)进一步确认。在env_auto.c中是使用宏定义的方式去条件编译了各种常见的flash芯片(如movinand、norflash、nand等)。主要体现在文件里的saveenv函数中通过读取 INF_REG 从而知道我们的启动介质,然后调用这种启动介质对应的操作函数来操作。

int saveenv(void)
{
#if defined(CONFIG_S5PC100) || defined(CONFIG_S5PC110) || defined(CONFIG_S5P6442)
    if (INF_REG3_REG == 2)
        saveenv_nand();
    else if (INF_REG3_REG == 3)//INF_REG3_REG是开发板中的一个寄存器,判断我们从哪里启动的
        saveenv_movinand();
    else if (INF_REG3_REG == 1)
        saveenv_onenand();
    else if (INF_REG3_REG == 4)
        saveenv_nor();
#elif    defined(CONFIG_SMDK6440)
    if (INF_REG3_REG == 3)
        saveenv_nand();
    else if (INF_REG3_REG == 4 || INF_REG3_REG == 5 || INF_REG3_REG == 6)
        saveenv_nand_adv();
    else if (INF_REG3_REG == 0 || INF_REG3_REG == 1 || INF_REG3_REG == 7)
        saveenv_movinand();
#else   // others
    if (INF_REG3_REG == 2 || INF_REG3_REG == 3)
        saveenv_nand();
    else if (INF_REG3_REG == 4 || INF_REG3_REG == 5 || INF_REG3_REG == 6)
        saveenv_nand_adv();
    else if (INF_REG3_REG == 0 || INF_REG3_REG == 7)
        saveenv_movinand();
    else if (INF_REG3_REG == 1)
        saveenv_onenand();
#endif
    else
        printf("Unknown boot device\n");

    return 0;
}

(2)INF_REG寄存器地址:E010F000+0C=E010_F00C,数据手册中含义是用户自定义数据。我们在start.S中判断启动介质后将#BOOT_MMCSD(就是3,定义在x210_sd.h)写入了INF_REG这个寄存器,所以saveenv函数中读出的肯定是3,所以实际执行的函数是:saveenv_movinand
在这里插入图片描述
在这里插入图片描述
saveenv_movinand函数体:

int saveenv_movinand(void)
{
#if defined(CONFIG_CMD_MOVINAND)
        movi_write_env(virt_to_phys((ulong)env_ptr));
//virt_to_phys函数将虚地址转化为实地址
        puts("done\n");

        return 1;
#else
    return 0;
#endif    /* CONFIG_CMD_MOVINAND */
}

(3)真正执行保存环境变量操作的是:cpu/s5pc11x/movi.c中的movi_write_env函数,这个函数肯定是写sd卡的,将DDR中的环境变量集合(其实就是env_ptr指向的内存区域,大小16kb = 32个扇区)写入iNand中的env分区中。
在这里插入图片描述
(4)raw_area_control是uboot中规划iNnad/SD卡的原始分区表,这个里面记录了我们对iNand的分区,env分区也在这里,下标是2,movi_read函数里面就是调用驱动部分的写SD卡/iNand的底层函数。

2.4、uboot内部获取环境变量

getenv和getenv_r不是一个环境变量,是uboot内部的一个函数,用于获取环境变量的值,在uboot/common/cmd_nvedit.c中。

2.4.1、getenv

(1)是不可重入的。

char *getenv (char *name)
{
    int i, nxt;

    WATCHDOG_RESET();

    for (i=0; env_get_char(i) != '\0'; i=nxt+1) {
        int val;

        for (nxt=i; env_get_char(nxt) != '\0'; ++nxt) {
            if (nxt >= CFG_ENV_SIZE) {
                return (NULL);
            }
        }
        if ((val=envmatch((uchar *)name, i)) < 0)
            continue;
        return ((char *)env_get_addr(val));
    }

    return (NULL);
}

(2)实现方式就是去遍历数组,挨个拿出所有的环境变量比对name,找到相等的直接返回这个环境变量的首地址。

2.4.2、getenv_r

(1)可重入版本。

int getenv_r (char *name, char *buf, unsigned len)
{
    int i, nxt;

    for (i=0; env_get_char(i) != '\0'; i=nxt+1) {
        int val, n;

        for (nxt=i; env_get_char(nxt) != '\0'; ++nxt) {
            if (nxt >= CFG_ENV_SIZE) {
                return (-1);
            }
        }
        if ((val=envmatch((uchar *)name, i)) < 0)
            continue;
        /* found; copy out */
        n = 0;
        while ((len > n++) && (*buf++ = env_get_char(val++)) != '\0')
            ;
        if (len == n)
            *buf = '\0';
        return (n);
    }
    return (-1);
}

(2)getenv函数是直接返回这个找到的环境变量在DDR中环境变量处的地址,而getenv_r函数的做法是找到了DDR中环境变量地址后,将这个环境变量复制一份到提供的buf中,而不动原来DDR中环境变量。
所以差别就是:getenv中返回的地址只能读不能随便乱写,而getenv_r中返回的环境变量是在自己提供的buf中,是可以随便改写加工的。

三、总结

(1)功能是一样的,但是可重入版本会比较安全一些,建议使用。
(2)有关于环境变量的所有操作,主要在于理解环境变量在DDR中的存储方法,环境变量和gd全局变量的关联和优先级,理环境变量在存储介质中的存储方式(专用raw分区)。
(3)环境变量到底在内存的哪里?以下是我的分析过程。

fastboot只烧录了uboot、kernel、rootfs到SD卡,并没有烧录环境变量到env分区
上电,uboot启动,此时SD卡中没有环境变量可以使用

1.uboot第二阶段start_armboot函数中调用env_init
gd->env_addr  = (ulong)&default_environment[0];
gd->env_valid = 1;
2.后续start_armboot函数中调用env_relocate
3.env_relocate中
3.1env_ptr = (env_t *)malloc (CFG_ENV_SIZE);给env_ptr申请一片16KB的内存,即堆区
3.2调用env_relocate_spec
3.3gd->env_addr = (ulong)&(env_ptr->data);//将这片内存作为环境变量的区域使用
4.env_relocate_spec调用env_relocate_spec_movinand
5.env_relocate_spec_movinand中
5.1uint *magic = (uint*)(PHYS_SDRAM_1);//PHYS_SDRAM_1 = 0x3000 0000
5.2
5.2.1如果SD中有环境变量,则加载,加载方法是调用movi_read_env(virt_to_phys((ulong)env_ptr));//virt_to_phys是虚地址转换成实地址
5.2.2如果没有则调用use_default,将uboot中封装的一套默认环境变量default_environment复制到堆区,之后使用堆区的那一份。

假如5.2.1生效
6.movi_read_env调用movi_read
6.1movi_read(raw_area_control.image[2].start_blk,
raw_area_control.image[2].used_blk, addr);//start_blk是env分区的起始扇区号,used_blk是env分区的大小(多少个扇区),addr:将SD卡内的env分区重定位到这里
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

uboot的环境变量相关源码分析 的相关文章

  • 面试了8家软件公司测试岗位,面试题大盘点,我真的尽力了。

    项目的测试流程 拿到需求文档后 xff0c 写测试用例 审核测试用例 等待开发包 部署测试环境 冒烟测试 xff08 网页架构图 xff09 页面初始化测试 xff08 查看数据库中的数据内容和页面展示的内容是否一致 xff0c 并且是否按
  • 2021年最强软件测试工程师Linux面试题及答案

    前言 xff1a 大家好 xff0c 我是一菲 xff0c 前段时间有很多朋友给我私信留言 xff1a 在面试软件测试工作时 xff0c 部分的liunx的问题答不上来 xff0c 于是一菲快马加鞭 xff0c 连夜给大家整理了一份关于li
  • iOS APP测试方法和测试工具 大揭秘

    随着移动互联网的发展 xff0c 以及智能手机的普及 xff0c 各种各样的 APP 层出不穷 xff0c 大家可以数一数自己手机上安装的 APP xff0c 随随便便几十个 xff0c 上百个 xff0c 所以现在 APP 测试的需求也越
  • oracle sql loader命令与ctl写法

    xfeff xfeff sql loader 的特点 oracle自己带了很多的工具可以用来进行数据的迁移 备份和恢复等工作 但是每个工具都有自己的特点 比如说exp和imp可以对数据库中的数据进行导出和导出的工作 xff0c 是一种很好的
  • 史上最全的字符串格式化方法,学这些就够用了

    一 思考 1 什么是字符串格式化 将变量 xff08 对象 xff09 的值填充到字符串中 在字符串中解析Python表达式 对字符串进行格式化显示 左对齐 右对齐 居中对齐保留数字有效位数 2 你学过的字符串格式化方式有哪些 字符串格式化
  • Linux实现黑客帝国效果,超级简单

    MATRIX是Linux的环境下黑客帝国风格的屏保 xff0c 绿色的字母在屏幕上刷刷如雨水落下 xff0c 偶尔看看能回忆起程序猿的黑客梦 首先要在网上找到CMATRIX安装包 xff0c 然后安装 xff0c 需要的话找柠檬班小米 xf
  • 10年互联网职场过来人给测试专业大学生的学习建议

    改进学习方法 xff0c 就如改进你的测试方法一样 不管你面临的是什么环境和挑战 xff0c 值得期许的 就值得去尝试 1 关于学习 在学校期间以专业课为主 xff0c 专业理论知识越扎实 xff0c 后期实践才越容易深入理解且上手更快 对
  • 使用 GitHub Copilot 自动化测试

    代码完成并不是什么新鲜事 像 IntelliSense 这样的工具已经允许开发人员通过尝试自动完成他们正在编写的函数或语句的名称来提高工作效率 xff0c 但是可用的工具只有一定程度的实际 智能 可用 随着 GitHub 的 Copilot
  • 想让你的接口自动化测试更加有效? 这个统计方法必须掌握

    覆盖率概念 接口自动化测试是现代软件开发中不可或缺的一环 xff0c 它能够帮助开发团队自动化执行测试用例 xff0c 以快速而准确地发现并修复软件缺陷 而覆盖率统计则是在测试执行完成后 xff0c 帮助测试团队了解哪些代码路径被覆盖了 x
  • 大小端字节序详解

    目录 引文 大小端介绍 xff08 1 xff09 什么是大端小端 xff08 2 xff09 为什么有大端和小端 xff08 3 xff09 笔试题讲解 引文 在开始正文之前 xff0c 我想先问一下大家 xff0c 内存中是怎样存放一个
  • 【C库函数】strcat函数详解

    目录 strcat 函数原型 参数讲解 返回值详解 函数讲解 xff08 1 xff09 源字符串和目标字符串都必须以 39 0 39 结束 xff08 2 xff09 目标空间必须足够大 xff0c 能容纳下源字符串的内容 xff08 3
  • CAN协议

    CAN xff08 Controller Area Network xff09 是一种常见的串行总线通信协议 xff0c 用于在汽车 工业控制和其他应用中传输数据 它是一种高效 可靠 安全的通信协议 xff0c 具有广泛的应用 下面是CAN
  • 串口协议简介

    串口协议是一种基于串行通信的数据传输协议 它通过串口接口将数据以串行的方式传输 串口协议通常包括物理层 数据链路层和应用层三个部分 xff0c 其中物理层主要定义了串口接口的电气特性 xff0c 数据链路层定义了数据的传输方式和错误检测机制
  • CAN协议总线仲裁原理:数据发送权争夺

    CAN总线仲裁原理是指在多个CAN节点同时发送数据时 xff0c 如何避免冲突 并选择一个节点作为发送者 CAN总线的仲裁原理基于一个分布式仲裁机制 xff0c 它可以快速而可靠地确定哪个节点可以获得总线控制权 xff0c 从而发送数据 C
  • Spring整合JMS(一)——基于ActiveMQ实现

    1 1 JMS简介 JMS的全称是Java Message Service xff0c 即Java消息服务 它主要用于在生产者和消费者之间进行消息传递 xff0c 生产者负责产生消息 xff0c 而消费者负责接收消息 把它应用到实际的业务需
  • getopt函数详解

    getopt 函数是C语言中一个常用的命令行参数解析函数 xff0c 它可以方便地解析命令行输入的参数 xff0c 以便程序对不同参数进行不同的处理 本文将详细讲解getopt 函数的使用方法和注意事项 xff0c 分点阐述如下 xff1a
  • UCOSIII

    UCOSIII简介 xff1a UCOSIII是MicroC OS III的改编版本 xff0c 主要是用于实时系统中的任务调度 xff0c 它是嵌入式系统中应用最广泛的操作系统之一 用函数说明 xff1a 1 OSInit 用于初始化UC
  • UCOSIII-任务创建-库函数

    创建任务 xff1a OSTaskCreate OS TCB amp StartTaskTCB 任务控制块 xff08 amp 传地址 xff09 CPU CHAR 34 start task 34 任务名字 xff08 可以随便写 xff
  • ucosiii-常用api

    uC OS III 提供了许多 API 函数 xff0c 可以根据需要选择使用 以下是一些常用的 uC OS III API 函数 xff1a 任务管理 API OSTaskCreate xff1a 创建一个新任务 xff1b OSTask
  • windows 清除 .git 文件夹

    有时我们需要将 git 管理项目中的 git文件夹删除 xff0c 但是如果项目太多 xff0c 一个一个手动删除太麻烦 xff0c 这时候可以用 bat 批处理文件删除 xff0c 具体操作如下 桌面 右击 新建文本文档 xff0c 此时

随机推荐

  • Your anti-virus program might be impacting your build performance.解决方案

    Your anti virus program might be impacting your build performance 解决方案 在使用 AndroidStudio 时 xff0c 经常会弹出框提示 xff1a Your ant
  • init.rc 启动 shell 脚本 开机执行脚本 init.rc执行shell脚本

    Android 重启时执行 shell 脚本 init rc 执行 shell 脚本 最近有个需求 xff0c 需要生成系统的默认配置 xff0c 使得在系统开机后 xff0c 直接读取已经配置好的文件 当时想的解决方案是 xff0c 在编
  • android 10 自定义系统服务接口给app调用

    Android 安卓自定义系统服务 最近有个需求 xff0c 要增加系统服务 xff0c 生成第三方 jar 包提供给第三方应用调用 xff0c 而且 jar 包必须用特定的包名 xff0c 最后生成的 jar 包不能包含 framewor
  • Android java.lang.NoSuchMethodError: No virtual method ;or its super classes (declaration of

    修改 AOSP 源码后调用错误 java lang NoSuchMethodError No virtual method in class or its super classes declaration of appears in sy
  • 谷歌使用技巧 20 招

    第一招 xff1a 使用搜索栏下方的 Tab 栏 xff0c 可以快速搜索 视频 图片 新闻第二招 xff1a 使用引号 xff0c 默认搜索会去搜索包含输入关键字的结果 xff0c 用 34 holy shit 34 会去进行整句搜索第三
  • 索引算法原理解析(B-tree以及磁盘存储原理)

    刚开始学习的时候 xff0c 百度去查 xff0c 但发现好多说得太复杂不好理解 xff0c 结合各个文章总结一下 xff08 建议大概看文字 xff0c 不理解不要紧 xff0c 然后再看图的执行步骤然后在结合文字 xff0c 这样一切就
  • C语言-----结构体内存对齐

    结构体内存对齐规则 xff1a 第一个成员在结构体变量偏移量为0 的地址处 其他成员变量要对齐到某个数字 xff08 对齐数 xff09 的整数倍的地址处 对齐数 61 编译器默认的一个对齐数与该成员大小中的较小值 vs中默认值是8 Lin
  • dpi计算 density 取值范围

    PPI DPI计算公式 Density 61 sqrt span class token punctuation span span class token punctuation span span class token car wp
  • Repo 流程

    First repo init creates the repo directory clones the git repository https android googlesource com tools repo to repo r
  • *** buffer overflow detected ***: terminatedAborted (core dumped)解决

    在执行一个程序的时候出现了下面的这个错误 xff0c 明明在Ubuntu下面已经编译好了 xff0c 执行的时候除了问题 xff0c 于是换了台电脑尝试还是一样 buffer overflow detected terminated Abo
  • 虚拟机Ubuntu远程启动Jetson nano RVIZ图形界面失败

    INFO 1644470251 178517 Rosapi started map manager 6 process has finished cleanly log file home jetson ros log fb3115c6 8
  • YOLOv5训练自己的数据集实现视频的识别

    写在前面 我本来是使用这个模型进行手写签名的定位 xff0c 但是因为比赛的主办方原因 xff0c 数据不允许公开 xff0c 所以我使用动物世界的一段开头视屏来制作我的数据集 这整个模型跑通的过程中 xff0c 我参考了很多不错的博客 x
  • stm32---OLED(SSD1306)

    OLED模块优缺点 优点 xff1a 尺寸小 xff0c 分辨率高 xff0c 低压3 3V就可工作 xff0c 支持多种接口方式 xff0c 该模块提供了总共4种接口包括 xff1a 6800 8080两种并行接口方式 4线的穿行SPI接
  • stm32---ADC模数转换

    ADC xff1a 模数转换器 xff0c 将模拟信号 xff08 0v xff0c 3v xff0c 6v等 xff09 转换为表示一定比例电压值的数字信号 xff08 1 xff0c 2 xff0c 3等 xff09 STM32F10x
  • stm32---DMA

    DMA 全称Direct Memory Access xff08 直接存储器访问 xff09 xff0c 把一个地址空间的值 复制 到另一个地址空间 xff0c 使用DMA传输方式无需CPU直接控制传输 xff0c 通过硬件为RAM和IO设
  • STM32通信---CAN

    一 CAN是什么 xff1f CAN xff0c 全称为 Controller Area Network xff0c 即控制器局域网 xff0c 是一种多主方式的串行通讯总线 xff0c 是国际上应用最广泛的现场总线之一 二 CAN的起源
  • 计算机网络学习笔记3-抓包工具的使用

    Wireshark win64 2 6 2的使用 安装一路next 在发送数据之前 运行抓包工具 当数据发送之后 记得停止抓包
  • linux运行java项目的shell脚本

    bin bash WORKDIR 61 home xiaohong Baowen cd WORKDIR WEB INF classes for file in 96 ls WORKDIR WEB INF lib jar 96 do CLAS
  • 基于STM32F103的智能门禁系统

    0 前言 本人大二软工菜鸟一枚 xff0c 大神不喜勿喷 1 功能演示 点这里功能演示 2 硬件选型 序号名称备注1STM32F103C8T6开发板用于主控2AS608指纹模块指纹解锁3RFID RC522射频模块刷卡解锁40 96寸四针O
  • uboot的环境变量相关源码分析

    一 uboot的环境变量基础 1 1 环境变量的作用 1 让我们可以不用修改uboot的源代码 xff0c 而是通过修改环境变量就可以影响uboot运行时的一些特性 譬如说修改bootdelay环境变量就可以更改系统开机自动启动时倒计时的秒