u-boot中环境变量操作和hash表

转载地址:https://www.2cto.com/kf/201609/550058.html

u-boot对环境变量的处理主要包括两部分:
一是环境变量初始化,二是环境变量的设定、删除等操作。下面将分别进行讨论。
这里所使用的u-boot版本为2015.7,硬件为I.MX6 boundary nitrogen6q开发平台。
一 .环境变量初始化
1.读取环境变量
环境变量的初始化在board_init_f阶段完成,其由在common/barod_r.c中定义的静态函数initr_env来实现:

?

1

2

3

4

5

6

7

8

9

10

11

static int initr_env(void)

{

      /* initialize environment */

     if (should_load_env())

          env_relocate();

     else

          set_default_env(((void *)0));

      /* Initialize from environment */

     load_addr = getenv_ulong("loadaddr", 16, load_addr);

     return 0;

}

should_load_env是board_r.c中的静态函数,经过编译预处理,直接返回1。接着执行env_relocate(),该函数在common/env_common.c中实现,编译预处理后为:

?

1

2

3

4

5

6

7

8

9

void env_relocate(void)

{

    if (gd->env_valid == 0) {

          bootstage_error(BOOTSTAGE_ID_NET_CHECKSUM);

          set_default_env("!bad CRC");

     } else {

          env_relocate_spec();

     }

}

gd->env_valid在board_f阶段中的函数env_init调用中被赋值为1,这里将接着执行env_relocate_spec。env_relocate_spec针对不同的环境变量存储设备有多处实现,这里使用CONFIG_ENV_IS_IN_SPI_FLASH宏,所以使用common/Env_sf.c中的定义(参见common/Makefile),由于没有定义CONFIG_ENV_OFFSET_REDUND,则
env_relocate_spec实现为:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

void env_relocate_spec(void)

{

    int ret;

    char *buf = NULL;

    buf = (char *)malloc(CONFIG_ENV_SIZE);

    env_flash = spi_flash_probe(CONFIG_ENV_SPI_BUS, CONFIG_ENV_SPI_CS,

            CONFIG_ENV_SPI_MAX_HZ, CONFIG_ENV_SPI_MODE);

    if (!env_flash) {

        set_default_env("!spi_flash_probe() failed");

        if (buf)

            free(buf);

        return;

    }

    ret = spi_flash_read(env_flash,

        CONFIG_ENV_OFFSET, CONFIG_ENV_SIZE, buf);

    if (ret) {

        set_default_env("!spi_flash_read() failed");

        goto out;

    }

    ret = env_import(buf, 1);

    if (ret)

        gd->env_valid = 1;

out:

    spi_flash_free(env_flash);

    if (buf)

        free(buf);

    env_flash = NULL;

}

如果从spi flash中读取环境变量成功,则调用函数env_import,该函数将对读取到的环境变量进行CRC校验,如校验失败,会执行set_default_env。否则接着调用himport_r执行hash表的初始化。
set_default_env函数用来配置默认的的环境变量。它在common/env_common.c中实现:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

void set_default_env(constchar*s)

{

    int flags = 0;

    if (sizeof(default_environment) > ENV_SIZE) {

        puts("*** Error - default environment is too large\n\n");

        return;

    }

    if (s) {

        if (*s == '!') {

            printf("*** Warning - %s, "

                "using default environment\n\n",

                s + 1);

        } else {

            flags = H_INTERACTIVE;

            puts(s);

        }

    } else {

        puts("Using default environment\n\n");

    }

    if (himport_r(&env_htab, (char *)default_environment,

            sizeof(default_environment), '\0', flags, 0,

            0, NULL) == 0)

        error("Environment import failed: errno = %d\n", errno);

    gd->flags |= GD_FLG_ENV_READY;

}

default_environment为include/Env_default.h中定义的变量。它是一个常量字符串数组。在default_environment定义时赋值时,赋值的常量字符串又包含了nitrogen6x.h中的宏定义CONFIG_EXTRA_ENV_SETTINGS,该宏定义为代表附加环境变量的常量字符串。也即是default_environment值为Env_default.h加上nitrogen6x.h文件中定义的环境变量值。
另外注意,set_default_env函数中,gd->flags最后被赋值为gd->flags |= GD_FLG_ENV_READY,而从SPI Flash中读取的环境变量,在使用后续函数env_import时,该函数在返回前也会进行同样的赋值操作。该标志代表环境变量已准备好,可以对其进行打印、编辑操作。
环境变量读取的执行总流程图示如下:
\

