fastboot及lk解析

FastBoot協議版本0.4

fastboot協議是一種通過USB連接與bootloaders通訊的機制。它被設計的非常容易實現,能夠用於多種設備和運行Linux、Windows或者OSX的主機。
這裏寫圖片描述
基本需求(Basic Requirements)

  • 兩個端點,一個輸入端,一個輸出端。
  • 對於全速(full-speed)USB,最大包尺寸必須是64個字節;
    對於高速(hign-speed)USB,最大包尺寸必須是512個字節。
  • 協議完全是主機驅動(SSW注:相對於設備客戶端而言),並且同步的。
    這與多通道、雙向、異步的ADB協議不同。

傳輸和組幀(Transport and Framing)
步驟1、主機發送命令(Command)。
一個命令是一個ASCII字符串,並且只能包含在不大於64個字節的單個包內。

步驟2、客戶端(SSW注:設備)用一個單個的不大於64個字節的包響應。
響應包開頭四個字節是“OKAY”、“FAIL”、“DATA”或者“INFO”。
響應包剩餘的字節可以包含ASCII格式的說明性信息。

      a、INFO -> 剩餘的60個字節包含說明信息(提供進度或者診斷信息)。
                 這些說明信息應該被顯示,然後重複步驟2。
      b、FAIL -> 指示請求的命令失敗。
                 剩餘的60個字節可以提供一個文本形式的失敗原因呈現給用戶。交互停止。
      c、OKAY -> 指示請求的命令成功完成。跳轉到步驟5。
      d、DATA -> 請求的命令已經爲數據階段做好準備。
                 一個數據響應包是12個字節長,組織形式爲DATA00000000,
                 其中8位十六進制的數字表示所傳輸數據的總大小。

步驟3、數據階段。
根據命令的不同,主機或者客戶端將發送指定大小的數據。
比指定長度短的包總是可接受的,零長度的包將被忽略。
這個階段會一直持續,直到客戶端已經發送或接收了上面數據響應包中指定大小的字節數爲止。

步驟4、客戶端用一個單個的不大於64個字節的包響應。

      a、INFO -> 顯示剩餘的60個字節,然後返回到步驟4。
      b、FAIL -> 顯示剩餘的60個字節(如果有的話)作爲失敗原因,命令失敗,停止交互。
      c、OKAY -> 成功。跳轉到步驟5。 

步驟5、命令執行成功。
結束交互。

示例會話(Example Session)
Host:主機 Client:客戶端(設備)

Host: “getvar:version” 請求版本號
Client: “OKAY0.4” 返回版本爲”0.4”

Host: “getvar:nonexistant” 請求未定義的變量
Client: “OKAY” 返回值爲”“

Host: “download:00001234” 請求發送0x1234大小的字節數據
Client: “DATA00001234” 準備好接收數據

Host: < 0x1234 bytes > 發送數據
Client: “OKAY” 數據接收成功完成

Host: “flash:bootloader” 請求刷新數據到bootloader
Client: “INFOerasing flash” 指示狀態/進度爲“擦除flash”
“INFOwriting flash” 指示狀態/進度爲“寫入flash”
“OKAY” 刷新成功完成

Host: “powerdown” 發送“關機”命令
Client: “FAILunknown command” 命令執行失敗

命令參考(Command Reference)
- 命令參數以printf風格的轉義序列表示。
- 命令是ASCII字符串,發送時不用引號(下面命令外使用引號僅僅爲了在此文檔中清楚的表達命令),
發送時也不以0字節結尾。
- 以小寫字母開頭的命令是爲本規範保留的,OEM特定的命令不應該以小寫字母開頭,以防和規範的未來版本不兼容。

   "getvar:%s"         從bootloader讀取配置或版本變量。
                       變量的值在OKAY響應的後面返回。
   "download:x"        寫入數據到內存,供下面闡述的”boot“、”randisk“、”flash“等命令使用。
                       如果RAM有足夠的空間,客戶端將用”DATAx“迴應;否則,將回應”FAIL“。
                       下載數據的大小會被記下來。
   "verify:x"          發送一個數字簽名去驗證下載的數據。
                       如果bootloader是”secure(安全的)“,那麼簽名驗證是必須的;
                       如果bootloader不是”secure“,”flash“和”boot“命令會忽略簽名驗證。
   "flash:%s"          將之前下載的影像寫入到指定的分區(如果可能的話)。
   "erase:%s"          擦除指定的分區(將分區全部寫成0xFFs)。
   "boot"              之前下載的數據一個boot.img,應該按照boot.img的正常步驟被啓動。
   "continue"          繼續正常啓動工作(如果可能的話)。
   "reboot"            重新啓動設備。
   "reboot-bootloader" 重新啓動進入bootloader。
                       對於升級bootloader之後,用新的bootloader去升級其他分區的升級過程,
                       這個命令是很有用的。
   "powerdown"         設備關機。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

客戶端變量(Client Variables)
命令”getvar:%s”用來讀取客戶端變量,客戶端變量代表關於設備和運行於設備之上軟件的各種信息。
當前已經定義的變量名稱如下:

   version             FastBoot協議所支持的版本。
   version-bootloader  Bootloader的版本字符串。
   version-baseband    基帶(Baseband)軟件的版本字符串。
   product             產品名稱。
   serialno            產品序列號。
   secure              如果值是”yes“,說明這是一個安全的bootloader,在它安裝或啓動映像之前,需要一個簽名。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

