Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> u-boot中環境變量操作和hash表

u-boot中環境變量操作和hash表

編輯:關於Android編程

u-boot對環境變量的處理主要包括兩部分:
一是環境變量初始化,二是環境變量的設定、刪除等操作。下面將分別進行討論。
這裡所使用的u-boot版本為2015.7,硬件為I.MX6 boundary nitrogen6q開發平台。
一 .環境變量初始化
1.讀取環境變量
環境變量的初始化在board_init_f階段完成,其由在common/barod_r.c中定義的靜態函數initr_env來實現:
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中實現,編譯預處理後為:

 

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實現為:
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中實現:
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來實現。該函數包含代碼較多,為了便於分析,這裡刪除了略去了出錯檢查和繁雜的字符串操作代碼,只保留其主要架構:

 

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。我們將分段討論該函數。

 

 

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表鍵值生成算法
其中的代碼
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次就會發生循環。
if (hval == 0)
        ++hval;
保留了表項數組索引0的空間,在上面himport_r調用的hcreate_r分配hash存儲空間時,多分配了一個表項空間,即該處的保留空間。
代碼段2:hash表鍵值重復沖突處理和編輯操作
上述代碼中
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代表第一個被刪除的表項對應的鍵值。

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用來標志訪問權限。這兩個函數的實現比較復雜,會另做一節對它們分析。接著的代碼:
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,這裡不為空,則將調用該函數執行操作權限檢查。
代碼:
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中的環境變量:
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中:

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:
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定義為:
#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變量中:

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。環境變量操作的權限檢查是通過該函數來執行。

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
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,該宏定義如下:
#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維持著這樣一個回調函數索引字符串和回調函數的映射表:

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)預編譯後為:

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中可查看到這些段:
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表項的回調函數初始化。
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved