0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

zircon微内核启动代码分析

yzcdx 来源:OS与AUTOSAR研究 2023-06-11 09:14 次阅读

1. C++入口函数lk_main

e12dd6c4-07ef-11ee-962d-dac502259ad0.png

Zircon微内核的代码是用C++写的,C++和C的基础语法差不多,C++新加入了一些面向对象的东西,在内核中没有界面化那些C++的API的情况下,区别基本就是C语言结构体的回调函数是一个.C++则是::,不用另外学习C++语法就可以看懂,但是为什么zircon使用了面向对象语言C++?这里你必须对面向对象的思想有最基本的认识,首先就是抽象出来共同的属性操作,这样就不用写代码对不同的模块写重复的代码,而且结构更加的清晰,其次对象的安全性也更好,可以参考zCore入门-面向对象的Rust微内核里面介绍的面向对象OS设计的好处,再次印证那句“编程语言和操作系统的设计是相辅相成的”。

进入正题系统启动整体流程为:kernel-》userboot-》bootfs镜像-》文件系统-》可执行文件-》组件管理器-》拉起进程。

先说kernel的启动,lk_main()入口函数在代码中的位置在kernel/top/main.cc中

//日志打印开关定义
dlog_bypass_init_early();
thread_init_early():
percpu::InitializeBoot();


//创建一个precpu的对象
thread_t*t=&percpu::Get(0).idle_thread;//找到cpu0对应的空闲线程
thread_construct_first(t,"bootstrap");

这t就是一个线程,线程在zircon中也是一个对象,相关的对象为Job->Process->Thread

e15c47d4-07ef-11ee-962d-dac502259ad0.png

Zircon 公开了三个运行代码的主要内核对象:

线程:给定地址空间内的执行线程。

进程:在私有、隔离的地址空间中运行的一组可执行指令。

作业:一组相关的流程和作业。所有作业形成一个单根树。

线程对象是表示分时CPU 执行上下文的构造。Thread 对象与特定的Process Object相关联,它为 I/O 和计算所需的其他对象提供内存和句柄。

线程是调度的基本单位,并且调度有优先级,在thread_construct_first()函数中,会设置线程的信息,例如base_priority优先级为HIGHEST_PRIORITY,set_current_thread设置此线程为当前正在运行的thread

list_add_head(&thread_list,&t->thread_list_node);//把线程加入线程表中,这样调度的时候就可以找到这个线程。

call_constructors();//调用全局构造函数

for (void (*const* a)() = __init_array_start; a != __init_array_end;a++)(*a)();

函数上打了attribute((constructor))则为全局构造函数,编译器将其编译到.init 段,而__init_array_start 和__init_array_end 是该段的开始和结尾。

2. 内核分层初始化lk_primary_cpu_init_level

在zircon中对于系统启动进行分层设计,这样结构很明朗好理解,而且新添加内容的时候方便找到对应的地方,在出错调试的时候也更加的方便,真是简单实用又高深的方法,大道至简也就这样吧,在我们的编程中可以借鉴。

lk_primary_cpu_init_level(LK_INIT_LEVEL_EARLIEST,LK_INIT_LEVEL_ARCH_EARLY-1);
lk_init_level(LK_INIT_FLAG_PRIMARY_CPU,start_level,stop_level);


for(conststructlk_init_struct*ptr=__start_lk_init;ptr!=__stop_lk_init;ptr++){
  /* keep the lowest one we haven't called yet */
if(ptr->level>=start_level&&ptr->level>last_called_level){
    found = ptr;
    continue;
  }
if(ptr->level==last_called_level&&ptr!=last&&seen_last){
    found = ptr;
    break;
}
found->hook(found->level);

可见去__start_lk_init结构体数组中按照level找元素,找到就执行hook()回调函数。

在kernel/kernel.ld中定义

PROVIDE_HIDDEN(__start_lk_init = .);
KEEP(*(.data.rel.ro.lk_init))
PROVIDE_HIDDEN(__stop_lk_init = .);

Ld文件规定了二进制文件的组织形式,在数据段中存在这个数组,这个数组里面的元素按照.data.rel.ro. lk_init字符串去组织

在kernel/include/lk/init.h中

#define LK_INIT_HOOK_FLAGS(_name, _hook,
_level, _flags) 
__ALIGNED(sizeof(void *)) __USED
__SECTION(".data.rel.ro.lk_init") 
static const struct lk_init_struct _init_struct_##_name = {         
.level = _level,                                               
.flags = _flags,                                               
.hook = _hook,                                                 
.name = #_name,                                                 
};
#define LK_INIT_HOOK(_name, _hook, _level)