以小寫字母開頭的變量名被本規範保留,OEM特定的變量名不應該以小寫字母開頭。

ADB (Android Debug Bridge)

ADB是Android系統的調試協議,所有android系統都支持它。
這裏寫圖片描述

解析源碼:

lk-refs-heads-master\app\aboot下的rules.mk可知

OBJS += \
$(LOCAL_DIR)/aboot.o \
$(LOCAL_DIR)/fastboot.o
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

主要包含aboot.c 和 fastboot.c

在aboot.c 中

APP_START(aboot)
.init = aboot_init,
APP_END
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

aboot_init的實現如下:
可知只要用戶按下BACK按鍵就跳過boot_linux_from_flash,進入到fastboot.

void aboot_init(const struct app_descriptor *app)
{
if (keys_get_state(KEY_BACK) != 0)
goto fastboot;


boot_linux_from_flash();
dprintf(CRITICAL, "ERROR: Could not do normal boot. Reverting "
"to fastboot mode.\n");


fastboot:
udc_init(&surf_udc_device);


fastboot_register("boot", cmd_boot);
fastboot_register("erase:", cmd_erase);
fastboot_register("flash:", cmd_flash);
fastboot_register("continue", cmd_continue);
fastboot_publish("product", "swordfish");
fastboot_publish("kernel", "lk");


fastboot_init((void*) SCRATCH_ADDR, 100 * 1024 * 1024);
udc_start();
}
  • 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
  • 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

調用usb初始化,並註冊fastboot的boot/erase/flash/continue/product/kernel等命令,並fastboot_init,新建thread來接受pc發過來的命令

int fastboot_init(void *base, unsigned size)
{
thread_t *thr;
dprintf(INFO, "fastboot_init()\n");


download_base = base;
download_max = size;


event_init(&usb_online, 0, EVENT_FLAG_AUTOUNSIGNAL);
event_init(&txn_done, 0, EVENT_FLAG_AUTOUNSIGNAL);


in = udc_endpoint_alloc(UDC_TYPE_BULK_IN, 512);
if (!in)
goto fail_alloc_in;
out = udc_endpoint_alloc(UDC_TYPE_BULK_OUT, 512);
if (!out)
goto fail_alloc_out;


fastboot_endpoints[0] = in;
fastboot_endpoints[1] = out;


req = udc_request_alloc();
if (!req)
goto fail_alloc_req;


if (udc_register_gadget(&fastboot_gadget))
goto fail_udc_register;


fastboot_register("getvar:", cmd_getvar);
fastboot_register("download:", cmd_download);
fastboot_publish("version", "0.5");


thr = thread_create("fastboot", fastboot_handler, 0, DEFAULT_PRIORITY, 4096);
thread_resume(thr);
return 0;


fail_udc_register:
udc_request_free(req);
fail_alloc_req:
udc_endpoint_free(out);

fail_alloc_out:
udc_endpoint_free(in);
fail_alloc_in:
return -1;
}
  • 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
  • 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

這個函數又註冊getvar/download命令,並建立thread接受pc發過來的執行,thread的callback函數是fastboot_handler :