无论是使用默认的环境变量,还是使用从spi flash读取到的有效环境变量配置,完成环境变量的读取后,都将调用himport_r将获取到的环境变量导入到hash表中。
2. 导入环境变量到hash表中
u-boot中使用三个结构体描述了hash表,它们在include/search.h文件中定义:
struct hsearch_data;
struct _ENTRY;
struct entry,即ENTRY;
下图描述了这些结构体的定义和它们之间的关系:

\

struct _ENTRY代表一个hash表项,其内部包含的struct entry(ENTRY)为hash表中具体的内容数据。struct hsearch_data用来管理整个hash表。struct _ENTRY结构体中的成员used也是作为hash表的管理之用。
读入的环境变量会导入到hash表中,以方便环境变量的查找,插入,编辑等操作。
在下面的描述中,使用了hash表键值和key两个名称,hash表键值代表上图中的hash表项索引index(整数),它还会存储在struct _ENTRY成员变量used中,当其为空时,表示该hash表中该索引表项未被占用;key代表是的是上图中ENTRY结构体中的成员变量字串指针key。hash表键值根据key通过hash算法来生成。key在u-boot中实际代表是环境变量名(name)。ENTRY结构体中的成员变量字符串指针data则代表相应环境变量的值(value)。

环境变量的hash表导入是通过函数himport_r来实现。该函数包含代码较多,为了便于分析,这里删除了略去了出错检查和繁杂的字符串操作代码,只保留其主要架构:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

int himport_r(struct hsearch_data *htab,

        const char *env, size_t size, const char sep, int flag,

        int crlf_is_lf, int nvars, char * const vars[])

{

    char *data, *sp, *dp, *name, *value;

    char *localvars[nvars];

    int i;

    if (htab == NULL)

    ...设定错误标志,返回0;    

    if ((data = malloc(size)) == NULL)

    ...设定错误标志,返回0;

    memcpy(data, env, size);

    dp = data;

    /* make a local copy of the list of variables */

    if (nvars)

        memcpy(localvars, vars, sizeof(vars[0]) * nvars);

    if ((flag & H_NOCLEAR) == 0) {

        /* Destroy old hash table if one exists */

        if (htab->table)

            hdestroy_r(htab);

    }

       

    /*如果还未创建,则下面创建它*/

    if (!htab->table) {

        int nent = CONFIG_ENV_MIN_ENTRIES + size / 8;

        if (nent > CONFIG_ENV_MAX_ENTRIES)

            nent = CONFIG_ENV_MAX_ENTRIES;

                /*创建hash表--分配存储空间*/

          if (hcreate_r(nent, htab) == 0) {

             ...释放data存储空间,然后返回0;

        }

    }

      ...

    if(crlf_is_lf){

        crlf_is_lf回车标志,这里传入的参数crlf_is_lf为0,不做处理

        如果有些环境变量过长,字符串中间包括'\r'分行,那么这里处理它,去掉回车,并将'\r'前后的字符串

        串联成一个字符串。

        如:"bootdelay=" "\r" "3" "\0"处理后为"bootdelay=" "3" "\0"

        在do_env_import(执行env import命令时带-r选项)中包含了crlf_is_lf的情况。

    }

    /* Parse environment; allow for '\0' and 'sep' as separators */

    do {

        ENTRY e, *rv;

        /*...解析环境变量,解析结构存入name和value中"*/ 

         /* enter into hash table */

        e.key = name;

        e.data = value;

        hsearch_r(e, ENTER, &rv, htab, flag);

     

    } while ((dp < data + size) && *dp);  /* size check needed for text */

                        /* without '\0' termination */

    free(data);

    /* process variables which were not considered */

    ...

    return 1;      /* everything OK */

}