LK_INIT_HOOK_FLAGS(_name, _hook, _level, LK_INIT_FLAG_PRIMARY_CPU)

LK_INIT_HOOK这个宏用于声明这个数据段的结构体,我们添加初始化的模块函数时就可以在模块尾部声明这个LK_INIT_HOOK。

LK_INIT_LEVEL_EARLIEST这个level的结构体不存在,只是空跑下算初始化

lk_primary_cpu_init_level(LK_INIT_LEVEL_ARCH_EARLY,LK_INIT_LEVEL_PLATFORM_EARLY - 1);

就会执行

kernel/lib/code_patching/code_patching.cc中apply_startup_code_patches函数

LK_INIT_HOOK(code_patching,apply_startup_code_patches,

LK_INIT_LEVEL_ARCH_EARLY)

这个函数好像没做什么。

3. 使用chatGPT解释看不懂代码

arch_early_init();

x86_mmu_early_init();

mmu就是内存管理单元,主要负责物理内存和虚拟内存的映射,这里是early_init,具体不详细说明了,这里说一个技巧那就是chatGPT,直接把代码复制到chatGPT提问让解析一下

e1cd05f0-07ef-11ee-962d-dac502259ad0.png

最近我也经常使用chatGPT,而且越难的东西它越擅长,这里说的难就是用的人少,需要大量经验,难入门的东西,一个典型就是汇编语言,只是看汇编就交给chatGPT,自己完全没必要学习汇编,学了又会忘记

chatGPT有一个缺点就是不保真,需要你是一个行内的人,你提问它回答给你启发,你可以自己大致的辨别有用的信息,并且求证,这不就是导师干的事么,最重要的还是方向啊,现在最牛逼的老师领进门了,那修行还不事半功倍。

platform_early_init(void)

对x86来说kernel/platform/pc/platform.cc中实现

初始化串口,之后串口就可以使用了

platform_save_bootloader_data
if (_zbi_base != NULL) {
zbi_header_t* bd = (zbi_header_t*)X86_PHYS_TO_VIRT(_zbi_base);
process_zbi(bd, (uintptr_t)_zbi_base);
}

_zbi_base在kernel/arch/x86/start.S中赋值,multiboot传给start.S的是imag的基地址

// _zbi_base is in bss, so now it's safe to set it.

mov %ebx,PHYS(_zbi_base)

这个zbi类型的数据,之前介绍过zbi,zbi有很多类型,这里为"CONTAINER"

boot_reserve_init();保留kernel所在的区域

PMM: boot reserve add [0x100000, 0x334fff]
platform_preserve_ramdisk();保留ramdis所在的区域
PMM: boot reserve add [0xb4e000, 0x1598fff]
pc_mem_init-》platform_mem_range_init-》mem_arena_init-》pmm_add_arena-》
PmmArena* arena = new
(boot_alloc_mem(sizeof(PmmArena))) PmmArena();

会分配boot内存0x1599000,0x1599040 这个都是物理内存如果是虚拟内存转换关系为:

paddr_to_physmap ()中:

"0xffffff8000000000UL" + 物理内存= 虚拟内存

lk_primary_cpu_init_level(LK_INIT_LEVEL_PLATFORM_EARLY,LK_INIT_LEVEL_TARGET_EARLY - 1);关于平台早期初始化的钩子函数,主要是一些驱动、中断

LK_INIT_HOOK(platform_dev_init,platform_dev_init, LK_INIT_LEVEL_PLATFORM)

这个会初始化一堆的驱动,驱动从zbi中找到ZBI_TYPE_KERNEL_DRIVER类型的,继续执行pdev_init_driver()会执行.data.rel.ro.lk_pdev_init数据段中结构体初始化,

LK_PDEV_INIT驱动通过这个宏就可以注册到.data.rel.ro.lk_pdev_init

然后就可以看到我们的打印了

vm_init_preheap();创建供内核使用的虚存空间VmAspace

上面提到boot的内存起始为0x1599000,分配到了0x1599040,

MarkPagesInUsePhys在页表中标记内存页已经使用

heap_init();