static int fastboot_handler(void *arg)
{
for (;;) {
event_wait(&usb_online);
fastboot_command_loop();
}
return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

這個函數是循環,如果usb接收到命令,就調用fastboot_command_loop來解析命令並調用命令的執行函數

static void fastboot_command_loop(void)
{
struct fastboot_cmd *cmd;
int r;
dprintf(INFO,"fastboot: processing commands\n");


again:
while (fastboot_state != STATE_ERROR) {
r = usb_read(buffer, 64);
if (r < 0) break;
buffer[r] = 0;
dprintf(INFO,"fastboot: %s\n", buffer);


for (cmd = cmdlist; cmd; cmd = cmd->next) {
if (memcmp(buffer, cmd->prefix, cmd->prefix_len))
continue;
fastboot_state = STATE_COMMAND;
cmd->handle((const char*) buffer + cmd->prefix_len,
   (void*) download_base, download_size);
if (fastboot_state == STATE_COMMAND)
fastboot_fail("unknown reason");
goto again;
}


fastboot_fail("unknown command");

}
fastboot_state = STATE_OFFLINE;
dprintf(INFO,"fastboot: oops!\n");
}
  • 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
  • 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

所有的命令都在cmdlist 這個列表中,調用memcmp來比較pc發的命令和fastboot的命令是否相等,如果相等就調用handle處理,
也就是我們fastboot_register時候的第二個參數.

在fastboot中我們一般通過fastboot_register來註冊命令

fastboot_register("erase:", cmd_erase);

我們來看看fastboot_register的實現

static struct fastboot_cmd *cmdlist;


void fastboot_register(const char *prefix,
      void (*handle)(const char *arg, void *data, unsigned sz))
{
struct fastboot_cmd *cmd;
cmd = malloc(sizeof(*cmd));
if (cmd) {
cmd->prefix = prefix;
cmd->prefix_len = strlen(prefix);
cmd->handle = handle;
cmd->next = cmdlist;
cmdlist = cmd;
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

在fastboot.c 中定義一個fastboot_cmd類型的靜態變量cmdlist,在調用fastboot_register 註冊時會先通過
malloc 申請一個fastboot_cmd,然後分貝給prefix賦值命令的名稱,如本例中的erase:,handle是收到命令後
要處理的函數,本例中賦值爲cmd_erase。然後將這個新建的cmd加到cmdlist中
這樣在fastboot 處理函數中fastboot_command_loop,會比較buffer中接收到的命令和cmdlist->prefix 相比較是否相等(if (memcmp(buffer, cmd->prefix, cmd->prefix_len))),
如果相等就調用handle函數。

cmd->handle((const char*) buffer + cmd->prefix_len,
   (void*) download_base, download_size);
  • 1
  • 2
  • 1
  • 2

我們會調用fastboot_publish 來註冊常量,例如下例中定義version=0.5

fastboot_publish("version", "0.5");

我們看看fastboot_publish的實現,和上面講的fastboot_register 類似,也是有一個常量的varlist,通過fastboot_publish
註冊的常量都在這個varlist上.

static struct fastboot_var *varlist;


void fastboot_publish(const char *name, const char *value)
{
struct fastboot_var *var;
var = malloc(sizeof(*var));
if (var) {
var->name = name;
var->value = value;
var->next = varlist;
varlist = var;
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

而我們又是通過
fastboot_register("getvar:", cmd_getvar);
來註冊如果獲取常量的
所以如果發過來的命令是getvar,就調用cmd_getva
cmd_getvar 就是將varlist中的所有產量通過fastboot_okay發送給pc,顯示出來.

static void cmd_getvar(const char *arg, void *data, unsigned sz)
{
struct fastboot_var *var;


for (var = varlist; var; var = var->next) {
if (!strcmp(var->name, arg)) {
fastboot_okay(var->value);
return;
}
}
fastboot_okay("");
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

fastboot init

\lk-refs-heads-master\arch\arm\crt0.s

_start:
    b reset
    b arm_undefined
    b arm_syscall
    b arm_prefetch_abort
    b arm_data_abort
    b arm_reserved
    b arm_irq
    b arm_fiq
    cmp r0, r1
    strlt r2, [r0], #4
    blt .L__bss_loop

    ......

    bl kmain 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

lk的第一行語句是從crt0.s 中的_start開始,在這個函數的最後會調用kmain 進入到c code的執行

void kmain(void)
{
   ....

// create a thread to complete system initialization
dprintf(SPEW, "creating bootstrap completion thread\n");
thread_resume(thread_create("bootstrap2", &bootstrap2, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));


// enable interrupts
exit_critical_section();


// become the idle thread
thread_become_idle();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

在這個函數的最後會new 一個thread,回調函數是bootstrap2

static int bootstrap2(void *arg)
{
dprintf(SPEW, "calling apps_init()\n");
apps_init();

....

return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

這個函數最後會調用apps_init。在lk中所有的應該都是以app的形式出現的,其中每個app對應一個thread,就像前面講過的fastboot,我們看看在apps_init中怎麼給每個app一個thread來運行的

void apps_init(void)
{
const struct app_descriptor *app;

/* call all the init routines */
for (app = &__apps_start; app != &__apps_end; app++) {
    if (app->init)
    app->init(app);
}
/* start any that want to start on boot */
for (app = &__apps_start; app != &__apps_end; app++) {
    if (app->entry && (app->flags & APP_FLAG_DONT_START_ON_BOOT) == 0) {
start_app(app);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

這個函數會對放在__apps_start__apps_end的apps如果有init函數,就全部調用init:

APP_START(aboot)
.init = aboot_init,
APP_END
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

以fastboot 爲例,apps_init中的app->init=aboot_init。我們在aboot_init會爲fastboot new一個thread來運行.

如果在__apps_start__apps_end的apps 有定義entry 且flags沒有APP_FLAG_DONT_START_ON_BOOT
例如shell.c中

APP_START(shell)
.init = shell_init,
.entry = shell_entry,
APP_END
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

的話,就在apps_init中就調用start_app

static void start_app(const struct app_descriptor *app)
{
printf("starting app %s\n", app->name);


thread_resume(thread_create(app->name, &app_thread_entry, (void *)app, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

就會爲這個apps new 一個thread,其回調函數是app_thread_entry

static int app_thread_entry(void *arg)
{
const struct app_descriptor *app = (const struct app_descriptor *)arg;


app->entry(app, NULL);


return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

app_thread_entry 中直接調用app->entry會運行在一個新的thread中,在shell.c中就是shell_entry會在一個new thread中運行.

lk thread

在lk中我們一般通過thread_create 來新建一個thread,但這個thread 是THREAD_SUSPENDED,必須要調用thread_resume才能開始運行

enum thread_state {
    THREAD_SUSPENDED = 0,
    THREAD_READY,
    THREAD_RUNNING,
    THREAD_BLOCKED,
    THREAD_SLEEPING,
    THREAD_DEATH,
};
thread_resume(thread_create(app->name, &app_thread_entry, (void *)app, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

我們來看看thread_create的實現

thread_t *thread_create(const char *name, thread_start_routine entry, void *arg, int priority, size_t stack_size)
{
thread_t *t;


t = malloc(sizeof(thread_t));
if (!t)
return NULL;


init_thread_struct(t, name);


t->entry = entry;
t->arg = arg;
t->priority = priority;
t->saved_critical_section_count = 1; /* we always start inside a critical section */
t->state = THREAD_SUSPENDED;
t->blocking_wait_queue = NULL;
t->wait_queue_block_ret = NO_ERROR;


/* create the stack */
t->stack = malloc(stack_size);
if (!t->stack) {
free(t);
return NULL;
}


t->stack_size = stack_size;


/* inheirit thread local storage from the parent */
int i;
for (i=0; i < MAX_TLS_ENTRY; i++)
t->tls[i] = current_thread->tls[i];


/* set up the initial stack frame */
arch_thread_initialize(t);


/* add it to the global thread list */
enter_critical_section();
list_add_head(&thread_list, &t->thread_list_node);
exit_critical_section();


return t;
}
  • 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
  • 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

這個函數但大部分在做新thread_t結構體的初始化。在最後調用list_add_head將這個新建的thread加入到thread_list 中.

/* global thread list */
static struct list_node thread_list;

所有的thread 都是放在這個全局的thread_list
現在爲止我們的thread 已經建好了,但是其狀態是t->state = THREAD_SUSPENDED;我們需要調用thread_resume來讓其運行

status_t thread_resume(thread_t *t)
{
#if THREAD_CHECKS
ASSERT(t->magic == THREAD_MAGIC);
ASSERT(t->state != THREAD_DEATH);
#endif


if (t->state == THREAD_READY || t->state == THREAD_RUNNING)
return ERR_NOT_SUSPENDED;


enter_critical_section();
t->state = THREAD_READY;
insert_in_run_queue_head(t);
thread_yield();
exit_critical_section();


return NO_ERROR;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

這個函數會先判斷t->state 是不是THREAD_READY或者 THREAD_RUNNING,如果是的話就退出,說明出錯了,由於我們是
新建一個thread,其狀態是THREAD_SUSPENDED,所以繼續往下走
將thread的狀態設成THREAD_READY
然後調用insert_in_run_queue_head插入到將要運行thread的list中

/* run queue manipulation */
static void insert_in_run_queue_head(thread_t *t)
{
#if THREAD_CHECKS
ASSERT(t->magic == THREAD_MAGIC);
ASSERT(t->state == THREAD_READY);
ASSERT(!list_in_list(&t->queue_node));
ASSERT(in_critical_section());
#endif


list_add_head(&run_queue[t->priority], &t->queue_node);
run_queue_bitmap |= (1<<t->priority);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

其中

static struct list_node run_queue[NUM_PRIORITIES]; 
 #define NUM_PRIORITIES 32

可以run_queue是一個數組,每個優先級對應數組中的一樣。其中每一項又是一個list.
thread_resume在把這個thread 放到run_queue後,繼續調用thread_yield 來運行

void thread_yield(void)
{
#if THREAD_CHECKS
ASSERT(current_thread->magic == THREAD_MAGIC);
ASSERT(current_thread->state == THREAD_RUNNING);
#endif


enter_critical_section();


#if THREAD_STATS
thread_stats.yields++;
#endif


/* we are yielding the cpu, so stick ourselves into the tail of the run queue and reschedule */
current_thread->state = THREAD_READY;
current_thread->remaining_quantum = 0;
insert_in_run_queue_tail(current_thread);
thread_resched();


exit_critical_section();
}
  • 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
  • 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

這個函數將當前正在運行的thread的狀態設成THREAD_READY,然後調用insert_in_run_queue_tail將當前thread 插入到run_queue 的最後面然後調用thread_resched來進程thread 切換

void thread_resched(void)
{
    thread_t *oldthread;
    thread_t *newthread;


    oldthread = current_thread;


    int next_queue = HIGHEST_PRIORITY - __builtin_clz(run_queue_bitmap) - (32 - NUM_PRIORITIES);
    //dprintf(SPEW, "bitmap 0x%x, next %d\n", run_queue_bitmap, next_queue);


    newthread = list_remove_head_type(&run_queue[next_queue], thread_t, queue_node);


    if (list_is_empty(&run_queue[next_queue]))
        run_queue_bitmap &= ~(1<<next_queue);


    newthread->state = THREAD_RUNNING;


    if (newthread == oldthread)
return;


/* set up quantum for the new thread if it was consumed */
if (newthread->remaining_quantum <= 0) {
    newthread->remaining_quantum = 5; // XXX make this smarter
}




/* do the switch */
    oldthread->saved_critical_section_count = critical_section_count;
    current_thread = newthread;
    critical_section_count = newthread->saved_critical_section_count;
    arch_context_switch(oldthread, newthread);
}
  • 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
  • 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

通過list_remove_head_type異常並返回在run_queu 頭部的thread,由於這次切換的時候是找優先級高的thread的運行,因此如果當前還有比我們優先級高的thread,可能這次運行的不是我們前面
新建的thread

int next_queue = HIGHEST_PRIORITY - __builtin_clz(run_queue_bitmap) - (32 - NUM_PRIORITIES);

將這個thread的狀態切換成newthread->state = THREAD_RUNNING
最後調用arch_context_switch來進行thread 切換

void arch_context_switch(thread_t *oldthread, thread_t *newthread)
{
// dprintf("arch_context_switch: old %p (%s), new %p (%s)\n", oldthread, oldthread->name, newthread, newthread->name);
    arm_context_switch(&oldthread->arch.sp, newthread->arch.sp);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

由於當前cpu是arm,因此調用arm_context_switch來切換

/* context switch frame is as follows:
* ulr
* usp
* lr
* r11
* r10
* r9
* r8
* r7
* r6
* r5
* r4
*/
/* arm_context_switch(addr_t *old_sp, addr_t new_sp) */
FUNCTION(arm_context_switch)
/* save all the usual registers + user regs */
/* the spsr is saved and restored in the iframe by exceptions.S */
sub r3, sp, #(11*4)
/* can't use sp in user mode stm */
mov r12, lr
stmia r3, { r4-r11, r12, r13, r14 }^

/* save old sp */
str r3, [r0] 


/* clear any exlusive locks that the old thread holds */
#if ARM_ISA_ARMV7
/* can clear it directly */
.word 0xf57ff01f // clrex
#elif ARM_ISA_ARMV6
/* have to do a fake strex to clear it */
ldr r0, =strex_spot
strex r3, r2, [r0]
#endif


/* load new regs */
ldmia r1, { r4-r11, r12, r13, r14 }^
mov lr, r12
/* restore lr */
add sp, r1, #(11*4)     /* restore sp */
bx lr
  • 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
  • 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

其實現比較簡單就是保存當前thread的寄存器,然後將新thread的寄存器從stack中load到寄存器中,並執行bx lr 跳到新thread來運行

lk的thread切換

lk是按照時間片來運行thread,每個thread 默認運行5個時鐘中斷,如果時間到了就在timer的中斷中做thread 切換。下來我們看看具體的代碼。
當我們通過thread_resume來讓thread 運行時.會調用thread_yiled

void thread_yield(void)
{
    enter_critical_section();


/* we are yielding the cpu, so stick ourselves into the tail of the run queue and reschedule */
    current_thread->state = THREAD_READY;
    current_thread->remaining_quantum = 0;
    insert_in_run_queue_tail(current_thread);
    thread_resched();


    exit_critical_section();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

這個函數會將current_thread->remaining_quantum = 0;remaining_quantum就是表示thread 可以運行的timer 中斷的個數
繼續看thread_resched

void thread_resched(void)
{
    thread_t *oldthread;
    thread_t *newthread;


/* set up quantum for the new thread if it was consumed */
if (newthread->remaining_quantum <= 0) {
    newthread->remaining_quantum = 5; // XXX make this smarter
}


}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

這個函數會將新thread的remaining_quantum 設定爲5,也就是運行5個time 中斷,如果一個timer中斷是10ms的話,每個thread 就默認運行50ms
明白這點後我們看看怎麼在時鐘中斷中切換thread

arm_irq中斷中會根據中斷的返回值來決定是否進行thread切換

FUNCTION(arm_irq)

/* XXX only deals with interrupting supervisor mode */


/* call into higher level code */
mov r0, sp /* iframe */
bl platform_irq


/* reschedule if the handler returns nonzero */
cmp     r0, #0
blne    thread_preempt
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

先看看platform_irq的實現

enum handler_return platform_irq(struct arm_iframe *frame)
{
    ret = handler[num].func(handler[num].arg);
    return ret;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

會根據中斷號,調用對應的函數而handler中的func是通過register_int_handler來註冊的.

void register_int_handler(unsigned int vector, int_handler func, void *arg)
{
    if (vector >= NR_IRQS)
    return;


    enter_critical_section();
    handler[vector].func = func;
    handler[vector].arg = arg;
    exit_critical_section();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

看下面的函數handler是platform_tick,對應的timer中斷號是INT_PIT

void platform_init_timer(void)
{
    register_int_handler(INT_PIT, &platform_tick, NULL);
}
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

繼續看platform_tick的實現

static enum handler_return platform_tick(void *arg)
{
    *REG(PIT_CLEAR_INT) = 1;
    if (t_callback) {
    return t_callback(arg, current_time());
    } else {
    return INT_NO_RESCHEDULE;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

調用t_callback,通過下面的函數設定t_callback

status_t platform_set_periodic_timer(platform_timer_callback callback, void *arg, time_t interval)
{
enter_critical_section();

t_callback = callback;

return NO_ERROR;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

所以t_callback == timer_tick

void timer_init(void)
{
list_initialize(&timer_queue);


/* register for a periodic timer tick */
platform_set_periodic_timer(timer_tick, NULL, 10); /* 10ms */
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

timer_tick中繼續call thread_timer_tick來決定是否要進行thread 切換

static enum handler_return timer_tick(void *arg, time_t now)
{
    timer_t *timer;
    enum handler_return ret = INT_NO_RESCHEDULE;


#if THREAD_STATS
    thread_stats.timer_ints++;
#endif


    for (;;) {
/* let the scheduler have a shot to do quantum expiration, etc */
    if (thread_timer_tick() == INT_RESCHEDULE)
    ret = INT_RESCHEDULE;

    return INT_RESCHEDULE;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

而在thread_timer_tick就是簡單的判定當前thread的時間片是否用完,如果用完就切換

enum handler_return thread_timer_tick(void)
{
    if (current_thread == idle_thread)
    return INT_NO_RESCHEDULE;

    current_thread->remaining_quantum--;
    if (current_thread->remaining_quantum <= 0)
    return INT_RESCHEDULE;
    else
    return INT_NO_RESCHEDULE;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

如果返回INT_RESCHEDULE也就是不等於0 ,也就是要切換thread,則在arm_irq中繼續調用thread_preempt進行thread 切換。

可以看到當前remaining_quantum 不等於0,就說明當前有更高優先級的thread搶佔了當前的thread, 所以講當前thread 插在run queue的前面等下一次運行。否則的話,就是當前thread時間片運行完,加到run queue的後面

void thread_preempt(void)
{
#if THREAD_CHECKS
    ASSERT(current_thread->magic == THREAD_MAGIC);
    ASSERT(current_thread->state == THREAD_RUNNING);
#endif

    enter_critical_section();

#if THREAD_STATS
    if (current_thread != idle_thread)
    thread_stats.preempts++; /* only track when a meaningful preempt happens */
#endif


/* we are being preempted, so we get to go back into the front of the run queue if we have quantum left */
    current_thread->state = THREAD_READY;
    if (current_thread->remaining_quantum > 0)
        insert_in_run_queue_head(current_thread);
    else
        insert_in_run_queue_tail(current_thread); /* if we're out of quantum, go to the tail of the queue */
    thread_resched();
    exit_critical_section();
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

kl中的memory

在lk中通過我們一般通過malloc來申請內存

void *malloc(size_t size)
{
    return heap_alloc(size, 0);
}
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

malloc 直接調用heap_alloc來申請。
lk_main函數中通過調用heap_init來初始由於malloc申請的heap

void heap_init(void)
{
    LTRACE_ENTRY;

// set the heap range
    theheap.base = (void *)HEAP_START;
    theheap.len = HEAP_LEN;

// initialize the free list
    list_initialize(&theheap.free_list);

// create an initial free chunk
                `heap_insert_free_chunk(heap_create_free_chunk(theheap.base, theheap.len));
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

可以看到這個函數一開始就要設置heap 需要管理的memory的start 和 size
這兩個宏定義如下:

#define HEAP_START ((unsigned long)&_end)
#define HEAP_LEN ((size_t)&_end_of_ram - (size_t)&_end)

_end_end_of_ram 又是在下面的文件中定義的


system-onesegment.ld
__data_end = .;


/* unintialized data (in same segment as writable data) */
. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss .bss.*) }

. = ALIGN(4);
_end = .;

. = %MEMBASE% + %MEMSIZE%;
_end_of_ram = .;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

可以看到需要用戶自己定義MEMBASEMEMSIZE變量
我們繼續看heap_init會調用下面的函數初始free list

// initialize the free list
list_initialize(&theheap.free_list);

可見這個時候free list是空的

static inline void list_initialize(struct list_node *list)
{
    list->prev = list->next = list;
}

繼續看是如何插入chunk的

// create an initial free chunk
    heap_insert_free_chunk(heap_create_free_chunk(theheap.base, theheap.len));

其中heap_create_free_chunk 的實現如下,

struct free_heap_chunk *heap_create_free_chunk(void *ptr, size_t len)
{
    DEBUG_ASSERT((len % sizeof(void *)) == 0); /*size must be aligned on pointer boundary*/

    struct free_heap_chunk *chunk = (struct free_heap_chunk *)ptr;
chunk->len = len;

    return chunk;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

可知lk中是用chunk 來管理memory的,剛開始的時候只有一個很大的chunk
heap_insert_free_chunk的實現如下:

static struct free_heap_chunk *heap_insert_free_chunk(struct free_heap_chunk *chunk)
{
#if DEBUGLEVEL > INFO
    vaddr_t chunk_end = (vaddr_t)chunk + chunk->len;
#endif

// dprintf("%s: chunk ptr %p, size 0x%lx, chunk_end 0x%x\n", __FUNCTION__, chunk, chunk->len, chunk_end);

    struct free_heap_chunk *next_chunk;
    struct free_heap_chunk *last_chunk;

// walk through the list, finding the node to insert before
    list_for_every_entry(&theheap.free_list, next_chunk, struct free_heap_chunk, node) {
    if (chunk < next_chunk) {
        DEBUG_ASSERT(chunk_end <= (vaddr_t)next_chunk);

        list_add_before(&next_chunk->node, &chunk->node);
        goto try_merge;
    }
}


// walked off the end of the list, add it at the tail
    list_add_tail(&theheap.free_list, &chunk->node);


// try to merge with the previous chunk
try_merge:
    last_chunk = list_prev_type(&theheap.free_list, &chunk->node, struct free_heap_chunk, node);
    if (last_chunk) {
        if ((vaddr_t)last_chunk + last_chunk->len == (vaddr_t)chunk) {
// easy, just extend the previous chunk
        last_chunk->len += chunk->len;

// remove ourself from the list
    list_delete(&chunk->node);

// set the chunk pointer to the newly extended chunk, in case 
// it needs to merge with the next chunk below
    chunk = last_chunk;
}
}

// try to merge with the next chunk
    if (next_chunk) {
        if ((vaddr_t)chunk + chunk->len == (vaddr_t)next_chunk) {
// extend our chunk
        chunk->len += next_chunk->len;


// remove them from the list
        list_delete(&next_chunk->node);
    }
}

    return chunk;
}
  • 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
  • 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

這個函數首先會根據chunk的地址,來將chunk插入到free list的位置中,這裏也可以知道free list 中的chunk 是按地址排序的這個函數後面的部分會試圖對已存在的chunk進行合併,類似kernel中的buddy system前面講過malloc 直接調用heap_alloc來申請內存

void *heap_alloc(size_t size, unsigned int alignment)
{
    void *ptr;
    LTRACEF("size %zd, align %d\n", size, alignment);

// alignment must be power of 2
    if (alignment & (alignment - 1))
        return NULL;


/*we always put a size field + base pointer + magic in front of the allocation*/
    size += sizeof(struct alloc_struct_begin);

/*make sure we allocate at least the size of a struct       free_heap_chunk so that when we free it, we can create a struct free_heap_chunk struct and stick it*/
// in the spot
    if (size < sizeof(struct free_heap_chunk))
        size = sizeof(struct free_heap_chunk);

// round up size to a multiple of native pointer size
    size = ROUNDUP(size, sizeof(void *));

// deal with nonzero alignments
    if (alignment > 0) {
        if (alignment < 16)
        alignment = 16;

// add alignment for worst case fit
        size += alignment;
}

// critical section
    enter_critical_section();

// walk through the list
    ptr = NULL;
    struct free_heap_chunk *chunk;
    list_for_every_entry(&theheap.free_list, chunk, struct free_heap_chunk, node) {
        DEBUG_ASSERT((chunk->len % sizeof(void *)) == 0); // len should always be a multiple of pointer size

// is it big enough to service our allocation?
    if (chunk->len >= size) {
    ptr = chunk;
// remove it from the list
    struct list_node *next_node = list_next(&theheap.free_list, &chunk->node);
    list_delete(&chunk->node);

    if (chunk->len > size + sizeof(struct free_heap_chunk)) {
/*here's enough space in this chunk to create a new one after the allocation*/
        struct free_heap_chunk *newchunk = heap_create_free_chunk((uint8_t *)ptr + size, chunk->len - size);


// truncate this chunk
    chunk->len -= chunk->len - size;


// add the new one where chunk used to be
    if (next_node)
        list_add_before(next_node, &newchunk->node);
    else
    list_add_tail(&theheap.free_list, &newchunk->node);
}

/*he allocated size is actually the length of this chunk, not   the size requested*/
    DEBUG_ASSERT(chunk->len >= size);
    size = chunk->len;

    ptr = (void *)((addr_t)ptr + sizeof(struct alloc_struct_begin));

// align the output if requested
    if (alignment > 0) {
        ptr = (void *)ROUNDUP((addr_t)ptr, alignment);
}

    struct alloc_struct_begin *as = (struct alloc_struct_begin *)ptr;
    as--;
    as->magic = HEAP_MAGIC;
    as->ptr = (void *)chunk;
    as->size = size;

    break;
    }
}
LTRACEF("returning ptr %p\n", ptr);

// heap_dump();
    exit_critical_section();
    return ptr;
}
  • 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
  • 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

這個函數前一部分size,例如需要在size基礎上加上alloc_struct_begin的size,用於管理chunk size 要至少4byte對其等後辦法通過list_for_every_entry遍歷free list 找到size滿足用戶申請的size後,將chunk的起始地址返回用戶。

LK是什麼

LK 是 Little Kernel 它是 appsbl (Applications ARM Boot Loader)流程代碼 ,little kernel 是小內核小操作系統
LK 代碼 在 bootable/bootloadler/lk 目錄下

LK目錄 代碼結構
+app // 應用相關
+arch // arm 體系
+dev // 設備相關
+include // 頭文件
+kernel // lk系統相關
+platform // 相關驅動
+projiect // makefile文件
+scripts // Jtag 腳本
+target // 具體板子相關

LK 流程分析
在 bootable/bootloadler/lk/arch/arm/ssystem-onesegment.ld 連接文件中 ENTRY(_start)指定 LK 從_start函數開始,_start在 lk/arch/crt0.S中 。crt0.S 主要做一些基本的 CPU 的初始化再通過bl kmain;跳轉到 C 代碼中。

kmain 在 lk/kernel/main.c 中

kmain()
kmain 主要做兩件事:
1、本身 lk 這個系統模塊的初始化;
2、boot 的啓動初始化動作。

kmain 源碼分析:

         void kmain()
      {
       1.初始化進程(lk 中的簡單進程)相關結構體。
         thread_init_early();
       2.做一些如 關閉 cache,使能 mmu 的 arm 相關工作。
        arch_early_init();
       3.相關平臺的早期初始化
        platform_early_init();
       4.現在就一個函數跳轉,初始化UART(板子相關)
        target_early_init();
       5.構造函數相關初始化
        call_constructors();
       6.lk系統相關的堆棧初始化
        heap_init();
       7.簡短的初始化定時器對象
        thread_init();
       8.lk系統控制器初始化(相關事件初始化)
        dpc_init();
       9.初始化lk中的定時器
        timer_init();
       10.新建線程入口函數 bootstrap2 用於boot 工作(重點)
       thread_resume(thread_create("bootstrap2", &bootstrap2, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
     }

以上與 boot 啓動初始化相關函數是arch_early_initplatform_early_initbootstrap2,這些是啓動的重點,我們下面慢慢來看。

arch_early_init()
     體系架構相關的初始化我們一般用的 ARM 體系
     1.關閉cache
     arch_disable_cache(UCACHE);
     2.設置向量基地址(中斷相關)
     set_vector_base(MEMBASE);
     3.初始化MMU
     arm_mmu_init();
     4.初始化MMU映射__平臺相關
     platform_init_mmu_mappings();
     5.開啓cache         
     arch_enable_cache(UCACHE)
     6.使能 cp10 和 cp11
     __asm__ volatile("mrc    p15, 0, %0, c1, c0, 2" : "=r" (val));
     val |= (3<<22)|(3<<20);
     __asm__ volatile("mcr    p15, 0, %0, c1, c0, 2" :: "r" (val));

    7.設置使能 fpexc 位 (中斷相關)
    __asm__ volatile("mrc  p10, 7, %0, c8, c0, 0" : "=r" (val));
    val |= (1<<30);
    __asm__ volatile("mcr  p10, 7, %0, c8, c0, 0" :: "r" (val));
    8.使能循環計數寄存器
    __asm__ volatile("mrc    p15, 0, %0, c9, c12, 0" : "=r" (en));
    en &= ~(1<<3); /*循環計算每個週期*/
    en |= 1; 
    __asm__ volatile("mcr    p15, 0, %0, c9, c12, 0" :: "r" (en));
   9.使能循環計數器
   en = (1<<31);
   __asm__ volatile("mcr    p15, 0, %0, c9, c12, 1" :: "r" (en));

platform_early_init()
平臺相關初始化不同平臺不同的初始化下面是msm7x30

    1.初始化中斷
   platform_init_interrupts();
    2.初始化定時器
    platform_init_timer();

bootstrap2
bootstrap2kmain的末尾以線程方式開啓。主要分三步:platform_inittarget_initapps_init

    1.platform_init
           platform_init 中主要是函數 acpu_clock_init。
           在 acpu_clock_init 對 arm11 進行系統時鐘設置,超頻 
    2.target_init
          針對硬件平臺進行設置。主要對 arm9 和 arm11 的分區表進行整合,初始化flash和讀取FLASH信息
    3.apps_init  
         apps_init 是關鍵,對 LK 中所謂 app 初始化並運行起來,而 aboot_init 就將在這裏開始被運行,Android Linux 內核的加載工作就在 aboot_init 中完成的 。

aboot_init

    1.設置NAND/ EMMC讀取信息頁面大小
    if (target_is_emmc_boot())
    {
              page_size = 2048;
              page_mask = page_size - 1;
    }
   else
   {
             page_size = flash_page_size();
             page_mask = page_size - 1;
    }
  2.讀取按鍵信息,判斷是正常開機,還是進入 fastboot ,還是進入recovery 模式
   。。。。。。。。。
  通過一系列的 if (keys_get_state() == XXX) 判斷
   。。。。。。。。。
  3.從 nand 中加載 內核
  boot_linux_from_flash();

  partition_dump();
  sz = target_get_max_flash_size();
  fastboot_init(target_get_scratch_address(), sz);
  udc_start(); // 開始 USB 協議

boot_linux_from_flash

主要是內核的加載過程,我們的 boot.img 包含:kernel 頭、kernel、ramdisk、second stage(可以沒有)。

       1.讀取boot 頭部
       flash_read(p, offset, raw_header, 2048) 
       offset += 2048;
       2.讀取 內核    
       memcmp(hdr->magic, BOOT_MAGIC, BOOT_MAGIC_SIZE)
       n = (hdr->kernel_size + (FLASH_PAGE_SIZE - 1)) & (~(FLASH_PAGE_SIZE - 1));
       flash_read(p, offset, (void*) hdr->kernel_addr, n)
       offset += n;
       3.讀取 ramdisk
       n = (hdr->ramdisk_size + (FLASH_PAGE_SIZE - 1)) & (~(FLASH_PAGE_SIZE - 1));
       flash_read(p, offset, (void*) hdr->ramdisk_addr, n)
       offset += n;
        4.啓動內核,
            boot_linux();//在boot_linux 中entry(0,machtype,tags);從kernel加載在內核中的地址開始運行了。

到這裏LK的啓動過程就結束了。


http://blog.csdn.net/viewsky11/article/details/53334219



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章