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

news2025/4/8 12:59:04

一、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 = (env_t *)malloc (CFG_ENV_SIZE);给env_ptr申请一片16KB的内存,即堆区
调用env_relocate_spec
gd->env_addr = (ulong)&(env_ptr->data);

总结:刚烧录的系统中环境变量分区是空白的,uboot第一次运行时加载的是uboot代码中自带的一份环境变量,叫默认环境变量(default_environment数组)。start_armboot函数中设置了执行saveenv命令,将这份默认环境变量default_environment保存到SD的env分区,之后又在env_relocate时将SD卡中的环境变量读到env_ptr指针指向的内存(堆区)。
在这里插入图片描述
在这里插入图片描述
修改代码,真的走了下面的函数体,而非上面。

我们在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];
	它肯定在这里之后将默认环境变量default_environment保存到SD卡中,
	即执行movi_write-》movi_write_env-》saveenv_movinand-》saveenv-》do_saveenv-》save命令
	怀疑:谁执行了save命令?不知道,也许是第二阶段某个代码自动执行了,我没找到,并且我在整个uboot代码中也                没找到gd->env_valid = 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调用movi_read_env(virt_to_phys((ulong)env_ptr));//virt_to_phys是虚地址转换成实地址
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分区重定位到这里

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

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

相关文章

c++(日期类)

本章主要以日期类为例&#xff0c;练习重载各种运算符&#xff0c;需要重点掌握&#xff1a; 1、日期类的<、 <、 、>、 >、 !、重载 2、日期类的 、 、-、-、、--、重载 3、日期类 - 日期类 4、日期类的 << 、>>重载 5、权限问题 目录 1、运算符…

DOM事件(下)

事件执行机制 ●今天来聊一聊事件的执行机制 ●什么是事件的执行机制呢&#xff1f; ○思考一个问题&#xff1f; ○当一个大盒子嵌套一个小盒子的时候&#xff0c;并且两个盒子都有点击事件 ○你点击里面的小盒子&#xff0c;外面的大盒子上的点击事件要不要执行 事件的传播&…

ASEMI代理ADM706SARZ-REEL原装ADI车规级ADM706SARZ-REEL

编辑&#xff1a;ll ASEMI代理ADM706SARZ-REEL原装ADI车规级ADM706SARZ-REEL 型号&#xff1a;ADM706SARZ-REEL 品牌&#xff1a;ADI /亚德诺 封装&#xff1a;SOIC-8 批号&#xff1a;2023 安装类型&#xff1a;表面贴装型 引脚数量&#xff1a;8 工作温度:-40C~85C …

JavaWeb《后端内容:2. MVC-IOC-ServletContext-事务管理-过滤器Filter》

1. 准备和回顾 本篇基于上一篇JavaWeb《后端内容&#xff1a;1. Tomcat - Servlet - Thymeleaf》 继续使用mvc进行优化&#xff0c;复制上面模块的代码&#xff0c;并新建工件和项目和配置服务器 这里可以再好好复习揣摩一下这里index页面的逻辑部分&#xff0c;尤其是关键字的…

PostgreSQL类型系统——Data Types

PostgreSQL Data Types PostgreSQL has a rich set of native data types available to users. Users can add new types to PostgreSQL using the CREATE TYPE command. PostgreSQL有一组丰富的本地数据类型可供用户使用。用户可以使用CREATE TYPE命令向PostgreSQL添加新类型…

[Gitops--12]微服务项目发布

微服务项目发布 1. 微服务项目发布 [流水线] [创建] [下一步] [创建] 1.1 mall-gateway 确认项目中的路由配置都正确 mall-gateway/src/main/resources/application.yml如果不一样就批量替换一下,一共7处 1.2 mall-auth-server mall-auth-server1.3 mall-cart 1.4 mall-c…

ChatGLM-LLaMA-chinese-insturct 学习记录(含LoRA的源码理解)

ChatGLM-LLaMA-chinese-insturct 前言一、实验记录1.1 环境配置1.2 代码理解1.2.1 LoRA 1.4 实验结果 二、总结 前言 介绍&#xff1a;探索中文instruct数据在ChatGLM, LLaMA等LLM上微调表现&#xff0c;结合PEFT等方法降低资源需求。 Github: https://github.com/27182812/Ch…

Win10任务栏透明,3个超好用解决方法!

案例&#xff1a;win10任务栏透明怎么办&#xff1f; 【我的电脑不知道为什么任务栏突然就变透明了&#xff0c;现在不知道该如何解决&#xff0c;遇到这种情况应该怎么办呀&#xff1f;】 Win10任务栏是Windows 10操作系统的一部分&#xff0c;通常默认为不透明。然而&#…

asp.net+sqlserver企业公司进销存管理系统