该函数首先为环境变量分配内存空间。如果标志参数flag中包含H_NOCLEAR---该标志代表强制清除hash表,那么就调用hdestroy_r函数。如果hash表为空(!htab->table)那么将创建hash表。语句
CONFIG_ENV_MIN_ENTRIES + size / 8
用来根据环境变量包含的字节数,以及CONFIG_ENV_MIN_ENTRIES宏定义的最小表项数,来计算hash表包含的表项数。这里的8,使用的是估算法,假设每条环境变量占用8个字节,即key=value(不含等号)。源代码中对此有详细注解。然后调用hcreate_r创建hash表----初始化htab->size为上面的表项数,分配hash表的内存空间。
if(crlf_is_lf)代码段处理环境变量中包含的'\r'。上面程序中有详细注解。
do {
...
} while ((dp < data + size) && *dp);
do-while代码段解析环境变量,然后将解析后的所有环境变量,逐条填入hash表中。程序首先解析环境变量,每条环境变量的设定用"\0"分割,环境变量名和其设定值用"="号分隔,程序最后将解析后的环境变量名存入name,值存入value中,如"baudrate=" "115200" "\0",解析后name的值为"baudrate",value的内容为"115200"。
e.key = name;
e.data = value;
hsearch_r(e, ENTER, &rv, htab, flag);
上述代码将环境变量名字符串name赋值给hash表项中的key,环境变量值赋值给hash表项中的data,然后将e代表的hash表项插入到hash表中。下面将会分析具体的插入操作实现。
3.环境变量hash表的插入、编辑等操作
下面的讨论涉及到hash表键值生成算法,关于这方面的内容,可参见数据结构的相关书籍和网络上的相关文章。
u-boot中涉及到的hash表操作主要包括环境变量初始化和环境变量编辑。前者被上面的函数himport_r调用,后者则被u-boot一些环境变量编辑命令所调用。

这些操作主要通过函数hsearch_r来实现。删除操作则通过hdelete_r函数实现。

和通用的hash表操作不同,u-boot中hash表的操作中附加了权限检查和可执行的回调函数。前者是针对结构体ENTRY中的成员flags的检查,后者则是对ENTRY中的成员callback的调用。

下面主要分析hash表项初始化(插入表项,和编辑操作中的插入相同)以及编辑(插入、修改,删除等)操作的实现函数hsearch_r。我们将分段讨论该函数。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

int hsearch_r(ENTRY item, ACTION action, ENTRY ** retval,   struct hsearch_data *htab, int flag)

