首页 > 实用文档 > 其他范文

Linux0.11 启动流程分析

xiaoyaopan

【简介】感谢网友“xiaoyaopan”参与投稿,以下是小编精心整理的Linux0.11 启动流程分析(共3篇),希望对大家有所帮助。

篇1:Linux0.11 启动流程分析

当PC上电后,80x86架构的CPU将自动进入实模式(指寻址采用和8086相同的16位段和偏移量,最大寻址空间1MB,最大分段64KB,可以使用32位指令。32位的x86 CPU用做高速的8086),并从地址0xF000:0xFFF0开始自动执行代码(这个地址通常是BIOS ROM中的地址)此条代码是一个远跳转指令:jmp far 0xF000:0xE05B。这时BIOS开始执行某些系统检测及设定(POST上电自检、中断例程、系统设置程序等等,并在物理地址0处开始初始化中断向量。此后,它将可启动设备的第一个扇区(磁盘引导扇区,512字节)读入到内存绝对地址0x0000:0x7C00处 即31KB,并跳转到这个地方开始执行。被读入的文件就是用8086汇编编写的(boot/bootsect.s)。

bootsect.s 将会把自己移动到内存绝对地址0x90000(576KB)处,并把启动设备后2KB字节代码(boot/setup.s)读入到内存0x90200处,而内核的其他部分(systen模块)则被读入到内存地址0x10000(64KB)开始处,

setup.s 程序将会把system模块移动到物理内存起始位置处,

负责从BIOS中获取系统数据,并将这些数据放到系统内存的适当位置。接下来加载中断描述符表寄存器(idtr)和全局描述符表寄存器(gdtr),开启A20地址线,重新设置两个中断控制芯片8259A,将硬件中断号重新设置为0x20~0x2f。最后设置CPU的控制寄存器CR0(机器状态字),从而进入32位保护模式,并跳转到位于system模块最前面部分head.s程序继续执行。

head.s程序执行完成后,已经正式完成了内存页目录和页表的设置,并重新设置了内核实际使用的中断描述表idt和全局描述符表gdt。另外还为软盘驱动程序开辟了1KB字节的缓冲区。

篇2:Android启动流程分析 action的执行和service的启动

在前面的文章分析完init.rc的解析以后,我们知道现在action按照init.c的main函数中的秩序,维护了一条qlist的链表,listnode为action_qlist service也维护了一条链表,listnode为service_list, 那么,在android的启动过程中,action和service是如何被启动的呢? 我们接下来再回到我们一直分析的init.c的main函数中来看一下。 在main函数中,完成前面的解析工作之后,我们分析到了一个死循环,来看一下:

for(;;) { int nr, i, timeout = -1; execute_one_command; restart_processes(); ..... }

这个for循环的处理有很多,但是我们现在暂时关心的只有这短短的两个函数。

execute_one_command();

restart_processes();

首先我们来分析execute_one_command函数,看着个函数的名字,我们就能明白这个函数的功能。 这个函数就是去执行一个command嘛。。 来看一下这个函数的实现:

void execute_one_command(void){ int ret, i; char cmd_str[256] = ; if (!cur_action || !cur_command || is_last_command(cur_action, cur_command)) { // 在第一次的启动中,因为都是NULL,所以肯定可以进入这个判断,如果不是第一次的话,因为得到cur_action或者cur_command都是null,并且如果这个command是当前action的最后一个command的话,会进入到下面的这个判断。 cur_action = action_remove_queue_head(); // 依次获取action从action_queue中 cur_command = NULL; // 在获取新的action之后,将cur_command设置为null if (!cur_action) // 如果没有action了,则返回return; INFO(processing action %p (%s), cur_action, cur_action->name); cur_command = get_first_command(cur_action); // 如果是一个新的action的话,会执行到这一步去获得first command } else { cur_command = get_next_command(cur_action, cur_command); // 仍然在action的内部链表中,如果仍然存在没有被获取到的command的话,则会去获得下一个command。 } if (!cur_command) // 如果获取到的command为空的话,会返回,反之,继续 return; ret = cur_command->func(cur_command->nargs, cur_command->args); // 会调用这个command的func区执行,执行的参数个数为nargs,命令为args if (klog_get_level() >= KLOG_INFO_LEVEL) { // Log的打印 for (i = 0; i < cur_command->nargs; i++) {strlcat(cmd_str, cur_command->args[i], sizeof(cmd_str));if (i < cur_command->nargs - 1) { strlcat(cmd_str, , sizeof(cmd_str));} } INFO(command '%s' action=%s status=%d (%s:%d), cmd_str, cur_action ? cur_action->name : , ret, cur_command->filename, cur_command->line); }}

其实这个逻辑是比较好理解的,我们要着重分析的仅仅是如何获取action以及command. 来看一下action_remove_queue_head这个函数:

struct action *action_remove_queue_head(void){ if (list_empty(&action_queue)) { // 首先我们去判断当前待执行的action是否已经为null,即是否还有action没有被执行 return 0; } else { struct listnode *node = list_head(&action_queue); // 如果仍然有未被执行的队列的话,就将node指向现在action_queue的头指针 struct action *act = node_to_item(node, struct action, qlist); // 取出action list_remove(node); // 将这个节点从整个action _queue的列表中删除 list_init(node); // 删除这个节点后,为了安全起见,将node自己指向自己,以避免出现野指针。 return act; // 返回已经查找到的action }}

我们可以看到,其实是从action_queue中拿每一个结构体的。 在拿到action之后呢?就要从action里面去拿command了。 来看一下下面的这两个函数:

static struct command *get_first_command(struct action *act) // 从一个actoin里面寻找其第一个command,所以只用传递action即可{ struct listnode *node;node = list_head(&act->commands); // 将node指向action的commands的结构体 if (!node || list_empty(&act->commands)) // 如果这个节点不存在,或者这个action的commands结构体为空,则返回null return NULL; return node_to_item(node, struct command, clist); // 返回第一个节点}static struct command *get_next_command(struct action *act, struct command *cmd) // 返回当前commands的下一个command{ struct listnode *node; node = cmd->clist.next; 指针向后移动next if (!node) // 如果不存在,则返回null return NULL; if (node == &act->commands) // 如果这个节点已经是头节点了,则返回null return NULL; return node_to_item(node, struct command, clist); // 返回next节点}

在获取到了command之后,我们会去调用command的方法:

ret = cur_command->func(cur_command->nargs, cur_command->args);

去执行command里面的每一个func。 但是,非常奇怪的是,执行完commands之后,service是怎么启动的呢? 我们再去init.rc里面一探究竟。

on boot .... class_start coreon nonencrypted class_start main class_start late_start

我们看到在action里面,会有一些commands是class_start, 而后面跟的参数,好像与我们service的class name 是一致的。

再回到init.rc里面看看service的部分呢?

service adbd /sbin/adbd --root_seclabel=u:r:su:s0 class core socket adbd stream 660 system system disabled seclabel u:r:adbd:s0# adbd on at boot in emulatoron property:ro.kernel.qemu=1 start adbdservice lmkd /system/bin/lmkd class core critical socket lmkd seqpacket 0660 system systemservice servicemanager /system/bin/servicemanager class core user system group system critical onrestart restart healthd onrestart restart zygote onrestart restart media onrestart restart surfaceflinger onrestart restart drm

从keywords里面,我们找到了对应的function:

./init/keywords.h:53: KEYWORD(class_start, COMMAND, 1, do_class_start)

来看一下do_class_start的实现:

int do_class_start(int nargs, char **args){ /* Starting a class does not start services * which are explicitly disabled. They must * be started individually. */ service_for_each_class(args[1], service_start_if_not_disabled); return 0;}

这个函数的实现很简单,仅仅是传递调用了service_for_each_class, 并且在传递service name的时候,多传递了一个参数为service_start_if_not_disable.

void service_for_each_class(const char *classname, void (*func)(struct service *svc)){ struct listnode *node; struct service *svc; list_for_each(node, &service_list) { // 遍历service的结构体,这里是不会重复的,因为service的name如果有重复的时候,在解析过程中就已经处理了 svc = node_to_item(node, struct service, slist); // 从slist里取出每一个结构体 if (!strcmp(svc->classname, classname)) { // 如果名字是匹配的话,就会进入这个判断func(svc); // 执行service_start_if_not_disable, 并且将当前的service结构体给传递进去 } }}

接下来要执行的就是service_start_if_not_disable了,我们来看一下具体的实现:

static void service_start_if_not_disabled(struct service *svc){ if (!(svc->flags & SVC_DISABLED)) { service_start(svc, NULL); } else { svc->flags |= SVC_DISABLED_START; }}

如果这个service被设置为disabled的话,就不会被启动,如果没有设置的话,我们会去启动这个service。 这里需要注意的是,在我们调用service_start的时候,我们会去将第二个形参置为NULL。 在service_start的时候,这个函数很长很场,但是可以根据注释,将其分为三个阶段。

void service_start(struct service *svc, const char *dynamic_args){ /// ****************************** start service 的第一个阶段 struct stat s; pid_t pid; int needs_console; int n; char *scon = NULL; int rc; /* starting a service removes it from the disabled or reset * state and immediately takes it out of the restarting * state if it was in there */ svc->flags &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET|SVC_RESTART|SVC_DISABLED_START)); // 这个service即将被启动,将其从disable或reset的状态给移除掉,置其为重新运行的状态 svc->time_started = 0; /* running processes require no additional work -- if * they're in the process of exiting, we've ensured * that they will immediately restart on exit, unless * they are ONESHOT */ if (svc->flags & SVC_RUNNING) { // 如果这个service仍然是运行态的话,即return return; } needs_console = (svc->flags & SVC_CONSOLE) ? 1 : 0; if (needs_console && (!have_console)) { ERROR(service '%s' requires console, svc->name); svc->flags |= SVC_DISABLED; return; } // 如果这个service的flags是初始console,但是这个已经启动了的话,就会设置当前的flags为disabled if (stat(svc->args[0], &s) != 0) { // 如果要执行的这个service的start的command不存在的话,返回error ERROR(cannot find '%s', disabling '%s', svc->args[0], svc->name); svc->flags |= SVC_DISABLED; return; } if ((!(svc->flags & SVC_ONESHOT)) && dynamic_args) { // 因为dynamic_args为null,所以这边不会进入这个判断 ERROR(service '%s' must be one-shot to use dynamic args, disabling, svc->args[0]); svc->flags |= SVC_DISABLED; return; } // ***********************************************************这里我们可以认为是第二个阶段,selinux是信息安全相关的操作,这边我们忽略掉 if (is_selinux_enabled() >0) { if (svc->seclabel) {scon = strdup(svc->seclabel);if (!scon) { ERROR(Out of memory while starting '%s', svc->name); return;} } else {char *mycon = NULL, *fcon = NULL;INFO(computing context for service '%s', svc->args[0]);rc = getcon(&mycon);if (rc < 0) { ERROR(could not get context while starting '%s', svc->name); return;}rc = getfilecon(svc->args[0], &fcon);if (rc < 0) { ERROR(could not get context while starting '%s', svc->name); freecon(mycon); return;}rc = security_compute_create(mycon, fcon, string_to_security_class(process), &scon);if (rc == 0 && !strcmp(scon, mycon)) { ERROR(Warning! Service %s needs a SELinux domain defined; please fix!, svc->name);}freecon(mycon);freecon(fcon);if (rc < 0) { ERROR(could not get context while starting '%s', svc->name); return;} } } // ***************************************** selinux的操作结束,进入到第三个阶段 NOTICE(starting '%s', svc->name); pid = fork(); // fork一个自进程,即所有从init.rc启动的service,都是一个子进程 if (pid == 0) { // pid = 0, 进入到子进程中 struct socketinfo *si; struct svcenvinfo *ei; char tmp[32]; int fd, sz; umask(077); if (properties_inited()) { get_property_workspace(&fd, &sz); // 得到属性存储空间的信息并加入到环境变量中sprintf(tmp, %d,%d, dup(fd), sz);add_environment(ANDROID_PROPERTY_WORKSPACE, tmp); } for (ei = svc->envvars; ei; ei = ei->next) // 将service自己声明的env加入到环境变量中add_environment(ei->name, ei->value); for (si = svc->sockets; si; si = si->next) { // 根据socket info设置socketint socket_type = ( !strcmp(si->type, stream) ? SOCK_STREAM :(!strcmp(si->type, dgram) ? SOCK_DGRAM : SOCK_SEQPACKET));int s = create_socket(si->name, socket_type, si->perm, si->uid, si->gid, si->socketcon ?: scon);if (s >= 0) { publish_socket(si->name, s);} } freecon(scon); scon = NULL; if (svc->ioprio_class != IoSchedClass_NONE) {if (android_set_ioprio(getpid(), svc->ioprio_class, svc->ioprio_pri)) { ERROR(Failed to set pid %d ioprio = %d,%d: %s, getpid(), svc->ioprio_class, svc->ioprio_pri, strerror(errno));} } if (needs_console) {setsid();open_console(); } else {zap_stdio(); }#if 0 for (n = 0; svc->args[n]; n++) {INFO(args[%d] = '%s', n, svc->args[n]); } for (n = 0; ENV[n]; n++) {INFO(env[%d] = '%s', n, ENV[n]); }#endif setpgid(0, getpid()); /* as requested, set our gid, supplemental gids, and uid */ if (svc->gid) { // 设置gidif (setgid(svc->gid) != 0) { ERROR(setgid failed: %s, strerror(errno)); _exit(127);} } if (svc->nr_supp_gids) {if (setgroups(svc->nr_supp_gids, svc->supp_gids) != 0) { ERROR(setgroups failed: %s, strerror(errno)); _exit(127);} } if (svc->uid) { // 设置uidif (setuid(svc->uid) != 0) { ERROR(setuid failed: %s, strerror(errno)); _exit(127);} } if (svc->seclabel) {if (is_selinux_enabled() >0 && setexeccon(svc->seclabel) < 0) { ERROR(cannot setexeccon('%s'): %s, svc->seclabel, strerror(errno)); _exit(127);} } if (!dynamic_args) { // 因为dynamic_args设置的为null,我们在第一次从init.rc启动的时候,一定会进入到这个判断,if (execve(svc->args[0], (char**) svc->args, (char**) ENV) < 0) { // !!! 执行当前的service的启动的命令,也就是说从这边开始,我们就可以理解为已经从init进程中,去像kernel执行init一样,就去执行各个service所对应的启动函数了! ERROR(cannot execve('%s'): %s, svc->args[0], strerror(errno));} } else {char *arg_ptrs[INIT_PARSER_MAXARGS+1];int arg_idx = svc->nargs;char *tmp = strdup(dynamic_args);char *next = tmp;char *bword;/* Copy the static arguments */memcpy(arg_ptrs, svc->args, (svc->nargs * sizeof(char *)));while((bword = strsep(&next, ))) { arg_ptrs[arg_idx++] = bword; if (arg_idx == INIT_PARSER_MAXARGS) break;}arg_ptrs[arg_idx] = '';execve(svc->args[0], (char**) arg_ptrs, (char**) ENV); } _exit(127); } freecon(scon); if (pid < 0) { ERROR(failed to start '%s', svc->name); svc->pid = 0; return; } svc->time_started = gettime(); svc->pid = pid; svc->flags |= SVC_RUNNING; if (properties_inited()) notify_service_state(svc->name, running);}

终于结束了漫长的init进程的分析,估计这十篇文章可以基本概括了init进程启动过程中的每一个细节。 但是,这样是不够的,我们需要接下来继续看一下,android系统启动流程所继续的其他部分。 接下来,也就是我们启动过程中常见的zygote了!

篇3:Linux系统启动流程分析

一 系统上电和启动ROM

NOR Flash作为启动ROM的系统启动过程

NOR Flash开头处存放启动代码,程序从NOR Flash开始处启动,

Linux系统启动流程分析

。 配置EMI寄存器,设置好各存储器的地址和存取规则。 配置电源管理模块,各模块上电。 启动代码将位于NOR Flash中的正式执行代码复制到内存中,以提高执行效率。 设置PC指针,指向NOR Flash中固定地址。 设置地址映射,用0地址映射到内存RAM空间。 设置PC指针,指向RAM中初始化代码,开始执行代码。

NAND Flash作为启动ROM的系统启动流程

上电初始,DMA默认设置将存储在NAND Flash中第一页的数据搬运到内部RAM中,然后设置PC到内部RAM开始处的地址,开始执行代码, 在启动代码中设置中断向量和硬件配置等。 将执行代码搬运到外部SDRAM或DDR=RAM,留出启动代码的位置。 将启动代码搬运到SDRAM或DDR-RAM中首址。 设置Remap,将0地址重新映射到SDRAM或DDR-RAM首地址。 设置PC指针,开始执行正式的执行代码。

二 Bootloader引导

三 Linux内核引导

非压缩内核:Image

压缩内核:zImage

内核初始化 设备初始化 启动内核 挂载文件系统 启动用户空间进程

四 init初始化系统服务

初始化log系统 解析/init.rc和/init.%hardware%.rc文件,执行early-init,并执行解析出的init动作、early-boot动作、boot动作和execute property动作。 进行设备初始化,属性服务器初始化并开启属性服务。 进入无线循环以等待属性设置或子进程退出事件。

相关图文

推荐文章