基于WEB的进销存管理系统主要企业内部提供服务&#xff0c;系统分为管理员&#xff0c;和员工2部分。 在本基于WEB的进销存管理系统中分为管理员&#xff0c;和普通用户2中模式&#xff0c;其中管理人员主要是对企业内商品类型。商品信息商品的出入库信息&#xff0c;以及员工…

堆栈溢出一般是什么原因?

堆栈是一个在计算机科学中经常使用的抽象数据类型。堆栈中的物体具有一个特性&#xff1a; 最后一个放入堆栈中的物体总是被最先拿出来&#xff0c; 这个特性通常称为后进先出(LIFO)队列。 堆栈中定义了一些操作。 两个最重要的是PUSH和POP。 PUSH操作在堆栈的顶部加入一 个元素…

MySQL深度分页

1. 什么是深度分页 深度分页问题的本质是在 MySQL 数据库中&#xff0c;通过 LIMIT 和 OFFSET 关键字进行分页时&#xff0c;MySQL 需要在每次查询时扫描整张表&#xff0c;直到找到当前页的数据。这种查询方式需要进行大量的磁盘 I/O 和内存操作&#xff0c;导致查询效率非常…

Microsoft Edge新功能测评体验

Microsoft Edge使用体验 Microsoft Edge是一款现代化的浏览器&#xff0c;它拥有众多功能和强大的性能&#xff0c;为用户带来更加流畅的浏览体验。 Edge最近推出了分屏功能&#xff0c;支持一个窗口同时显示两个选项卡&#xff0c;这可以大大提高生产力和多任务处理能力。 一…

什么样的蓝牙耳机佩戴舒适?蓝牙耳机佩戴舒适度排名

越来越多的人开始使用运动蓝牙耳机了&#xff0c;不仅仅是因为蓝牙耳机的它无耳机线的束缚&#xff0c;日常还很便携&#xff0c;市面上的蓝牙耳机质量参差不齐&#xff0c;有些佩戴舒适度也比较差&#xff0c;下面整理了几款评分还不错的几款蓝牙耳机。 一、南卡小音舱Lite2蓝…

第四十四章 Unity 滑动条 (Slider) UI

本章节我们介绍滑动条 (Slider)&#xff0c;它允许用户通过拖动鼠标从预定范围中选择数值。首先&#xff0c;我们点击菜单栏“GameObject”->“UI”->“Slider”&#xff0c;调整其位置&#xff0c;最终效果如下 我们发现滑动条 (Slider)下面有三个子游戏对象Background&…

如何使DocuWare成为所有部门的数据中心

如何使DocuWare成为所有部门的数据中心 自动化流程通常需要多个部门的数据&#xff0c;而各个部门通常使用不同的软件。 DocuWare可帮助您集中管理所有信息&#xff0c;并将信息应用于您的进程和工作流程当中。 您的公司使用不同的系统&#xff0c;但您又想将这些数据整合在一…

手敲Mybatis(十)-完善ORM框架支持增删改查

我们把基本的功能都完成了&#xff0c;解析xml、构建映射代理、执行sql&#xff0c;解析处理结果&#xff0c;目前这些只支持查询&#xff0c;我们还差添加下增删改的功能&#xff0c;本章节就来完善下增删改&#xff0c;其实本章节比较简单&#xff0c;因为之前的每个章节都已…

这一篇LiveData掉不掉价(使用->原理分析->粘性事件解决)

1. 简介 LiveData 是一种可观察的数据存储器类。与常规的可观察类不同&#xff0c;LiveData 具有生命周期感知能力&#xff0c;意指它遵循其他应用组件&#xff08;如 activity、fragment 或 service&#xff09;的生命周期。这种感知能力可确保 LiveData 仅更新处于活跃生命周…

数据备份系列:Rsync 备份详解(二)

一、Rsync Cron 场景使用 在对数据备份要求实时性不高的情况下&#xff0c;可优先考虑该场景&#xff0c;选择一个合适的时间&#xff0c;对数据进行定时远程增量同步。 在《数据备份系列&#xff1a;Rsync 备份详解&#xff08;一&#xff09;》中我们已经对服务搭建以及远程…

【虚幻引擎】UE5数据表格导入

数据表 顾名思义&#xff0c;DataTable是一种表格&#xff0c;里面装着大量游戏相关的数据&#xff0c;这些数据会按照其含义和用途分类&#xff0c; 其中&#xff0c;数据字段可以是UObject的任意有效属性&#xff08;包括资产的引用信息&#xff09;。设计师若要将 CSV文件导…

c++类的静态变量、静态函数 笔记

正文&#xff1a; 1、看下面这个是一个常规的类 #include <iostream> #include <windows.h> using namespace std; class BOX{int callsNum1;public:BOX(){callsNum;};int fun(){return callsNum;}; }; // int BOX::callsNum1;// 程序的主函数 int main() {SetCo…