{

    unsigned int hval;

    unsigned int count;

    unsigned int len = strlen(item.key);

    unsigned int idx;

    unsigned int first_deleted = 0;

    int ret;

    /* Compute an value for the given string. Perhaps use a better method. */

    hval = len;

    count = len;

    while (count-- > 0) {

        hval <<= 4;

        hval += item.key[count];

    }

    /*

     * First hash function:

     * simply take the modul but prevent zero.

     */

    hval %= htab->size;

    if (hval == 0)

        ++hval;

    

/*--------------------------以上为代码段1--------------------------------------------*/

    /* The first index tried. */

    idx = hval;

    if (htab->table[idx].used) {

        /*

         * Further action might be required according to the

         * action value.

         */

        unsigned hval2;

        if (htab->table[idx].used == -1  && !first_deleted) /*下面的函数_hdelete中会把used填充为-1*/

            first_deleted = idx;

         /*_compare_and_overwrite_entry中执行了change_ok权限检查,  以及执行回调call_back函数*/

        ret = _compare_and_overwrite_entry(item, action, retval, htab, flag, hval, idx);

        if (ret != -1

            return ret;

  

        /*

         * Second hash function:

         * as suggested in [Knuth]

         */

        hval2 = 1 + hval % (htab->size - 2);  /*如如hash表键值出现重复*/

        do {

            /*

             * Because SIZE is prime this guarantees to

             * step through all available indices.

             */

            if (idx <= hval2)

                idx = htab->size + idx - hval2;

            else

                idx -= hval2;

            /*

             * If we visited all entries leave the loop

             * unsuccessfully.

             */

            if (idx == hval)

                break;

            /* If entry is found use it. */

            ret = _compare_and_overwrite_entry(item, action, retval, htab, flag, hval, idx);

            if (ret != -1) /*没有错误,直接返回*/

                return ret;

        }

        while (htab->table[idx].used); /*直至生成或找到没有重复的键值*/

    }

/*--------------------------以上为代码段2--------------------------------------------*/

    /* An empty bucket has been found. */

    if (action == ENTER) { /*这里是填充操作,即初始化*/

        /*

         * If table is full and another entry should be

         * entered return with error.

         */

        if (htab->filled == htab->size) {

            ...设置错误标志,且返回0;

        }

        /*

         * Create new entry;

         * create copies of item.key and item.data

         */

        if (first_deleted)

            idx = first_deleted;

        htab->table[idx].used = hval;

        htab->table[idx].entry.key = strdup(item.key);

        htab->table[idx].entry.data = strdup(item.data);

        if (!htab->table[idx].entry.key ||  !htab->table[idx].entry.data) {

            ...设置错误标志 ENOMEM,且返回0;

        }

        ++htab->filled;

        /* This is a new entry, so look up a possible callback */

        env_callback_init(&htab->table[idx].entry); /*回调函数初始化*/

        /* Also look for flags */

        env_flags_init(&htab->table[idx].entry);    /*flag标志初始化*/

        /*hash表项初始化时,其中涉及的环境变量表现会执行其相关的命令,如stdout=serial,vga

         那么就会执行类似u-boot中的setenv stdout serial,vga命令,且进行权限检查。

         当然这些回调函数是用户定义的。如权限检查置位且回调函数不为空,权限检查和回调函数返回失败,

         则删除相应的hash表项,且返回标志*/

        /* check for permission */

        if (htab->change_ok != NULL && htab->change_ok(

            &htab->table[idx].entry, item.data, env_op_create, flag)) {

            _hdelete(item.key, htab, &htab->table[idx].entry, idx);

                                                                       

            ...设置错误标志EPERM ,且返回0;

          }

        /* If there is a callback, call it */

        if (htab->table[idx].entry.callback &&

            htab->table[idx].entry.callback(item.key, item.data,

            env_op_create, flag)) {

            _hdelete(item.key, htab, &htab->table[idx].entry, idx);

            ...设置错误标志 EINVAL,且返回0;

        }

        /* return new entry */

        *retval = &htab->table[idx].entry;

/*--------------------------以上为代码段3--------------------------------------------*/

        return 1;

    }

    ...设置错误标志 ESRCH,且返回0;

}

代码段1:hash表键值生成算法
其中的代码

?

1

2

3

4

while (count-- > 0) { /*这里使用的是估算法,假设每条环境变量占8个字节,hval为32位(32/4)*/

    hval <<= 4;

    hval += item.key[count];

}

是hash键值算法的核心。将item.key---环境变量name中的字符移位并累加,然后模hash表项数,即为该环境变量在hash表项中的数组索引---hash表键值。注意这里使用的还是估算法,即假设每条环境变量字符串占用8个字节,注意上述代码中hval为32位数,移位操作8次就会发生循环。

?

1

2

if (hval == 0)

        ++hval;

保留了表项数组索引0的空间,在上面himport_r调用的hcreate_r分配hash存储空间时,多分配了一个表项空间,即该处的保留空间。
代码段2:hash表键值重复冲突处理和编辑操作
上述代码中

?

1

2

if (htab->table[idx].used == -1  && !first_deleted) /*下面的函数_hdelete中会把used填充为-1*/

     first_deleted = idx;

别处使用的函数_hdelete中会把used填充为-1,变量first_deleted记录第一次被删除的键值。下面的代码段3将会使用此处被赋值的first_deleted。
函数_compare_and_overwrite_entry主要执行三项操作:
c.1)检查以idx为索引的hash表项中的key(环境变量名字符串)是否和输入参数item.key一致,不一致返回-1
c.2)调用change_ok执行权限检查,不通过返回0。

c.3)如果相应表项的成员callback 不为空,则执行该回调函数。函数执行失败返回0。

回调函数在hash表项初始化时或被填充(见下面的代码段3)。

c.4)执行hash表项修改操作,即修改hash表项中的data为新值。
htab->table[idx].used的包含的值及其含义说明如下:
0 -- 未使用;
-1 -- 曾被删除
index -- 键值索引
当htab->table[idx].used为0时,则代表还未被使用和填充,将跳过代码段2,执行代码段3。
当htab->table[idx].used不为空,表示如下三种情况:
a)代码段生成的hash表键值出现重复
即以idx为索引的表项已被其他环境变量占用,由于环境变量名是唯一的,那么_compare_and_overwrite_entry执行的hash表项中key(代表被占用的环境变量名)和输入参数item.key一致性检查将出错返回-1。然后调整hash表的键值生成算法,执行while循环,直至找到一个未重复的hash表键值,注意while将循环里重复上述检查过程。

另外还有一种情况,即此时hash表项中key为空,即还未被初始化,那么_compare_and_overwrite_entry也将返回-1。

