通过学习do_printenv这个函数,去学习内里的字符串解析方法。
第一步,研究do_printenv要知道uboot中的环境变量是怎样存储的。
在do_printenv里会调用到env_get_char函数,返回值是unsigned char类型的。
通过env_get_char函数可以获得一个字符。
从哪里获得呢?
从default_environment[ ]这个数组获得。
这个数组里面的元素排列都是紧密排列的,用\0隔开、
就是这样排列的。
相当于这样
char buf[ ] = {“abc” “\0” “ccd” “\0”};
然后我测试了一下,如果改成下面这样子
char buf[ ] = {“abc\0ccd\0”};
效果是一模一样的,但是如果试图用printf()去打印出来的话就不全,因为printf()遇到\0就停了。可以试着用puts打印出来,两种的效果是一样的
结论就是:
环境变量放在一个字符串数组里面,紧密排列,各个环境变量之间是用\0分开的。
而且还要注意,这里存的是环境变量=xxxxx这样的信息,而不只是环境变量名字。
知道存储方法后,要怎么取?
下面是do_printenv函数的截图
分两部分分析
第一部分: end if argc ==1
之前
如果只传1个参数,也就是只print或者printenv的话,执行第一部分argc==1的情况
这个很好分析。
有一些细节要注意,就是for循环里面++nxt,为什么不可以是nxt++,其实是可以的,因为这是在for循环的表达式3里面。
引用:
链接: for循环中++i 和 i++ 的区别
根据上面的for循环的语法定义 ++i 和 i++的结果是一样的,都要等代码块执行完毕才能执行语句3,但是性能是不同的。在大量数据的时候++i的性能要比i++的性能好原因:
i++由于是在使用当前值之后再+1,所以需要一个临时的变量来转存。
而++i则是在直接+1,省去了对内存的操作的环节,相对而言能够提高性能.
为了保险起见,我把++nxt改成nxt++,然后烧录到到sd卡里面启动,效果一样的。
最后一句
printf("\nEnvironment size: %d/%ld bytes\n",i, (ulong)ENV_SIZE);
结果
前面的i是遍历完所有的环境变量,得到的大小,如果你用set xxx=yyy去增加环境变量的话,则这个值会变,不再是308了。
后面的16380是怎么来的呢?
通过这些宏得到是16380,注意ENV_HEADER_SIZE到底是哪个,要分清楚哪个宏有定义哪个宏没定义。
第二部分: 剩下的,如果传多个参数的
这里困扰我的主要在envmatch函数里面
如果改写一下就会好一点理解。
int envmatch (uchar *s1, int i2)
{
while (*s1 == env_get_char(i2++))
{
if (*s1++ == '=')
{
return(i2);
}
}
if (*s1 == '\0' && env_get_char(i2-1) == '=')
return(i2);
return(-1);
}
我测试过,改成这样重新烧录效果一样,证明改写的方法是正确的。
另外很重要的点再i2++和*s1++上,一定要注意
env_get_char(i2++)的理解:
i2++还好理解一点,没那么容易栽在这里,这里就是说先env_get_char(i2),然后再i2=i2+1;
if (*s1++ == '=') if语句的理解
尽管这个条件不成立,但是条件里面执行了的涉及到赋值运算的话,那么还是要进行赋值的,也就是每一次判断的时候,s1其实会变,这里的s改变就会导致while循环里面的s1也跟着改变。
另外虽然++的优先级是大于*,但是就不能理解为是先s1++了再解引用,这是不对的。
正确的理解是这样
链接: 应该理解为,由于后++优先级高于*,应该先p++,后取值,但因为是后++,所以先执行*p,然后等赋值完成以后,p再++。.
对于这里,也就是说先解引用s1,即*s1,判断完之后了,s1再++。
对于这里我也做了测试,我把整一套的k=envmatch()给再现了一次
#include <stdio.h>
//用到的函数的声明
static char env_get_char_init (int index);
char env_get_char (int index);
int envmatch (char *s1, int i2);
int main(int argc,char *argv[])
{
int k;
char *name = argv[1];
k = envmatch((char *)name, 4); //传参4是跟根据我假设的default_environment[]而定的
printf("k = %d\n",k);
return 0;
}
int envmatch (char *s1, int i2)
{
/* printf("before while: i2 = %d\n",i2);
printf("before while: s1 = %c\n",*s1);
printf("before while: env_get_char(i2++) = %c\n",env_get_char(i2++));
i2=i2-1; */
while (*s1 == env_get_char(i2++))
{
/* printf("after while : *s1 = %c\n",*s1);
printf("after while : i2 = %d\n",i2);
printf("after while : *s1++ = %c\n",*s1++);
s1=s1-1; */
if (*s1++ == '=')
{
// printf("after while if : i2 = %d\n",i2);
return(i2);
}
//printf("after if: *s1 = %c\n",*s1);
}
if (*s1 == '\0' && env_get_char(i2-1) == '=')
return(i2);
return(-1);
}
char env_get_char (int index)
{
char c;
c = env_get_char_init(index);
return (c);
}
static char env_get_char_init (int index)
{
char c;
//自己设置一个envirenment[]数组
char default_environment[] = {"a=1\0bc=3\0bd=45\0"};
c = default_environment[index];
return (c);
}
对于我这份代码
编译之后就 ./a.out bc
传参进去测试,得到返回值k是等于7,再对照environment数组,
第7个元素是3,到时候再把3put出来,就变成了bc=3实现了env的打印。
总结
当你看懂了的时候会觉得这不难,但是当你看不懂的时候觉得很难,难点就在于 *s1++
,还有while循环、if循环,尽管只有一句语句,但是就是少了{}也迷惑了我很久,最终是自己改了代码测试过发送加上之后没错,把层次捋清楚,再查*p++
,得到正确的执行步骤。
用到的测试的方法有:
方法1.自己把代码抽出来,然后编译测试
方法2.直接改源码然后烧录进去看结果。
最终把这个do_printenv给看懂看透了。
以后遇到相关的字符串解析的话可以利用uboot中的do_printenv的解析方法。