Zircon 的内核堆由内部的 cmpctmalloc 实现。

cmpct_init

vm_init();虚拟内存初始化

找到内核镜像的各个段,及初始化读写策略,例如

regions[] = {
{
.name = "kernel_code",
.base = (vaddr_t)__code_start,
.size = ROUNDUP((uintptr_t)__code_end - (uintptr_t)__code_start,
PAGE_SIZE),
.arch_mmu_flags = ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_EXECUTE,
},

// 遍历上面的几个段,并设置策略

for (uint i = 0; i < fbl::count_of(regions); ++i) {
temp_region* region = ®ions[i];
ASSERT(IS_PAGE_ALIGNED(region->base));
dprintf(INFO, "VM: reserving kernel region [%#" PRIxPTR
", %#" PRIxPTR ") flags %#x name '%s'
",
region->base,
region->base + region->size, region->arch_mmu_flags, region->name);
// 在vmm中标记一块虚拟内存,这块虚拟内存抽象为VmRegion类,拥有自己的底层mmu相关的配置
zx_status_t status = aspace->ReserveSpace(region->name,
region->size, region->base);
ASSERT(status == ZX_OK);
// 对某VmRegion对应的虚拟内存设置内存保护的相关参数
status = ProtectRegion(aspace, region->base, region->arch_mmu_flags);
ASSERT(status == ZX_OK);
}
// 标记映射表
// reserve the kernel aspace where the physmap is
aspace->ReserveSpace("physmap", PHYSMAP_SIZE,
PHYSMAP_BASE);

·ReserveSpace:在 vmm 中标记一块虚拟内存,这块虚拟内存抽象为VmRegion类,拥有自己的底层mmu相关的配置

·ProtectRegion:对某VmRegion对应的虚拟内存设置内存保护的相关参数

物理内存一块区域存储的数据,可以映射为内核里面一个VmRegion类,这个类里面有其虚拟内存信息,这一过程就是内存映射。

kernel_init();

mp_init();多核初始化

// 多核初始化
void mp_init(void) {
// 核间中断任务表初始化
mp.ipi_task_lock = SPIN_LOCK_INITIAL_VALUE;
for (uint i = 0; i < fbl::count_of(mp.ipi_task_list); ++i) {
list_initialize(&mp.ipi_task_list[i]);
}
}

thread_create("bootstrap2",&bootstrap2, NULL, DEFAULT_PRIORITY);

4.bootstrap2线程

创建 bootstrap2 线程加入thread_list列表,由 bootstrap2 线程完成剩下的初始化工作,等到下次调度的时候,这个线程会运行。

thread_become_idle();变成idle进程,并且开启中断启动调度

sched_reschedule();启动线程调度

arch_enable_ints();开启中断

进入bootstrap2线程

arch_init(); CPU 架构初始化,这里为x86 intel平台cpu的一些初始化,包括mmu、gdtidt、tarce

-》platform_init(); 平台初始化

platform_init_smp多cpu管理

-》x86_bringup_aps-》

status =

x86_bootstrap16_acquire((uintptr_t)_x86_secondary_cpu_long_mode_entry,

&bootstrap_aspace, (void**)&bootstrap_data,

&bootstrap_instr_ptr);

_x86_secondary_cpu_long_mode_entry是第二个cpu启动执行的地址

之后会启动其他cpu进入_x86_secondary_cpu_long_mode_entry执行

x86_secondary_entry-》

finish_secondary_entry-》

lk_secondary_cpu_entry-》

thread_secondary_cpu_entry-》

thread_exit开启本cpu调度

pc_init_smbios

SMBIOS(System Management BIOS)是主板或系统制造者以标准格式显示产品管理信息所需遵循的统一规范。

打印出来为:smbios: manufacturer="QEMU"product="Standard PC (Q35 + ICH9, 2009)"

lk_primary_cpu_init_level(LK_INIT_LEVEL_TARGET, LK_INIT_LEVEL_LAST);

初始化下面几个模块:

* debuglog in $zx/kernel/lib/debuglog/debuglog.cc
* kcounters in $zx/kernel/lib/counters/counters.cc
* ktrace in $zx/kernel/lib/ktrace/ktrace.cc
* kernel_shell in $zx/kernel/lib/console/console.cc
* userboot in $zx/kernel/lib/userabi/userboot.cc

LK_INIT_HOOK(userboot, userboot_init,LK_INIT_LEVEL_USER)中userboot_init函数会启动

5. Userboot进程

e20d2b76-07ef-11ee-962d-dac502259ad0.png

kernel-》userboot-》bootfs镜像-》文件系统-》可执行文件-》组件管理器-》拉起进程

关于userboot初始化

zx_status_t status = MessagePacket::Create创建一个新的MessagePacket msg

Handle** const handles =msg->mutable_handles()创建一个可变引用handles指向其中的handle数组,这个数组里面存了很多信息

enum HandleIndex : uint32_t {
// These describe userboot itself.
kProcSelf, // 记录新进程中指向新进程本身的handle号
kVmarRootSelf, // 记录为新进程创建的vmar的handle
kRootJob, //一个process属于一个job
kRootResource,
// Essential VMO handles.
kZbi,
kFirstVdso,
kLastVdso = kFirstVdso + static_cast(VdsoVariant::COUNT)
- 1,
// These get passed along to userland to be recognized by ZX_PROP_NAME.
// The decompressor is also used by userboot itself.
// The remainder are VMO handles that userboot doesn't care about.
kUserbootDecompressor,
kFirstKernelFile = kUserbootDecompressor,
kCrashlog,
kCounterNames,
kCounters,
#if ENABLE_ENTROPY_COLLECTOR_TEST
kEntropyTestData,
#endif
kFirstInstrumentationData,
kHandleCount = kFirstInstrumentationData +
InstrumentationData::vmo_count()
};

创建process,其包含一个vmar,每个进程都从一个单一的虚拟内存地址区域 (VMAR) 开始,进程根VMAR 跨越整个用户地址空间。VMAR 用于映射虚拟内存对象(VMO),虚拟内存对象将程序所需的代码、数据、匿名和共享内存页面提供到进程的地址空间中。

// It also gets many VMOs for VDSOs and other things.

const VDso* vdso = VDso::Create();

vdso->GetVariants(&handles[kFirstVdso]);

vDSO(virtualDynamic Shared Object),Zircon vDSO 是 Zircon 内核访问系统调用的唯一方法(作为系统调用的跳板)。它之所以是虚拟的,是因为它不是从文件系统中的ELF文件加载的,而是由内核直接提供的vDSO镜像

bootstrap_vmos(handles);
EmbeddedVmo decompress_zbi("lib/hermetic/decompress-zbi.so",
decompress_zbi_image,
DECOMPRESS_ZBI_DATA_END);
handles[kUserbootDecompressor] =
decompress_zbi.vmo_handle().release();

这里开始解压缩ZBI格式的ramdisk文件,之前我们知道镜像分为了kernel+ranmdisk,现在kernel初始化差不多了,要开始读这个ramdis里面的内容了,原则上这里面的内容都是用户进程相关的东西。

e25b2164-07ef-11ee-962d-dac502259ad0.png

platform_get_ramdisk(&rsize);

ramdisk_base和ramdisk_size

zx_status_t status = VmObjectPaged::CreateFromWiredPages(

rbase, rsize, true, &rootfs_vmo);

创建一个vmo对象,就是虚拟内存对象,指向这个ramdisk

get_vmo_handle(rootfs_vmo, false, nullptr,&handles[kZbi]);

handles的kZbi里面存的这个vmo

e29f5834-07ef-11ee-962d-dac502259ad0.png

status = get_vmo_handle(ktl::move(kcounters_vmo), true, nullptr,

&handles[kCounters]);

内核计数器放入handles

// Make the channel that will hold the message.
KernelHandle user_handle, kernel_handle;
status = ChannelDispatcher::Create(&user_handle, &kernel_handle,
&rights);
ASSERT(status ==
ZX_OK);

创建一个channel,有两头一个头是user一头是kernel,用于通信

status = kernel_handle.dispatcher()->Write(ZX_KOID_INVALID,

ktl::move(msg));

内核侧先写入点数据

process->AddHandle(ktl::move(user_handle_owner));

用户进程把这个channel通过handle绑定到自己身上

Job->Process->handle->channel

status = userboot.Map(vmar, &vdso_base,&entry);

映射就是把vmo映射到vmar上面,vmar是process里面的数据。Entry就是userboot的入口地址,vdso_base就是进行系统调用的基地址

// Map userboot proper.
status = RoDso::Map(vmar_handle.dispatcher(), 0);
if (status == ZX_OK) {
*entry =
vmar_handle.dispatcher()->vmar()->base() + USERBOOT_ENTRY;
// Map the vDSO right after it.
*vdso_base =
vmar_handle.dispatcher()->vmar()->base() + RoDso::size();
// Releasing |vmar_handle| is safe because it has a no-op
// on_zero_handles(), otherwise the mapping routines would have
// to take ownership of the handle and manage its lifecycle.
status = vdso_->Map(vmar_handle.release(), RoDso::size());
}

6. userboot如何在vDSO中取得系统调用

当内核将userboot映射到第一个用户进程时,会像正常程序那样,在内存中选择一个随机地址进行加载。而在映射userboot的vDSO时,并不采用上述随机的方式,而是将vDSO映像直接放在内存中userboot的映像之后。这样一来,vDSO代码与userboot的偏移量总是固定的

在编译阶段中,系统调用的入口点符号表会从vDSO ELF映像中提取出来,随后写入到链接脚本的符号定义中。利用每个符号在vDSO映像中相对固定的偏移地址,可在链接脚本提供的_end符号的固定偏移量处,定义该符号。通过这种方式,userboot代码可以直接调用到放在内存中,其映像本身之后的,每个确切位置上的vDSO入口点。

vdso会映射到userboot的vmar中

status = VmObjectPaged::Create(PMM_ALLOC_FLAG_ANY, 0u,
stack_size, &stack_vmo);
status = vmar->Map(0,
    ktl::move(stack_vmo), 0, stack_size,
ZX_VM_PERM_READ |
ZX_VM_PERM_WRITE,
&stack_mapping);

新建一个堆栈的vmo,然后映射到vmar上面

uintptr_t sp =

compute_initial_stack_pointer(stack_base, stack_size);

计算线程的栈地址

status =

ThreadDispatcher::move(process), 0, "userboot",

&(), &rights);