这里重点说明的是hash表项初始化中键值生成时重复问题,但键值重复的处理又不仅限于此。环境变量编辑时所涉及的hash表操作也会遇到该问题。但后者只不过重复还原前者的键值生成操作流程,以保证键值映射的一致性。

b)已被使用,这里是编辑操作
_compare_and_overwrite_entry也会检查根据代码段1键值算法映射的键值是否重复,如果不重复,那么执行上述的_compare_and_overwrite_entry中的c.2和c.3操作。c.2和c.3操作如出错均会返回0,接着执行c.4完成表项内容修改操作(修改环境变量的值value),出错也将返回0。所以上面的操作出错都会返回0,而非-1,则表示编辑操作完成,
hsearch_r函数直接返回。
如果_compare_and_overwrite_entry返回-1, 表示该hash表项初始化时,在hash表生成时的键值处理中,已有重复键值。所以,针对已被初始化的hash表,这里也要处理对这种重复键值进行重定位。即执行上述的a)。因为第一个被删除的hash表项即为有效的键值。
c.)相应的hash表项曾被删除,这里再次被使用
如果已被删除,那么只有针对hash表项的再次初始化才有意义,编辑操作执行_compare_and_overwrite_entry中的c.1返回-1,接着将执行a)。其实,此时代码再次执行a)要么无法找到有效的表项索引。即使能找到,下面的代码段3也只会使用第一次的曾被删除的表项索引值,该值记录在first_deleted中。
从上面的分析中可以看出,函数_compare_and_overwrite_entry的返回值为-1时,将hash表项初始化时和编辑操作时的键值重复检查混同处理,导致程序执行流程复杂化。所以该函数的首字符为下划线,表示可疑版本(可改进)。

代码段3:
这里主要执行的是hash表项的初始化,在u-boot中使用新增环境命令时也将执行该段代码。其他环境变量的编辑命令一般在代码段2都被正确执行后直接返回。而在使用u-boot命令setenv xxx新增一个环境变量时,代码段2也无法正确执行,不能返回到上层函数。
无论是整个hash表项的初始化,还是上述新增环境变量时的插入新的hash表项,它们执行的操作都是相同的,即都是插入新表项操作。程序执行到代码段3时,上面的程序已经生成了有效的hash表键值,并赋值给变量idx。
htab->filled代表已使用的hash表项数,如其值大于hash->size,即hash表项数,那么设置错误标志,并直接返回0。
如果first_deleted不为空,则其在代码2中曾被赋值,表示该表项曾经被删除过,其first_deleted代表第一个被删除的表项对应的键值。

?

1

2

3

4

5

6

7

htab->table[idx].used = hval;

htab->table[idx].entry.key = strdup(item.key);

htab->table[idx].entry.data = strdup(item.data);

if (!htab->table[idx].entry.key ||  !htab->table[idx].entry.data) {

     ...设置错误标志 ENOMEM,且返回0;

}

++htab->filled;

上面的代码完成hash表项内容的填充。strdup函数会分配内存空间,注意entry.key和entry.data都是指针变量。if语句执行键值一致性检查。填充无错误则变量htab->filled递增1,该变量上面使用过,代表已使用的hash表项数。接着执行函数env_callback_init和env_flags_init,前者是hash表项中回调函数的初始化,后者是hash表项中flags的初始化,flags用来标志访问权限。这两个函数的实现比较复杂,会另做一节对它们分析。接着的代码:

?

1

2

3

4

5

6

if (htab->change_ok != NULL && htab->change_ok(

     &htab->table[idx].entry, item.data, env_op_create, flag)) {

     _hdelete(item.key, htab, &htab->table[idx].entry, idx);

                                                                       

 ...设置错误标志EPERM ,且返回0;

}

htab->change_ok函数在其定义时赋值为env_flags_validate,这里不为空,则将调用该函数执行操作权限检查。
代码:

?

1

2

3

4

5

6

if (htab->table[idx].entry.callback &&

htab->table[idx].entry.callback(item.key, item.data,

env_op_create, flag)) {

     _hdelete(item.key, htab, &htab->table[idx].entry, idx);

     ...设置错误标志 EINVAL,且返回0;

}

执行在env_callback_init中初始化的回调函数。如权限检查和回调函数的执行出现错误,则直接删除该hash表项。
这里,可以看到,环境变量在其初始化的hash表填充时,就会调用表项中设置的回调函数。例如,有如下的存储在spi flash中的环境变量:

