fastboot及lk解析

本文深入介绍了FastBoot协议的版本0.4,详细解释了该协议的基本需求、传输和组帧步骤,以及命令参考等内容。此外,还展示了如何在LittleKernel (LK) 中实现FastBoot协议,包括初始化过程和内存管理等关键技术。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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.youkuaiyun.com/viewsky11/article/details/53334219



ADB,即 Android Debug Bridge,它是 Android 开发/测试人员不可替代的强大工具,也是 Android 设备玩家的好玩具。 注:有部分命令的支持情况可能与 Android 系统版本及定制 ROM 的实现有关。 基本用法 命令语法 adb 命令的基本语法如下: adb [-d|-e|-s ] 如果只有一个设备/模拟器连接时,可以省略掉 [-d|-e|-s ] 这一部分,直接使用 adb 。 为命令指定目标设备 如果有多个设备/模拟器连接,则需要为命令指定目标设备。 参数 含义 -d 指定当前唯一通过 USB 连接的 Android 设备为命令目标 -e 指定当前唯一运行的模拟器为命令目标 -s 指定相应 serialNumber 号的设备/模拟器为命令目标 在多个设备/模拟器连接的情况下较常用的是 -s 参数,serialNumber 可以通过 adb devices 命令获取。如: $ adb devices List of devices attached cf264b8f device emulator-5554 device 10.129.164.6:5555 device 输出里的 cf264b8f、emulator-5554 和 10.129.164.6:5555 即为 serialNumber。 比如这时想指定 cf264b8f 这个设备来运行 adb 命令获取屏幕分辨率: adb -s cf264b8f shell wm size 又如想给 10.129.164.6:5555 这个设备安装应用(这种形式的 serialNumber 格式为 :,一般为无线连接的设备或 Genymotion 等第三方 Android 模拟器): adb -s 10.129.164.6:5555 install test.apk 遇到多设备/模拟器的情况均使用这几个参数为命令指定目标设备,下文中为简化描述,不再重复。 启动/停止 启动 adb server 命令: adb start-server (一般无需手动执行此命令,在运行 adb 命令时若发现 adb server 没有启动会自动调起。) 停止 adb server 命令: adb kill-server 查看 adb 版本 命令: adb version 示例输出: Android Debug Bridge version 1.0.36 Revision 8f855a3d9b35-android 以 root 权限运行 adbd adb 的运行原理是 PC 端的 adb server 与手机端的守护进程 adbd 建立连接,然后 PC 端的 adb client 通过 adb server 转发命令,adbd 接收命令后解析运行。 所以如果 adbd 以普通权限执行,有些需要 root 权限才能执行的命令无法直接用 adb xxx 执行。这时可以 adb shell 然后 su 后执行命令,也可以让 adbd 以 root 权限执行,这个就能随意执行高权限命令了。 命令: adb root 正常输出: restarting adbd as root 现在再运行 adb shell,看看命令行提示符是不是变成 # 了? 有些手机 root 后也无法通过 adb root 命令让 adbd 以 root 权限执行,比如三星的部分机型,会提示 adbd cannot run as root in production builds,此时可以先安装 adbd Insecure,然后 adb root 试试。 相应地,如果要恢复 adbd 为非 root 权限的话,可以使用 adb unroot 命令。 指定 adb server 的网络端口 命令: adb -P start-server 默认端口为 5037。 设备连接管理 查询已连接设备/模拟器 命令: adb devices 输出示例: List of devices attached cf264b8f device emulator-5554 device 10.129.164.6:5555 device 输出格式为 [serialNumber] [state],serialNumber 即我们常说的 SN,state 有如下几种: offline —— 表示设备未连接成功或无响应。 device —— 设备已连接。注意这个状态并不能标识 Android 系统已经完全启动和可操作,在设备启动过程中设备实例就可连接到 adb,但启动完毕后系统才处于可操作状态。 no device —— 没有设备/模拟器连接。 以上输出显示当前已经连接了三台设备/模拟器,cf264b8f、emulator-5554 和 10.129.164.6:5555 分别是它们的 SN。从 emulator-5554 这个名字可以看出它是一个 Android 模拟器,而 10.129.164.6:5555 这种形为 : 的 serialNumber 一般是无线连接的设备或 Genymotion 等第三方 Android 模拟器。 常见异常输出: 没有设备/模拟器连接成功。 List of devices attached 设备/模拟器未连接到 adb 或无响应。 List of devices attached cf264b8f offline USB 连接 通过 USB 连接来正常使用 adb 需要保证几点: 硬件状态正常。 包括 Android 设备处于正常开机状态,USB 连接线和各种接口完好。 Android 设备的开发者选项和 USB 调试模式已开启。 可以到「设置」-「开发者选项」-「Android 调试」查看。 如果在设置里找不到开发者选项,那需要通过一个彩蛋来让它显示出来:在「设置」-「关于手机」连续点击「版本号」7 次。 设备驱动状态正常。 这一点貌似在 Linux 和 Mac OS X 下不用操心,在 Windows 下有可能遇到需要安装驱动的情况,确认这一点可以右键「计算机」-「属性」,到「设备管理器」里查看相关设备上是否有黄色感叹号或问号,如果没有就说明驱动状态已经好了。否则可以下载一个手机助手类程序来安装驱动先。 通过 USB 线连接好电脑和设备后确认状态。 adb devices 如果能看到 xxxxxx device 说明连接成功。 无线连接(需要借助 USB 线) 除了可以通过 USB 连接设备与电脑来使用 adb,也可以通过无线连接——虽然连接过程中也有需要使用 USB 的步骤,但是连接成功之后你的设备就可以在一定范围内摆脱 USB 连接线的限制啦! 操作步骤: 将 Android 设备与要运行 adb 的电脑连接到同一个局域网,比如连到同一个 WiFi。 将设备与电脑通过 USB 线连接。 应确保连接成功(可运行 adb devices 看是否能列出该设备)。 让设备在 5555 端口监听 TCP/IP 连接: adb tcpip 5555 断开 USB 连接。 找到设备的 IP 地址。 一般能在「设置」-「关于手机」-「状态信息」-「IP地址」找到,也可以使用下文里 查看设备信息 - IP 地址 一节里的方法用 adb 命令来查看。 通过 IP 地址连接设备。 adb connect 这里的 就是上一步中找到的设备 IP 地址。 确认连接状态。 adb devices 如果能看到 :5555 device 说明连接成功。 如果连接不了,请确认 Android 设备与电脑是连接到了同一个 WiFi,然后再次执行 adb connect 那一步; 如果还是不行的话,通过 adb kill-server 重新启动 adb 然后从头再来一次试试。 断开无线连接 命令: adb disconnect 无线连接(无需借助 USB 线) 注:需要 root 权限。 上一节「无线连接(需要借助 USB 线)」是官方文档里介绍的方法,需要借助于 USB 数据线来实现无线连接。 既然我们想要实现无线连接,那能不能所有步骤下来都是无线的呢?答案是能的。 在 Android 设备上安装一个终端模拟器。 已经安装过的设备可以跳过此步。我使用的终端模拟器下载地址是:Terminal Emulator for Android Downloads 将 Android 设备与要运行 adb 的电脑连接到同一个局域网,比如连到同一个 WiFi。 打开 Android 设备上的终端模拟器,在里面依次运行命令: su setprop service.adb.tcp.port 5555 找到 Android 设备的 IP 地址。 一般能在「设置」-「关于手机」-「状态信息」-「IP地址」找到,也可以使用下文里 查看设备信息 - IP 地址 一节里的方法用 adb 命令来查看。 在电脑上通过 adb 和 IP 地址连接 Android 设备。 adb connect 这里的 就是上一步中找到的设备 IP 地址。 如果能看到 connected to :5555 这样的输出则表示连接成功。 节注一: 有的设备,比如小米 5S + MIUI 8.0 + Android 6.0.1 MXB48T,可能在第 5 步之前需要重启 adbd 服务,在设备的终端模拟器上运行: restart adbd 如果 restart 无效,尝试以下命令: start adbd stop adbd 应用管理 查看应用列表 查看应用列表的基本命令格式是 adb shell pm list packages [-f] [-d] [-e] [-s] [-3] [-i] [-u] [--user USER_ID] [FILTER] 即在 adb shell pm list packages 的基础上可以加一些参数进行过滤查看不同的列表,支持的过滤参数如下: 参数 显示列表 无 所有应用 -f 显示应用关联的 apk 文件 -d 只显示 disabled 的应用 -e 只显示 enabled 的应用 -s 只显示系统应用 -3 只显示第三方应用 -i 显示应用的 installer -u 包含已卸载应用 包名包含 字符串 所有应用 命令: adb shell pm list packages 输出示例: package:com.android.smoketest package:com.example.android.livecubes package:com.android.providers.telephony package:com.google.android.googlequicksearchbox package:com.android.providers.calendar package:com.android.providers.media package:com.android.protips package:com.android.documentsui package:com.android.gallery package:com.android.externalstorage ... // other packages here ... 系统应用 命令: adb shell pm list packages -s 第三方应用 命令: adb shell pm list packages -3 包名包含某字符串的应用 比如要查看包名包含字符串 mazhuang 的应用列表,命令: adb shell pm list packages mazhuang 当然也可以使用 grep 来过滤: adb shell pm list packages | grep mazhuang 安装 APK 命令格式: adb install [-lrtsdg] 参数: adb install 后面可以跟一些可选参数来控制安装 APK 的行为,可用参数及含义如下: 参数 含义 -l 将应用安装到保护目录 /mnt/asec -r 允许覆盖安装 -t 允许安装 AndroidManifest.xml 里 application 指定 android:testOnly="true" 的应用 -s 将应用安装到 sdcard -d 允许降级覆盖安装 -g 授予所有运行时权限 运行命令后如果见到类似如下输出(状态为 Success)代表安装成功: [100%] /data/local/tmp/1.apk pkg: /data/local/tmp/1.apk Success 上面是当前最新版 v1.0.36 的 adb 的输出,会显示 push apk 文件到手机的进度百分比。 使用旧版本 adb 的输出则是这样的: 12040 KB/s (22205609 bytes in 1.801s) pkg: /data/local/tmp/SogouInput_android_v8.3_sweb.apk Success 而如果状态为 Failure 则表示安装失败,比如: [100%] /data/local/tmp/map-20160831.apk pkg: /data/local/tmp/map-20160831.apk Failure [INSTALL_FAILED_ALREADY_EXISTS] 常见安装失败输出代码、含义及可能的解决办法如下: 输出 含义 解决办法 INSTALL_FAILED_ALREADY_EXISTS 应用已经存在,或卸载了但没卸载干净 adb install 时使用 -r 参数,或者先 adb uninstall 再安装 INSTALL_FAILED_INVALID_APK 无效的 APK 文件 INSTALL_FAILED_INVALID_URI 无效的 APK 文件名 确保 APK 文件名里无中文 INSTALL_FAILED_INSUFFICIENT_STORAGE 间不足 清理间 INSTALL_FAILED_DUPLICATE_PACKAGE 已经存在同名程序 INSTALL_FAILED_NO_SHARED_USER 请求的共享用户不存在 INSTALL_FAILED_UPDATE_INCOMPATIBLE 以前安装过同名应用,但卸载时数据没有移除 先 adb uninstall 再安装 INSTALL_FAILED_SHARED_USER_INCOMPATIBLE 请求的共享用户存在但签名不一致 INSTALL_FAILED_MISSING_SHARED_LIBRARY 安装包使用了设备上不可用的共享库 INSTALL_FAILED_REPLACE_COULDNT_DELETE 替换时无法删除 INSTALL_FAILED_DEXOPT dex 优化验证失败或间不足 INSTALL_FAILED_OLDER_SDK 设备系统版本低于应用要求 INSTALL_FAILED_CONFLICTING_PROVIDER 设备里已经存在与应用里同名的 content provider INSTALL_FAILED_NEWER_SDK 设备系统版本高于应用要求 INSTALL_FAILED_TEST_ONLY 应用是 test-only 的,但安装时没有指定 -t 参数 INSTALL_FAILED_CPU_ABI_INCOMPATIBLE 包含不兼容设备 CPU 应用程序二进制接口的 native code INSTALL_FAILED_MISSING_FEATURE 应用使用了设备不可用的功能 INSTALL_FAILED_CONTAINER_ERROR 1. sdcard 访问失败; 2. 应用签名与 ROM 签名一致,被当作内置应用 1. 确认 sdcard 可用,或者安装到内置存储; 2. 打包时不与 ROM 使用相同签名 INSTALL_FAILED_INVALID_INSTALL_LOCATION 1. 不能安装到指定位置; 2. 应用签名与 ROM 签名一致,被当作内置应用 1. 切换安装位置,添加或删除 -s 参数; 2. 打包时不与 ROM 使用相同签名 INSTALL_FAILED_MEDIA_UNAVAILABLE 安装位置不可用 一般为 sdcard,确认 sdcard 可用或安装到内置存储 INSTALL_FAILED_VERIFICATION_TIMEOUT 验证安装包超时 INSTALL_FAILED_VERIFICATION_FAILURE 验证安装包失败 INSTALL_FAILED_PACKAGE_CHANGED 应用与调用程序期望的不一致 INSTALL_FAILED_UID_CHANGED 以前安装过该应用,与本次分配的 UID 不一致 清除以前安装过的残留文件 INSTALL_FAILED_VERSION_DOWNGRADE 已经安装了该应用更高版本 使用 -d 参数 INSTALL_FAILED_PERMISSION_MODEL_DOWNGRADE 已安装 target SDK 支持运行时权限的同名应用,要安装的版本不支持运行时权限 INSTALL_PARSE_FAILED_NOT_APK 指定路径不是文件,或不是以 .apk 结尾 INSTALL_PARSE_FAILED_BAD_MANIFEST 无法解析的 AndroidManifest.xml 文件 INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION 解析器遇到异常 INSTALL_PARSE_FAILED_NO_CERTIFICATES 安装包没有签名 INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES 已安装该应用,且签名与 APK 文件不一致 先卸载设备上的该应用,再安装 INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING 解析 APK 文件时遇到 CertificateEncodingException INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME manifest 文件里没有或者使用了无效的包名 INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID manifest 文件里指定了无效的共享用户 ID INSTALL_PARSE_FAILED_MANIFEST_MALFORMED 解析 manifest 文件时遇到结构性错误 INSTALL_PARSE_FAILED_MANIFEST_EMPTY 在 manifest 文件里找不到找可操作标签(instrumentation 或 application) INSTALL_FAILED_INTERNAL_ERROR 因系统问题安装失败 INSTALL_FAILED_USER_RESTRICTED 用户被限制安装应用 INSTALL_FAILED_DUPLICATE_PERMISSION 应用尝试定义一个已经存在的权限名称 INSTALL_FAILED_NO_MATCHING_ABIS 应用包含设备的应用程序二进制接口不支持的 native code INSTALL_CANCELED_BY_USER 应用安装需要在设备上确认,但未操作设备或点了取消 在设备上同意安装 INSTALL_FAILED_ACWF_INCOMPATIBLE 应用程序与设备不兼容 does not contain AndroidManifest.xml 无效的 APK 文件 is not a valid zip file 无效的 APK 文件 Offline 设备未连接成功 先将设备与 adb 连接成功 unauthorized 设备未授权允许调试 error: device not found 没有连接成功的设备 先将设备与 adb 连接成功 protocol failure 设备已断开连接 先将设备与 adb 连接成功 Unknown option: -s Android 2.2 以下不支持安装到 sdcard 不使用 -s 参数 No space left on devicerm 间不足 清理间 Permission denied … sdcard … sdcard 不可用 参考:PackageManager.java adb install 内部原理简介 adb install 实际是分三步完成: push apk 文件到 /data/local/tmp。 调用 pm install 安装。 删除 /data/local/tmp 下的对应 apk 文件。 所以,必要的时候也可以根据这个步骤,手动分步执行安装过程。 卸载应用 命令: adb uninstall [-k] 表示应用的包名,-k 参数可选,表示卸载应用但保留数据和缓存目录。 命令示例: adb uninstall com.qihoo360.mobilesafe 表示卸载 360 手机卫士。 清除应用数据与缓存 命令: adb shell pm clear 表示应用名包,这条命令的效果相当于在设置里的应用信息界面点击了「清除缓存」和「清除数据」。 命令示例: adb shell pm clear com.qihoo360.mobilesafe 表示清除 360 手机卫士的数据和缓存。 查看前台 Activity 命令: adb shell dumpsys activity activities | grep mFocusedActivity 输出示例: mFocusedActivity: ActivityRecord{8079d7e u0 com.cyanogenmod.trebuchet/com.android.launcher3.Launcher t42} 其中的 com.cyanogenmod.trebuchet/com.android.launcher3.Launcher 就是当前处于前台的 Activity。 查看正在运行的 Services 命令: adb shell dumpsys activity services [] 参数不是必须的,指定 表示查看与某个包名相关的 Services,不指定表示查看所有 Services。 不一定要给出完整的包名,比如运行 adb shell dumpsys activity services org.mazhuang,那么包名 org.mazhuang.demo1、org.mazhuang.demo2 和 org.mazhuang123 等相关的 Services 都会列出来。 与应用交互 主要是使用 am 命令,常用的 如下: command 用途 start [options] 启动 指定的 Activity startservice [options] 启动 指定的 Service broadcast [options] 发送 指定的广播 force-stop 停止 相关的进程 参数很灵活,和写 Android 程序时代码里的 Intent 相对应。 用于决定 intent 对象的选项如下: 参数 含义 -a 指定 action,比如 android.intent.action.VIEW -c 指定 category,比如 android.intent.category.APP_CONTACTS -n 指定完整 component 名,用于明确指定启动哪个 Activity,如 com.example.app/.ExampleActivity 里还能带数据,就像写代码时的 Bundle 一样: 参数 含义 --esn null 值(只有 key 名) -e|--es string 值 --ez boolean 值 --ei integer 值 --el long 值 --ef float 值 --eu URI --ecn component name --eia [,<EXTRA_INT_VALUE...] integer 数组 --ela [,<EXTRA_LONG_VALUE...] long 数组 调起 Activity 命令格式: adb shell am start [options] 例如: adb shell am start -n com.tencent.mm/.ui.LauncherUI 表示调起微信主界面。 adb shell am start -n org.mazhuang.boottimemeasure/.MainActivity --es "toast" "hello, world" 表示调起 org.mazhuang.boottimemeasure/.MainActivity 并传给它 string 数据键值对 toast - hello, world。 调起 Service 命令格式: adb shell am startservice [options] 例如: adb shell am startservice -n com.tencent.mm/.plugin.accountsync.model.AccountAuthenticatorService 表示调起微信的某 Service。 发送广播 命令格式: adb shell am broadcast [options] 可以向所有组件广播,也可以只向指定组件广播。 例如,向所有组件广播 BOOT_COMPLETED: adb shell am broadcast -a android.intent.action.BOOT_COMPLETED 又例如,只向 org.mazhuang.boottimemeasure/.BootCompletedReceiver 广播 BOOT_COMPLETED: adb shell am broadcast -a android.intent.action.BOOT_COMPLETED -n org.mazhuang.boottimemeasure/.BootCompletedReceiver 这类用法在测试的时候很实用,比如某个广播的场景很难制造,可以考虑通过这种方式来发送广播。 既能发送系统预定义的广播,也能发送自定义广播。如下是部分系统预定义广播及正常触发时机: action 触发时机 android.net.conn.CONNECTIVITY_CHANGE 网络连接发生变化 android.intent.action.SCREEN_ON 屏幕点亮 android.intent.action.SCREEN_OFF 屏幕熄灭 android.intent.action.BATTERY_LOW 电量低,会弹出电量低提示框 android.intent.action.BATTERY_OKAY 电量恢复了 android.intent.action.BOOT_COMPLETED 设备启动完毕 android.intent.action.DEVICE_STORAGE_LOW 存储间过低 android.intent.action.DEVICE_STORAGE_OK 存储间恢复 android.intent.action.PACKAGE_ADDED 安装了新的应用 android.net.wifi.STATE_CHANGE WiFi 连接状态发生变化 android.net.wifi.WIFI_STATE_CHANGED WiFi 状态变为启用/关闭/正在启动/正在关闭/未知 android.intent.action.BATTERY_CHANGED 电池电量发生变化 android.intent.action.INPUT_METHOD_CHANGED 系统输入法发生变化 android.intent.action.ACTION_POWER_CONNECTED 外部电源连接 android.intent.action.ACTION_POWER_DISCONNECTED 外部电源断开连接 android.intent.action.DREAMING_STARTED 系统开始休眠 android.intent.action.DREAMING_STOPPED 系统停止休眠 android.intent.action.WALLPAPER_CHANGED 壁纸发生变化 android.intent.action.HEADSET_PLUG 插入耳机 android.intent.action.MEDIA_UNMOUNTED 卸载外部介质 android.intent.action.MEDIA_MOUNTED 挂载外部介质 android.os.action.POWER_SAVE_MODE_CHANGED 省电模式开启 (以上广播均可使用 adb 触发) 强制停止应用 命令: adb shell am force-stop 命令示例: adb shell am force-stop com.qihoo360.mobilesafe 表示停止 360 安全卫士的一切进程与服务。 文件管理 复制设备里的文件到电脑 命令: adb pull [电脑上的目录] 其中 电脑上的目录 参数可以省略,默认复制到当前目录。 例: adb pull /sdcard/sr.mp4 ~/tmp/ 小技巧:设备上的文件路径可能需要 root 权限才能访问,如果你的设备已经 root 过,可以先使用 adb shell 和 su 命令在 adb shell 里获取 root 权限后,先 cp /path/on/device /sdcard/filename 将文件复制到 sdcard,然后 adb pull /sdcard/filename /path/on/pc。 复制电脑里的文件到设备 命令: adb push 例: adb push ~/sr.mp4 /sdcard/ 小技巧:设备上的文件路径普通权限可能无法直接写入,如果你的设备已经 root 过,可以先 adb push /path/on/pc /sdcard/filename,然后 adb shell 和 su 在 adb shell 里获取 root 权限后,cp /sdcard/filename /path/on/device。 模拟按键/输入 在 adb shell 里有个很实用的命令叫 input,通过它可以做一些有趣的事情。 input 命令的完整 help 信息如下: Usage: input [] [...] The sources are: mouse keyboard joystick touchnavigation touchpad trackball stylus dpad gesture touchscreen gamepad The commands and default sources are: text (Default: touchscreen) keyevent [--longpress] ... (Default: keyboard) tap (Default: touchscreen) swipe [duration(ms)] (Default: touchscreen) press (Default: trackball) roll (Default: trackball) 比如使用 adb shell input keyevent 命令,不同的 keycode 能实现不同的功能,完整的 keycode 列表详见 KeyEvent,摘引部分我觉得有意思的如下: keycode 含义 3 HOME 键 4 返回键 5 打开拨号应用 6 挂断电话 24 增加音量 25 降低音量 26 电源键 27 拍照(需要在相机应用里) 64 打开浏览器 82 菜单键 85 播放/暂停 86 停止播放 87 播放下一首 88 播放上一首 122 移动光标到行首或列表顶部 123 移动光标到行末或列表底部 126 恢复播放 127 暂停播放 164 静音 176 打开系统设置 187 切换应用 207 打开联系人 208 打开日历 209 打开音乐 210 打开计算器 220 降低屏幕亮度 221 提高屏幕亮度 223 系统休眠 224 点亮屏幕 231 打开语音助手 276 如果没有 wakelock 则让系统休眠 下面是 input 命令的一些用法举例。 电源键 命令: adb shell input keyevent 26 执行效果相当于按电源键。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值