创建userboot线程,线程是调度的基本单位。

auto arg1 = static_cast(hv);     // 传给userboot线程的第一个参数为一个Handle的指针的编号(实例在process结构中)   
// 第二个参数为vdso的基地址
status = thread->Start(
ThreadDispatcher::EntryState{entry, sp, arg1, vdso_base},
参数为:入口地址、堆栈地址、handels、vdso地址

kernel如何启用userboot?

与任何其他进程一样,userboot必须从已经映射到其地址空间的vDSO开始,这样它才能进行系统调用。内核将userboot和vDSO映射到第一个用户进程,然后在userboot的入口处启动它。

userboot的入口处在哪里?

UserbootImage userboot(vdso);

userboot.Map(vmar, &vdso_base, &entry);

从vdso中找到userboot

const VDso* vdso = VDso::Create();

vdso->GetVariants(&handles[kFirstVdso]);

bootstrap_vmos(handles);

Vdso获取

kernel/lib/userabi/userboot/BUILD.gn中编译

loadable_module("userboot") {
sources = [
"bootdata.cc",
"bootfs.cc",
   "loader-service.cc",
"option.cc",
"start.cc",
"userboot-elf.cc",
"util.cc",
]
configs += [ "$zx/public/gn/config:rodso" ]
ldflags = [ "-Wl,-e,_start" ]
libs = [ vdso_syms_ld ]

_start是入口函数,在

kernel/lib/userabi/userboot/start.cc中定义

extern "C" [[noreturn]] void
_start(zx_handle_t arg) {
bootstrap(zx::channel{arg});
}

这里我们开始进入userboot的代码了。

7. Userboot进程代码

e2e27d26-07ef-11ee-962d-dac502259ad0.png

zx_status_t status = channel.read(0, child_message.cmdline,handles.data(),

sizeof(child_message.cmdline),

handles.size(), &cmdline_len, &nhandles);

读出来init的时候发的消息,里面主要是一些参数,解析出来为

e3315f18-07ef-11ee-962d-dac502259ad0.png

options o;

child_message.pargs.environ_num =

parse_options(log.get(), child_message.cmdline, cmdline_len, &o);

把这option都存入o里面

zx::vmo bootfs_vmo{
bootdata_get_bootfs(log.get(), vmar_self.get(),
handles[kRootJob],
handles[kUserbootDecompressor],
handles[kFirstVdso],
handles[kZbi])};

定位bootfs里面第一个程序

bootdata_get_bootfs
bootdata_t bootdata;
zx_status_t status = zx_vmo_read(bootdata_vmo, &bootdata,
off,
sizeof(bootdata));

读出bootdate的头,这里bootdata.type是BOOTDATA_BOOTFS_BOOT

zx::create(bootdata.extra, 0,
&bootfs_vmo);创建一个vmo
status = Decompressor(job,
engine_vmo, vdso_vmo)
(*zx::unowned_vmo{bootdata_vmo},
off + sizeof(bootdata),
bootdata.length,
bootfs_vmo, 0,
bootdata.extra);

拿到vdso

handles[kBootfsVmo] =

bootfs_vmo.release(); // bootfs_vmo实际在用户态也只是维护一个zx_handle_t

const char* root_option =

o.value[OPTION_ROOT];拿到root进程为pkg/bootsvc

zx::process proc;
zx::vmar vmar;
zx::unowned_job root_job{handles[kRootJob]};    // 本进程的job也就是子进程的job,传承下去
const char* filename = o.value[OPTION_FILENAME];
filename 是bin/bootsvc
//创建子进程
status = zx::create(
*root_job, filename,
static_cast(strlen(filename)), 0,
&proc, &vmar);
check(log.get(), status, "zx_process_create");
load_child_process(log.get(), &o, &bootfs, root_prefix,
handles[kFirstVdso],
proc.get(), vmar.get(),
thread.get(), to_child.get(),
&entry,
&vdso_base, &stack_size,
loader_service_channel.reset_and_get_address());
加载bin/bootsvc,elf程序
elf_load_bootfs-》bootfs_open-》load
bootfs_open -》bootfs_search
zx_status_t status = zx_vmo_create_child(fs->vmo,
ZX_VMO_CHILD_COPY_ON_WRITE,
e->data_off, e->data_len, &vmo);
status =
zx_vmo_replace_as_executable(vmo, ZX_HANDLE_INVALID, &vmo);
elf_load_vmo 这个的vdso从handles[kFirstVdso]传进来的
load(log, "vDSO", vmar, vmo,
NULL, NULL, NULL, NULL, false, false)

会把vdso这个elf格式文件读入进来

sp =

compute_initial_stack_pointer(stack_base, stack_size);

新建堆栈空间

// 同时给子进程发送消息和handle数组。注意,这时子进程还没启动

status = to_child.write(0, &child_message, sizeof(child_message),

handles.data(),handles.size());

status = proc.start(thread, entry, sp,

std::move(child_start_handle), vdso_base);

启动bootsvc

8. bootsvc

在system/core/bootsvc/main.cc中

zx_handle_close(dl_set_loader_service(ZX_HANDLE_INVALID));关闭跟userboot的通道,这时候

ldsvc.Serve(std::move(loader_service_channel));会接收到,然后继续往下执行

zx::vmo bootfs_vmo(zx_take_startup_handle(PA_HND(PA_VMO_BOOTFS, 0)));

从handle里面获取bootfs_vmo

status =

bootsvc::Create(loop.dispatcher(),&bootfs_svc);

status =

bootfs_svc->AddBootfs(std::move(bootfs_vmo));

创建一个bootfs的服务,关联bootfs_vmo

status = bootsvc::RetrieveBootImage(&image_vmo, &item_map,&factory_item_map);

找回boot信息

LoadBootArgs(bootfs_svc, &args_vmo,&args_size);把参数信息读入到vmo

const char* config_path = "/config/devmgr";
fbl::Vector buf;
zx::vmo config_vmo;
uint64_t file_size;
zx_status_t status = bootfs->Open(config_path, &config_vmo,
&file_size);
zx::resource root_resource_handle(zx_take_startup_handle(PA_HND(PA_RESOURCE,
0)));

找到系统资源

fbl::RefPtr svcfs_svc =

bootsvc::Create(loop.dispatcher());

创建svcfs服务

status =

bootsvc::Create(bootfs_svc,loop.dispatcher(), &loader_svc);

创建loader服务

std::thread(LaunchNextProcess, bootfs_svc, svcfs_svc, loader_svc,std::cref(log)).detach();

启动LaunchNextProcess

std::thread(LaunchNextProcess, bootfs_svc, svcfs_svc, loader_svc,
std::cref(log)).detach();
void LaunchNextProcess(fbl::RefPtr
bootfs,
fbl::RefPtr svcfs,
fbl::RefPtr loader_svc,
const zx::debuglog&
log) {
const char* bootsvc_next = getenv("bootsvc.next");
if
(bootsvc_next == nullptr) {
bootsvc_next = "bin/devcoordinator";
}

9. devcoordinator

system/core/devmgr/devcoordinator/main.cc中有main函数

status =

StartSvchost(root_job, require_system, &coordinator, std::move(fshost_client));

/boot/bin/svchost启动

devmgr_vfs_init(&coordinator, devmgr_args, needs_svc_mount,std::move(fshost_server));

devmgr_vfs_init -》

fshost_start-》

devmgr_launch -》

devmgr_launch_with_loader

fshost启动

intret =

thrd_create_with_name(&t, pwrbtn_monitor_starter, nullptr,"pwrbtn-monitor-starter");

pwrbtn-monitor启动

ret=

thrd_create_with_name(&t, service_starter, &coordinator,"service-starter");

const char* args[] = {"/boot/bin/miscsvc", nullptr};

devmgr::devmgr_launch(g_handles.svc_job, "miscsvc", args,nullptr, -1, handles, types,

countof(handles),nullptr, FS_BOOT | FS_DEV | FS_SVC | FS_VOLUME);

miscsvc启动

zx_status_t status =

devmgr::devmgr_launch(g_handles.svc_job,"netsvc", args, nullptr, -1,nullptr, nullptr, 0,&proc, FS_ALL);

netsvc启动

devmgr::devmgr_launch(g_handles.svc_job, "virtual-console",args, env.get(), -1, handles, types,

handle_count,nullptr, FS_ALL);

virtual-console启动

intret =

thrd_create_with_name(&t, fuchsia_starter, coordinator,"fuchsia-starter");

coordinator.PrepareProxy(coordinator.sys_device(), nullptr);

'devhost:sys'启动

coordinator.PrepareProxy(coordinator.test_device(), nullptr);

'devhost:test'启动

coordinator.BindDrivers();

for
(Driver& drv : drivers_) {
zx_status_t status = BindDriver(&drv);
if (status != ZX_OK && status != ZX_ERR_UNAVAILABLE) {
log(ERROR, "devcoordinator: failed to bind driver '%s': %s
",
drv.name.data(),
zx_status_get_string(status));
}
}

绑定了devhost:root和devhost:misc

coordinator.set_running(true);

status = loop.Run();

设置状态为运行,进入循环,启动四个devhost

10. Devhost

Zircon内核中,设备驱动程序以ELF格式的共享库形式存在,由devhost进程按需动态加载(实现代码参见

zircon/system/core/devmgr/devhost/目录)

核心设备管理进程(devmgr),包含具有跟踪设备与驱动关联的devcoordinator进程,同时管理着驱动程序发现,devhost进程创建和控制,还要维护设备文件系统(devfs),通过devfs机制,用户层的服务和应用实现对设备的操作。

e3754318-07ef-11ee-962d-dac502259ad0.png

进程devcoordinator将设备看做是一个统一树状结构。树的分支(和子分支)由一定数量的隶属于devhost进程的设备组成。关于如何将整棵设备树划分以分配到多个devhost进程中,取决于系统的策略:基于安全或者稳定性原因的驱动隔离;以及为了性能原因将驱动进行并置。

参考:

https://blog.csdn.net/sinat_20184565/article/details/92002908

dm dump可以查看进程树

devhost[proxy]
devhost[sys/platform]
devhost[sys/platform/001b]
devhost[sys/platform/acpi/acpi-pwrbtn]
devhost[sys/platform/acpi/i8042]
devhost[00:01.0]
devhost[00:1f.2]

当program loader设置了一个新进程后,使该进程能够进行系统调用的唯一方法是:program loader在新进程的第一个线程开始运行之前,将vDSO映射到新进程的虚拟地址空间(地址随机)。因此,在启动其他能够进行系统调用的进程的每个进程自己本身都必须能够访问vDSO的VMO。

vDSO映像在编译时嵌入到内核中。内核将它作为只读VMO公开给用户空间。内核启动时,会通过计算得到它所在的物理页。






审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 计数器
    +关注

    关注

    32

    文章

    2256

    浏览量

    94549
  • 虚拟机
    +关注

    关注

    1

    文章

    916

    浏览量

    28182
  • C++语言
    +关注

    关注

    0

    文章

    147

    浏览量

    6990
  • ChatGPT
    +关注

    关注

    29

    文章

    1560

    浏览量

    7641

原文标题:Fuchsia入门-zircon微内核启动代码分析

文章出处:【微信号:OS与AUTOSAR研究,微信公众号:OS与AUTOSAR研究】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    BootLoader启动代码分析

    BootLoader启动代码分析
    发表于 08-04 10:09

    如何使用dtb方式启动内核

    份linux 内核代码可以在多个板卡上运行,每个板卡可以使用自己的dtb文件。  老式的u-boot使用ATAGS的方式启动linux内核,本文使用新式的dtb方式
    发表于 04-22 14:06

    Linux内核代码情景分析(上册)浙江大学

    本书着重于对LINUX系统最新版本(2.4.0)内核代码进行情景描述和情景分析. 上册共6章.
    发表于 06-10 14:40 0次下载
    Linux<b class='flag-5'>内核</b>源<b class='flag-5'>代码</b>情景<b class='flag-5'>分析</b>(上册)浙江大学

    Linux内核代码情景分析(下册)浙江大学

    本书着重于对LINUX系统最新版本(2.4.0)内核代码进行情景描述和情景分析. 上册共3章.
    发表于 06-10 14:43 0次下载

    Android内核分析

    介绍Android 移动平台系统架构,通过对Android 源代码分析,将其与标准Linux 内核(2.6.27)源代码相比较,详细解析Android
    发表于 10-29 16:17 116次下载

    嵌入式uCLinux内核启动过程分析

    分析uCLinux的启动过程,可以加快系统启动速度、正确建立应用环境。本文要研究的就是uCLinux操作系统内核启动过程。
    发表于 08-15 16:51 789次阅读

    linux内核启动内核解压过程分析

    linux启动内核解压过程分析,一份不错的文档,深入了解内核必备
    发表于 03-09 13:39 1次下载

    ARM处理器的启动代码分析与设计

    ARM处理器的启动代码分析与设计
    发表于 09-25 08:27 12次下载
    ARM处理器的<b class='flag-5'>启动</b><b class='flag-5'>代码</b>的<b class='flag-5'>分析</b>与设计

    Linux内核移植相关代码解析

    本文通过整理之前研发的一个项目(ARM7TDMI +uCLinux),分析内核启动过程及需要修改的文件,以供内核移植者参考。整理过程中也同时参考了众多网友的帖子,在此谢过。由于整理过程
    发表于 11-07 11:29 0次下载

    linux内核启动流程

    Linux的启动代码真的挺大,从汇编到C,从Makefile到LDS文件,需要理解的东西很多。毕竟Linux内核是由很多人,花费了巨大的时间和精力写出来的。而且直到现在,这个世界上仍然有成千上万的程序员在不断完善Linux
    发表于 11-14 16:19 4352次阅读
    linux<b class='flag-5'>内核</b><b class='flag-5'>启动</b>流程

    Linux内核代码情景分析(全册高清带书签)pdf下载

    Linux内核代码情景分析需要的拿走吧
    发表于 01-04 16:57 9次下载

    嵌入式Linux内核移植相关代码分析

    本文通过整理之前研发的一个项目(ARM7TDMI + uCLinux),分析内核启动过程及需要修改的文件,以供内核移植者参考。整理过程中也同时参考了众多网友的帖子,在此
    发表于 04-02 14:37 266次阅读

    内核与宏内核的比较与分析

    混合内核实质上也是内核,而外内核是一种比较极端的设计方法,目前还处于研究阶段,所以我们就着重讨论宏内核
    发表于 03-17 16:05 11次下载
    <b class='flag-5'>微</b><b class='flag-5'>内核</b>与宏<b class='flag-5'>内核</b>的比较与<b class='flag-5'>分析</b>

    一种实时嵌入式多任务内核分析与改进

    一种实时嵌入式多任务内核分析与改进(嵌入式开发系统)-一种实时嵌入式多任务内核分析与改进
    发表于 07-30 13:49 11次下载
    一种实时嵌入式多任务<b class='flag-5'>微</b><b class='flag-5'>内核</b>的<b class='flag-5'>分析</b>与改进

    分析ARM Cortex-M内核复位启动过程

    ARM Cortex-M内核的复位启动过程也被称为复位序列(Reset sequence),下面就来简要总结分析下这一过程。
    的头像 发表于 03-20 09:58 2398次阅读