?

1

2

3

4

5

6

7

8

9

const unsigned char  default_environment[] = {

      "bootdelay=" "3" "\0"

      "baudrate=" "115200" "\0"

      "stdout=" "serial,vga" "\0"

      "ethprime=" "FEC" "\0"

      "preboot=" "" "\0"

      "loadaddr=" "0x12000000" "\0"

      "\0"

};

以第二行的环境变量"stdout=" "serial,vga"为例,当其从spi flash加载到内存空间中,并填充到hash表中时,如果该项环境变量的相关赋值(创建)操作被允许(权限检查通过),且有相应的回调函数,那么此处就会执行该函数。另外代码段2中的_compare_and_overwrite_entry中也将执行权限检查和回调函数。如,在u-boot中执行:
=>setenv stdout serial,hdmi
那么该命令会调用do_env_set函数,它又调用上述的hsearch_r并执行代码段2。所以,在"console_init_r分析"一节中,曾经提到过该命令是立即生效的。
3.env_flags_init和env_callback_init
env_flags_init初始化操作权限,env_callback_init初始化回调函数。
所谓的权限是针对每一个hash表项而言的,而每一个hash表项对应一条环境变量,权限即环境变量的创建,修改,删除等权限。回调函数是执行这些环境变量的操作后附加的一些操作。
如上面的例子中,执行setenv stdout serial,hdmi时,会首先检查环境变量stdout的修改操作是否被允许,如果允许将环境变量的值修改为serial,hdmi,然后调用回调函数,重设console。

下面来逐一分析这两个函数的具体实现。

3.1 env_flags_init

权限操作集中在文件env_flags.c中:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

void env_flags_init(ENTRY *var_entry)

{

    const char *var_name = var_entry->key;

    char flags[ENV_FLAGS_ATTR_MAX_LEN + 1] = "";

    int ret = 1;

    if (first_call) {

        flags_list = getenv(ENV_FLAGS_VAR);

        first_call = 0;

    }

    /* look in the ".flags" and static for a reference to this variable */

    ret = env_flags_lookup(flags_list, var_name, flags);

    /* if any flags were found, set the binary form to the entry */

    if (!ret && strlen(flags))

        var_entry->flags = env_parse_flags_to_bin(flags);

}

first_call为静态变量,其定义时初始化为1。上述代码首先从环境变量中获取flags的值(ENV_FLAGS_VAR),然后调用env_flags_lookup:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

static inline int env_flags_lookup(const char *flags_list, const char *name,

    char *flags)

{

    int ret = 1;

    if (!flags)

        /* bad parameter */

        return -1;

    /* try the env first */

    if (flags_list)

        ret = env_attr_lookup(flags_list, name, flags);

    if (ret != 0)

        /* if not found in the env, look in the static list */

        ret = env_attr_lookup(ENV_FLAGS_LIST_STATIC, name, flags);

    return ret;

}

一般地,环境变量中不含ENV_FLAGS_VAR,那么此处的flags_list值为空,则执行env_attr_lookup(ENV_FLAGS_LIST_STATIC, name, flags);
ENV_FLAGS_LIST_STATIC定义为:

?

1

2

3

4

5

6

#define ENV_FLAGS_LIST_STATIC \

  "ipaddr:i," \

  "gatewayip:i," \

  "netmask:i," \

  "serverip:i," \

  "serial#:so,"

每条环境变量的操作权限用逗号分隔,变量名和权限位使用冒号分隔。如上面ipaddr是环境变量名,i是权限描述。
权限描述又分为权限类型和权限值描述。

类型即是该环境变量对应值的类型,其字符的定义实现及其含义如下:
字符s代表string类型数据
字符d代表decimal类型数据
字符x代表hyexadecimal类型数据
字符b代表boolean 类型数据
字符i代表ip address 类型数据
权限值及其含义如下:
字符a表示any,可进行任何操作,它为权限的默认值
字符r表示read-only,只读
字符o表示write-once,可一次写
字符c表示change-default,可改变为默认值
上述权限值对应的u-boot操作包含在env_flags_varaccess_mask变量中:

?

1

2

3

4

5

6

7

8

9

10

static const int env_flags_varaccess_mask[] = {

      0,

      ENV_FLAGS_VARACCESS_PREVENT_DELETE |

            ENV_FLAGS_VARACCESS_PREVENT_CREATE |

            ENV_FLAGS_VARACCESS_PREVENT_OVERWR,

      ENV_FLAGS_VARACCESS_PREVENT_DELETE |

            ENV_FLAGS_VARACCESS_PREVENT_OVERWR,

      ENV_FLAGS_VARACCESS_PREVENT_DELETE |

            ENV_FLAGS_VARACCESS_PREVENT_NONDEF_OVERWR

};

env_flags_init函数会调用函数env_parse_flags_to_bin,将权限值字符,映射为上面env_flags_varaccess_mask变量中的二进制值,即

字符a最终映射为0
字符r映射为:ENV_FLAGS_VARACCESS_PREVENT_DELETE |
ENV_FLAGS_VARACCESS_PREVENT_CREATE |
ENV_FLAGS_VARACCESS_PREVENT_OVERWR,
字符o映射为:
ENV_FLAGS_VARACCESS_PREVENT_DELETE |
ENV_FLAGS_VARACCESS_PREVENT_OVERWR,
字符c映射为:
ENV_FLAGS_VARACCESS_PREVENT_DELETE |
ENV_FLAGS_VARACCESS_PREVENT_NONDEF_OVERWR
从上面的ENV_FLAGS_VARACCESS_XX宏定义名可以看出其代表的操作权限。

env_flags_init最后会将映射后的二进制权限位值赋值给hash表项的成员flags。struct hsearch_data的成员变量change_ok,它是一个函数指针,定义时被初始化为env_flags_validate。环境变量操作的权限检查是通过该函数来执行。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

int env_flags_validate(const ENTRY *item, const char *newval, enum env_op op, int flag)

{

    const char *name;

    const char *oldval = NULL;

    if (op != env_op_create)

        oldval = item->data;

    name = item->key;

    /* Default value for NULL to protect string-manipulating functions */

    newval = newval ? : "";

    /* validate the value to match the variable type */

    if (op != env_op_delete) {

        enum env_flags_vartype type = (enum env_flags_vartype)

            (ENV_FLAGS_VARTYPE_BIN_MASK & item->flags);

        if (_env_flags_validate_type(newval, type) < 0) {

                ...

            return -1;

        }

    }

    /* check for access permission */

#ifndef CONFIG_ENV_ACCESS_IGNORE_FORCE

    if (flag & H_FORCE)

        return 0;

#endif

    switch (op) {

    case env_op_delete:

        if (item->flags & ENV_FLAGS_VARACCESS_PREVENT_DELETE) {

          return 1;

        }

        break;

    case env_op_overwrite:

        if (item->flags & ENV_FLAGS_VARACCESS_PREVENT_OVERWR) {

             return 1;

        } else if (item->flags &

            ENV_FLAGS_VARACCESS_PREVENT_NONDEF_OVERWR) {

            const char *defval = getenv_default(name);

            if (defval == NULL)

                defval = "";

            if (strcmp(oldval, defval) != 0) {

                  return 1;

            }

        }

        break;

    case env_op_create:

        if (item->flags & ENV_FLAGS_VARACCESS_PREVENT_CREATE) {

             return 1;

        }

        break;

    }

    return 0;

}

如果定义了CONFIG_ENV_ACCESS_IGNORE_FORCE,则所有的访问权限将被忽略。
针对不同的操作,传给env_flags_validate的参数op值就不同。
在上面的函数hsearch_r中,代码段2中调用_compare_and_overwrite_entry,然后调用change_ok,传入的op值是env_op_overwrite,而代码段3中,调用change_ok ,传入的op值为env_op_create。这样env_flags_validate函数将根据不同的op操作,来执行相应的权限检查。如当操作是创建hash表项时,那么,会检查ENV_FLAGS_VARACCESS_PREVENT_CREATE标志。
另外,如果相应的环境变量hash表项没有定义flags,则其值为0,允许所有的权限。即针对环境变量的操作,默认的权限为全部使能。
3.2 env_callback_init

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

void env_callback_init(ENTRY *var_entry)

{

    const char *var_name = var_entry->key;

    char callback_name[256] = "";

    struct env_clbk_tbl *clbkp;

    int ret = 1;

    if (first_call) {

        callback_list = getenv(ENV_CALLBACK_VAR);

        first_call = 0;

    }

    /* look in the ".callbacks" var for a reference to this variable */

    if (callback_list != NULL)

        ret = env_attr_lookup(callback_list, var_name, callback_name);

    /* only if not found there, look in the static list */

    if (ret)

        ret = env_attr_lookup(ENV_CALLBACK_LIST_STATIC, var_name,

            callback_name);

    /* if an association was found, set the callback pointer */

    if (!ret && strlen(callback_name)) {

        clbkp = find_env_callback(callback_name);

        if (clbkp != NULL)

            var_entry->callback = clbkp->callback;

    }

}

代码首先从环境变量中获取回调函数的值(ENV_CALLBACK_VAR),然后调用env_attr_lookup,这里使用参数ENV_CALLBACK_LIST_STATIC,该宏定义如下:

?

1

2

3

4

5

6

7

8

9

#define ENV_CALLBACK_LIST_STATIC ENV_DOT_ESCAPE ENV_CALLBACK_VAR ":callbacks," \

      ENV_DOT_ESCAPE ENV_FLAGS_VAR ":flags," \

      "baudrate:baudrate," \

      NET_CALLBACKS \

      "loadaddr:loadaddr," \

      SILENT_CALLBACK \

      SPLASHIMAGE_CALLBACK \

      "stdin:console,stdout:console,stderr:console," \

      CONFIG_ENV_CALLBACK_LIST_STATIC

每条环境变量操作的回调函数用逗号分隔,变量名和回调函数索引字符串使用冒号分隔。如上面stdin是环境变量名,冒号后面的console是回调函数索引字符串。之所以称为"回调函数索引字符串",因为这里只是字符串,必须使用映射机制,将回调函数索引字符串和所对应的回调函数关联起来。然后使用回调函数索引字符串,查找到相应的回调函数。

结构体struct env_clbk_tbl维持着这样一个回调函数索引字符串和回调函数的映射表:

?

1

2

3

4

5

struct env_clbk_tbl {

    const char *name;       /* Callback name */

    int (*callback)(const char *name, const char *value, enum env_op op,

        int flags);

};

利用宏U_BOOT_ENV_CALLBACK填充该结构体,将上述的回调函数索引字符串和回调函数关联起来,如:
U_BOOT_ENV_CALLBACK(console, on_console);
将回调函数索引字符串"console"和on_console回调函数相关联,那么上述环境变量stdin操作最后的回调函数就是on_console。

在u-boot中,使用U_BOOT_ENV_CALLBACK定义并初始化的struct env_clbk_tbl变量都存放在名为_u_boot_list_2_env_clbk_2_xx的符号段中。

U_BOOT_ENV_CALLBACK(console, on_console)预编译后为:

?

1

struct env_clbk_tbl _u_boot_list_2_env_clbk_2_console __attribute__((aligned(4))) __attribute__((unused, section(".u_boot_list_2_""env_clbk""_2_""console"))) = {"console", on_console};

注意上述section中的符号段名。所有利用U_BOOT_ENV_CALLBACK 定义的的环境变量回调函数都将放在这样一个以_u_boot_list_2_env_clbk_2开头的符号段中。
在u-boot生成的符号表文件system.map中可查看到这些段:

?

1

2

3

4

5

6

7

8

9

17868084 D _u_boot_list_2_env_clbk_2_bootfile

1786808c D _u_boot_list_2_env_clbk_2_callbacks

17868094 D _u_boot_list_2_env_clbk_2_console

1786809c D _u_boot_list_2_env_clbk_2_ethaddr

178680a4 D _u_boot_list_2_env_clbk_2_flags

178680ac D _u_boot_list_2_env_clbk_2_gatewayip

178680b4 D _u_boot_list_2_env_clbk_2_ipaddr

178680bc D _u_boot_list_2_env_clbk_2_loadaddr

178680c4 D _u_boot_list_2_env_clbk_2_netmask

env_callback_init函数中接着执行的find_env_callback就是在_u_boot_list_2_env_clbk_2_xx段中查找环境变量相应的符号段,该符号也即上述struct env_clbk_tbl变量的地址。find_env_callback函数对其中name进行核对后,返回有效的回调函数指针然后函数env_callback_init将其赋值给hash表项的成员callback。这样就完成了环境变量hash表项的回调函数初始化。

猜你喜欢

转载自blog.csdn.net/kunkliu/article/details/82860805