一、实验目的

操作系统是一个软件,也需要通过某种机制加载并运行它。在这里我们将通过另外一个更加简单的软件-bootloader来完成这些工作。为此,我们需要完成一个能够切换到x86的保护模式并显示字符的bootloader,为启动操作系统ucore做准备。lab1提供了一个非常小的bootloader和ucore OS,整个bootloader执行代码小于512个字节,这样才能放到硬盘的主引导扇区中。通过分析和实现这个bootloader和ucore OS,读者可以了解到:
1.计算机原理

  • CPU的编址与寻址:基于分段机制的内存管理
  • CPU的中断机制
  • 外设:串口/并口/CGA,时钟,硬盘
    2.Bootloader软件
  • 编译运行bootloader的过程
  • 调试bootloader的方法
  • PC启动bootloader的过程
  • ELF执行文件的格式和加载
  • 外设访问:读硬盘,在CGA上显示字符串
    3.Ucore OS软件
  • 编译运行ucore OS的过程
  • ucore OS的启动过程
  • 调试ucore OS的方法
  • 函数调用关系:在汇编级了解函数调用栈的结构和处理过程
  • 中断管理:与软件相关的中断处理
  • 外设管理:时钟

二、实验内容
lab1中包含一个bootloader和一个OS。这个bootloader可以切换到X86保护模式,能够读磁盘并加载ELF执行文件格式,并显示字符。而这lab1中的OS只是一个可以处理时钟中断和显示字符的幼儿园级别OS。

三、实验练习
1.练习1:理解通过make生成执行文件的过程。(要求在报告中写出对下述问题的回答)
1.1操作系统镜像文件ucore.img是如何一步一步生成的?(需要比较详细地解释Makefile中每一条相关命令和命令参数的含义,以及说明命令导致的结果)
① 进入Ubuntu20.04,打开终端,通过sudo su指令,输入密码,从普通用户切换到root用户。

② 因为我之前已经换过源了,所以这个实验里不用再换源。输入apt install qemu-system-i386,再输入y,安装qemu

③ 输入sudo dj或ctrl+d或exit切换回普通用户,实验准备部分结束。

④ 进入到OS/ucore_os_lab-master/labcodes/lab1目录下,输入指令make

⑤ 如果连续两次输入make/make V=,会提示没有更改,这时可以make clean后再次make V=

⑥ 运行make V=,观察每一步的make指令,得到以下所有输出:
lab1是ucore的开始,部分函数未完成调用会出现warning




其中,除kernel部分外:
链接生成kernel:

bootblock部分:

链接生成bootblock:

生成ucore.img:

ld部分,由bootmain.o和bootasm.o来生成,再通过gcc编译。
有一个0x7c00是偏移量,如果直接对bootasm.o进行调试,地址是不对的,得加上0x7c00这个偏移量

⑦ 通过man查看,如看ld则输入man ld,回车即可

⑧ Makefile文件内容如下:

PROJ := challenge
EMPTY   :=
SPACE   := $(EMPTY) $(EMPTY)
SLASH   := /
##make “V=”可输出make执行的命令
V       := @
#need llvm/cang-3.5+
#USELLVM := 1
##选择交叉编译器检查GCCPREFIX
# try to infer the correct GCCPREFX
ifndef GCCPREFIX
GCCPREFIX := $(shell if i386-elf-objdump -i 2>&1 | grep '^elf32-i386$$' >/dev/null 2>&1; \then echo 'i386-elf-'; \elif objdump -i 2>&1 | grep 'elf32-i386' >/dev/null 2>&1; \then echo ''; \else echo "***" 1>&2; \echo "*** Error: Couldn't find an i386-elf version of GCC/binutils." 1>&2; \echo "*** Is the directory with i386-elf-gcc in your PATH?" 1>&2; \echo "*** If your i386-elf toolchain is installed with a command" 1>&2; \echo "*** prefix other than 'i386-elf-', set your GCCPREFIX" 1>&2; \echo "*** environment variable to that prefix and run 'make' again." 1>&2; \echo "*** To turn off this error, run 'gmake GCCPREFIX= ...'." 1>&2; \echo "***" 1>&2; exit 1; fi)
endif
##设置QEMU
# try to infer the correct QEMU
ifndef QEMU
QEMU := $(shell if which qemu-system-i386 > /dev/null; \then echo 'qemu-system-i386'; exit; \elif which i386-elf-qemu > /dev/null; \then echo 'i386-elf-qemu'; exit; \elif which qemu > /dev/null; \then echo 'qemu'; exit; \else \echo "***" 1>&2; \echo "*** Error: Couldn't find a working QEMU executable." 1>&2; \echo "*** Is the directory containing the qemu binary in your PATH" 1>&2; \echo "***" 1>&2; exit 1; fi)
endif# eliminate default suffix rules
.SUFFIXES: .c .S .h##如果遇到error或者被中断了就删除所有目标文件
# delete target files if there is an error (or make is interrupted)
.DELETE_ON_ERROR:##设置编译器选项
# define compiler and flags
ifndef  USELLVM
##gcc编译,-g为了gdb调试,-Wall生成警告信息,-02优化处理级别
HOSTCC      := gcc
HOSTCFLAGS  := -g -Wall -O2
CC      := $(GCCPREFIX)gcc
##-fno-builtin不使用C语言的内建函数,-ggdb为GDB生成更丰富的调试信息,-m32用32位编译,-gstabs生成stabs格式调试信息但不包括GDB调试信息,-nostdinc不在系统默认头文件目录中寻找文件,$(DEFS)未定义可用来扩展信息
CFLAGS  := -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc $(DEFS)
##$(shell)可以输出shell指令,-fno-stack-protector禁用堆栈保护,-E仅预处理不进行编译汇编链接可以提高速度,-x c指明C语言
##/dev/null指定目标文件,>/dev/null 2>&1标准错误重定向到标准输出,&&先运行前一句,若成功再运行后一句
##意为只预处理,所有出错全部作为垃圾(/dev/null类似垃圾文件)测试能否开启-fno-stack-protector,若能则CFLAGS += -fno-stack-protector
CFLAGS  += $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector)
else
##若使用clang,类似处理
HOSTCC      := clang
HOSTCFLAGS  := -g -Wall -O2
CC      := clang
CFLAGS  := -fno-builtin -Wall -g -m32 -mno-sse -nostdinc $(DEFS)
CFLAGS  += $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector)
endif##源文件类型为.c和.s
CTYPE   := c SLD      := $(GCCPREFIX)ld
##shell中命令shell中命令 ld -V可以输出支持的版本,|管道将前者的输出作为后者的输入,grep在输入中搜索elf_i386字串,找到就输出elf_i386##意味如果支持elf_i386则LDFLAGS := -m elf_i386
LDFLAGS := -m $(shell $(LD) -V | grep elf_i386 2>/dev/null)
##-nostdlib不连接系统标准库文件
LDFLAGS += -nostdlibOBJCOPY := $(GCCPREFIX)objcopy
OBJDUMP := $(GCCPREFIX)objdump##定义一些shell命令
COPY    := cp
MKDIR   := mkdir -p
MV      := mv
RM      := rm -f
AWK     := awk
SED     := sed
SH      := sh
TR      := tr
TOUCH   := touch -cOBJDIR  := obj
BINDIR  := binALLOBJS  :=
ALLDEPS :=
TARGETS :=##在function.mk中定义了大量辅助函数,部分说明参考了引用中的博文
include tools/function.mk##call:call func,变量1,变量2,...
##listf:列出某地址下某类型的文件
##listf_cc:列出变量1下的.c与.S文件
listf_cc = $(call listf,$(1),$(CTYPE))# for cc
##将文件打包
add_files_cc = $(call add_files,$(1),$(CC),$(CFLAGS) $(3),$(2),$(4))
##创建目标文件包
create_target_cc = $(call create_target,$(1),$(2),$(3),$(CC),$(CFLAGS))# for hostcc
add_files_host = $(call add_files,$(1),$(HOSTCC),$(HOSTCFLAGS),$(2),$(3))
create_target_host = $(call create_target,$(1),$(2),$(3),$(HOSTCC),$(HOSTCFLAGS))##patsubst替换通配符
##cgtype(filenames,type1,type2)把文件名中type1的改为type2,如.c改为.o
cgtype = $(patsubst %.$(2),%.$(3),$(1))
##列出所有目标文件,并按规则改后缀名
objfile = $(call toobj,$(1))
asmfile = $(call cgtype,$(call toobj,$(1)),o,asm)
outfile = $(call cgtype,$(call toobj,$(1)),o,out)
symfile = $(call cgtype,$(call toobj,$(1)),o,sym)# for match pattern
match = $(shell echo $(2) | $(AWK) '{for(i=1;i<=NF;i++){if(match("$(1)","^"$$(i)"$$")){exit 1;}}}'; echo $$?)# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# include kernel/userINCLUDE    += libs/CFLAGS    += $(addprefix -I,$(INCLUDE))LIBDIR   += libs$(call add_files_cc,$(call listf_cc,$(LIBDIR)),libs,)# -------------------------------------------------------------------
# kernelKINCLUDE    += kern/debug/ \kern/driver/ \kern/trap/ \kern/mm/KSRCDIR     += kern/init \kern/libs \kern/debug \kern/driver \kern/trap \kern/mmKCFLAGS       += $(addprefix -I,$(KINCLUDE))$(call add_files_cc,$(call listf_cc,$(KSRCDIR)),kernel,$(KCFLAGS))KOBJS = $(call read_packet,kernel libs)# create kernel target
##将所有文件链接生成kernel
kernel = $(call totarget,kernel)$(kernel): tools/kernel.ld$(kernel): $(KOBJS)@echo + ld $@$(V)$(LD) $(LDFLAGS) -T tools/kernel.ld -o $@ $(KOBJS)@$(OBJDUMP) -S $@ > $(call asmfile,kernel)@$(OBJDUMP) -t $@ | $(SED) '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $(call symfile,kernel)$(call create_target,kernel)# -------------------------------------------------------------------# create bootblock
##将所有文件链接生成bootblock
bootfiles = $(call listf_cc,boot)
$(foreach f,$(bootfiles),$(call cc_compile,$(f),$(CC),$(CFLAGS) -Os -nostdinc))bootblock = $(call totarget,bootblock)$(bootblock): $(call toobj,$(bootfiles)) | $(call totarget,sign)@echo + ld $@$(V)$(LD) $(LDFLAGS) -N -e start -Ttext 0x7C00 $^ -o $(call toobj,bootblock)@$(OBJDUMP) -S $(call objfile,bootblock) > $(call asmfile,bootblock)@$(OBJCOPY) -S -O binary $(call objfile,bootblock) $(call outfile,bootblock)@$(call totarget,sign) $(call outfile,bootblock) $(bootblock)$(call create_target,bootblock)# -------------------------------------------------------------------# create 'sign' tools
##生成sign辅助工具
$(call add_files_host,tools/sign.c,sign,sign)
$(call create_target_host,sign,sign)# -------------------------------------------------------------------##生成ucore.img
# create ucore.img
UCOREIMG    := $(call totarget,ucore.img)$(UCOREIMG): $(kernel) $(bootblock)$(V)dd if=/dev/zero of=$@ count=10000$(V)dd if=$(bootblock) of=$@ conv=notrunc$(V)dd if=$(kernel) of=$@ seek=1 conv=notrunc$(call create_target,ucore.img)# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>$(call finish_all)IGNORE_ALLDEPS  = clean \dist-clean \grade \touch \print-.+ \handinifeq ($(call match,$(MAKECMDGOALS),$(IGNORE_ALLDEPS)),0)
-include $(ALLDEPS)
endif# files for grade scriptTARGETS: $(TARGETS).DEFAULT_GOAL := TARGETS.PHONY: qemu qemu-nox debug debug-nox
qemu-mon: $(UCOREIMG)$(V)$(QEMU)  -no-reboot -monitor stdio -hda $< -serial null
qemu: $(UCOREIMG)$(V)$(QEMU) -no-reboot -parallel stdio -hda $< -serial null
log: $(UCOREIMG)$(V)$(QEMU) -no-reboot -d int,cpu_reset  -D q.log -parallel stdio -hda $< -serial null
qemu-nox: $(UCOREIMG)$(V)$(QEMU)   -no-reboot -serial mon:stdio -hda $< -nographic
TERMINAL        :=gnome-terminal
debug: $(UCOREIMG)$(V)$(QEMU) -S -s -parallel stdio -hda $< -serial null &$(V)sleep 2$(V)$(TERMINAL) -e "gdb -q -tui -x tools/gdbinit"debug-nox: $(UCOREIMG)$(V)$(QEMU) -S -s -serial mon:stdio -hda $< -nographic &$(V)sleep 2$(V)$(TERMINAL) -e "gdb -q -x tools/gdbinit".PHONY: grade touchGRADE_GDB_IN    := .gdb.in
GRADE_QEMU_OUT  := .qemu.out
HANDIN          := proj$(PROJ)-handin.tar.gzTOUCH_FILES        := kern/trap/trap.cMAKEOPTS        := --quiet --no-print-directorygrade:$(V)$(MAKE) $(MAKEOPTS) clean$(V)$(SH) tools/grade.shtouch:$(V)$(foreach f,$(TOUCH_FILES),$(TOUCH) $(f))print-%:@echo $($(shell echo $(patsubst print-%,%,$@) | $(TR) [a-z] [A-Z])).PHONY: clean dist-clean handin packall tags
clean:$(V)$(RM) $(GRADE_GDB_IN) $(GRADE_QEMU_OUT) cscope* tags-$(RM) -r $(OBJDIR) $(BINDIR)dist-clean: clean-$(RM) $(HANDIN)handin: packall@echo Please visit http://learn.tsinghua.edu.cn and upload $(HANDIN). Thanks!packall: clean@$(RM) -f $(HANDIN)@tar -czf $(HANDIN) `find . -type f -o -type d | grep -v '^\.*$$' | grep -vF '$(HANDIN)'`tags:@echo TAGS ALL$(V)rm -f cscope.files cscope.in.out cscope.out cscope.po.out tags$(V)find . -type f -name "*.[chS]" >cscope.files$(V)cscope -bq $(V)ctags -L cscope.files

⑨ 总结:
根据生成ucore的代码可知:
首先要生成一个有10000个块的文件,每个块默认512字节,用0填充dd if=/dev/zero of=bin/ucore.img count=10000。
接着把bootblock中的内容写到第一个块
dd if=bin/bootblock of=bin/ucore.img conv=notrunc
从第二个块开始写kernel中的内容
dd if=bin/kernel of=bin/ucore.img seek=1 conv=notrunc
dd的一些参数的含义:
-if表示输入文件,如果不指定,那么会默认从stdin中读取输入
-of表示输出文件,如果不指定,那么会stdout
bs表示以字节为单位的块大小
count表示被赋值的块数
/dev/zero是一个字符设备,会不断返回0值字节\0
conv = notrunc 不截短输出文件
seek=blocks 从输出文件开头跳过blocks个块后再开始复制
这样ucore.img就一步一步的生成了。

1.2一个被系统认为是符合规范的硬盘主引导扇区的特征是什么?
答:
磁盘主引导扇区只有512字节
磁盘最后两个字节为0x55AA
由不超过466字节的启动代码和不超过64字节的硬盘分区表加上两个字节的结束符组成
在tools/sign.c中有相应代码用来检测是否符合要求。执行指令less tools/sign.c,查看代码段,截图如下:


2.练习2:使用qemu执行并调试lab1中的软件
① 输入qemu-system-i386 -S -s -hda bin/ucore.img,进入qemu界面


② 输入gdb,开始调试。
③ 再输入target remote:1234连接qemu

④ 输入file bin/指定要调试的文件,如file obj/bootblock.o,输入y进入对应的.o文件进行调试。输入b *0x7c00,在0x7c00处设置断点,再输入c,继续调试。再输入layout asm,进入调试框界面。

⑤ 通过layout split将窗口分为三部分,分别为代码、汇编代码、命令窗口。

⑥ 命令窗口输入si,进入下一条指令;b bootasm.S:51在文件第51行加入断点。

⑦ counting,再输入info registers查看寄存器值

⑧ 输入info registers cr0查看cr0里的值
⑨ 从汇编代码可以看出,cr0的值先放入eax中,再由eax值加1后返回给cr0。通过调试检查是否如此

⑩ 其他一些调试指令:
b bootmain:在bootmain函数处设置断点
bt:查看当前函数调用情况
p:print
s:单步执行
n:进入下一行代码

3.练习3:分析bootloader进入保护模式的过程
① Bootasm.S部分源码分析




② 总结:
先设置寄存器ax,ds,es,ss寄存器值为0;地址线20被封锁,高于1MB的地址默认回卷到0。由于历史原因,A20地址位由键盘控制器芯片8042管理,所有激活A20要给8042发命令激活。
8042有两个I/O端口:0x60和0x64,激活流程位:发送0xd1命令到0x64端口后,发送0xdf到0x60
从实模式转换到保护模式:用到了全局描述符和段表,使得虚拟地址和物理地址匹配,保证转换时有效的内存映射不改变;lgdt汇编指令把GDTR描述符的大小和起始位置存入gdtr寄存器中;将cr0的最后一位设置为1,进入保护模式;指令跳转由代码段跳到protcseg的起始位置。

③ 问题:
为何开启A20,以及如何开区A20
答:
为了兼顾早期的PC机,第20根地址线在实模式下不能使用,超过1MB的地址会默认返回到地址0,所以需要开启A20.
通过将键盘控制器上的A20线置于高电位,全部32条地址线可用,可以访问4G的内存空间。

如何初始化GDT表
答:一个简单的GDT表和其描述符已经静态存储在引导区中,载入lgdt gdtdesc即可初始化GDT表

如何使能和进入保护模式
答:通过将cr0寄存器最低位置1,开启保护模式

4.练习4:分析bootloader加载ELF格式的OS的过程
4.1bootloader如何读取硬盘扇区的?
① 打开bootmain,在bootmain中第一句就是读取硬盘扇区,截图如下:

② 大致流程:等待磁盘准备好->发出读取扇区的命令->等待磁盘准备好->把磁盘扇区数据读取到指定内存
③ readsect从设备的第secno扇区读取数据到dst位置

I/O地址 功能
0x1f0 读数据,当0x1f7不为忙状态时,可以读。
0x1f2 要读写的扇区数,每次读写前,你需要表明你要读写几个扇区。最小是1个扇区
0x1f3 如果是LBA模式,就是LBA参数的0-7位
0x1f4 如果是LBA模式,就是LBA参数的8-15位
0x1f5 如果是LBA模式,就是LBA参数的16-23位
0x1f6 第0~3位:如果是LBA模式就是24-27位 第4位:为0主盘;为1从盘
0x1f7 状态和命令寄存器。操作时先给命令,再读取,如果不是忙状态就从0x1f0端口读数据

4.2bootloader是如何加载ELF格式的OS?
1.从硬盘读了8个扇区数据到内存0x10000处,并把这里强制转换成elfhdr使用;
2.校验e_magic字段;
3.根据偏移量分别把程序段的数据读取到内存中。

5.实现函数调用堆栈跟踪函数(需要编程)
① 函数堆栈的基本原理
一个函数调用动作可分解为:零到多个PUSH指令(用于参数入栈),一个CALL指令。CALL指令内部其实还暗含了一个将返回地址(即CALL指令下一条指令的地址)压栈的动作(由硬件完成)。
几乎所有本地编译器都会在每个函数体之前插入类似pushl %ebp 和movl %esp , %ebp的指令。这样在程序执行到一个函数的实际指令前,已经有以下数据顺序入栈:参数、返回地址、ebp寄存器。
基本栈结构如下图:

一般而言,ss:[ebp+4]处为返回地址,ss:[ebp+8]处为第一个参数值(最后一个入栈的参数值,此处假设其占用4字节内存),ss:[ebp-4]处为第一个局部变量,ss:[ebp]处为上一层ebp值。由于ebp中的地址处总是“上一层函数调用时的ebp值”,而在每一层函数调用中,都能通过当时的ebp值“向上(栈底方向)”能获取返回地址、参数值,“向下(栈顶方向)”能获取函数局部变量值。如此形成递归,直至到达栈底。这就是函数调用栈。

② 进入lab1/kern/debug目录下的kdebug.c,实现print_stackframe函数。函数实现如下图:

③ 在lab1下执行make qemu,在qemu模拟器中得到结果,符合预期。

6.完善中断初始化和处理(需要编程)
6.1中断描述符表(也可简称为保护模式下的中断向量表)中一个表项占多少字节?其中哪几位代表中断处理代码的入口?
答:
中断描述符表中,一个表项占8字节,其中015位和4863为分别为offset的低16位和高16位。16~31位为段选择子。通过段选择子获得段基址,加上段内偏移量即可获得到中断处理代码的路口。


6.2请编程完善kern/trap/trap.c中对中断向量表进行初始化的函数idt_init。在idt_init函数中,依次对所有中断入口进行初始化。使用mmu.h中的SETGATE宏,填充idt数组内容。每个中断的入口由tools/vectors.c生成,使用trap.c中声明的vectors数组即可。
① 编程完善kern/trap/trap.c中的函数idt_init,实现如下:

② 利用了SETGATE宏,在mmu.h中查看这个宏

gate:为相应的idt数组内容,处理函数的入口地址
istrap:系统段设置为1,中断门设置为0
sel:段选择子,这里是GO-KTEXT
#define GD_KTEXT ((SEG_KTEXT)<<3) //kernel text
off:为_vectors数字内容,存在vectors.s中,支持256个中断
dpl:设置优先级,0为内核级,3为用户级

6.3请编程完善trap.c中的中断处理函数trap,在对时钟中断进行处理的部分填写trap函数中处理时钟中断的部分,使操作系统每遇到100次时钟中断后,调用print_ticks子程序,向屏幕上打印一行文字”100 ticks”。
编程完善trap.c中的中断处理函数trap_dispatch,实现如下:

2)运行结果如下,实现中断。

ucore Lab1 系统软件启动过程相关推荐

  1. 清华操作系统实验课程实验一:系统软件启动过程

    ucore_lab1_练习一 (本示例中,如果输入行前面出现美元符号($),表示这是命令行提示符.紧接其后的是命令行,可以通过控制台输入命令) 理解通过make生成执行文件的过程 通过静态分析代码来了 ...

  2. 操作系统实验—ucore Lab1

    一.内容 通过 Lab1 中的 bootloader 可以从实模式切换的保护模式,然后再读取磁盘并加载 ELF 文件以加载 OS 操作系统,操作系统能够读入字符并显示到屏幕上,具体内容如下: 练习 1 ...

  3. 操作系统 ucore lab1

    操作系统 ucore lab1 实验目的 操作系统是一个软件,也需要通过某种机制加载并运行它.在这里我们将通过另外一个更加简单的软件-bootloader来完成这些工作.为此,我们需要完成一个能够切换 ...

  4. ucore - 第一章 - bootloader启动过程

    目录 1.概述 2.引用 3.关键字 4.cup上电过程 5.bootloader 5.1.关闭中断.初始化段寄存器 5.2.开启A20总线 5.3.初始化GDT表 5.3.1.实模式和保护模式 5. ...

  5. cisco路由器启动过程

    巧改启动方式修复路由器故障 作者:李夏艳 在Windows操作系统中,管理员在启动时可以按F8键,选择不同的启动方式来修复故障.如以VGA模式进入解决分辨率不匹配的问题等等.其实在思科的路由器中也有类 ...

  6. 《ucore lab1 练习5》实验报告

    [练习5]实现函数调用堆栈跟踪函数 我们需要在lab1中完成kdebug.c中函数print_stackframe的实现,可以通过函数print_stackframe来跟踪函数调用堆栈中记录的返回地址 ...

  7. cisco 交换机和路由器启动过程

    交换机是OSI第二层上的设备,而路由器是第三层上的设备:它们的启动过程却是大致相同的.因为每台设备之间它们的型号.大小等特性不一样,导致启动过程也有些差别: 不管是路由器还是交换机当它们加载完IOS后 ...

  8. ucore lab1 实验报告

    UCORE LAB1 实验报告 练习一 理解通过make生成执行文件的过程 1.操作系统镜像文件ucore.img是如何一步一步生成的? 先打开lab1文件夹下的Makefile,查看里面的代码,在各 ...

  9. Android系统的启动过程

    Android系统的启动过程可以简单地总结为以下几个流程: 加载BootLoader -> 初始化内核 -> 启动init进程 -> init进程fork出Zygote(孵化器)进程 ...

  10. Android系统默认Home应用程序(Launcher)的启动过程源代码分析

    在前面一篇文章中,我们分析了Android系统在启动时安装应用程序的过程,这些应用程序安装好之后,还需要有一个Home应用程序来负责把它们在桌面上展示出来,在Android系统中,这个默认的Home应 ...

最新文章

  1. ecshop 搜索热词推荐_多多搜索自定义关键词推广的基础点:如何选对致命的关键词...
  2. 计算机网络基础知识,仅此一篇足矣
  3. apache spark_Apache Spark:更改架构之前必须解决的5个陷阱
  4. linux c 封装redis,封装hiredis——C++与redis对接(一)(string的SET与GET操作)
  5. 切换至 root 身份
  6. CHM文件出现的常见错误及解决办法
  7. 用ajax获取淘宝关键字接口
  8. 【一天一个C++小知识】009.C++面向对象
  9. oracle数据库服务器c盘满,Oracle数据库服务器磁盘满导致数据库无法登陆,通过清理归档文件解决...
  10. Flutter 实现 仿Android原生启动模式SingleTask
  11. 外贸员需要知道的那些事儿
  12. 目前文字识别技术,主要应用在哪些场景?
  13. 爬取新浪微博热搜排行
  14. 天龙八部荣耀版体验服服务器未响应,《天龙八部荣耀版》创新竖版手游官网-合区来了!体验服合区测试解析...
  15. 怎么删除github项目/仓库中已经上传的代码
  16. Vagrant 介绍
  17. 我的2020|有风有雨亦有晴
  18. 高斯型随机粗糙面MATLAB仿真
  19. 容器性能测验 调研报告
  20. pytorch:实例讲解DataLoader具体工作流程

热门文章

  1. 微信公众号开发测试平台地址
  2. Android好用的音乐,安卓手机音乐播放器哪个好用?十大最好音乐播放器介绍
  3. 大学计算机信息技术教程电子书资源,教与学中用好教材《大学计算机信息技术教程》.pdf...
  4. AI中台——智能聊天机器人平台的架构与应用(分享实录)
  5. 使用opencv进行车牌提取及识别
  6. IDEA 设置类注释模板
  7. 微软永恒之蓝ms17010补丁下载-wannacry
  8. Chrome 自动播放m3u8
  9. 【ESP32 Arduino平衡小车制作】(一)霍尔编码器解码
  10. ocr文字识别html,在线OCR 随时随地轻松搞定文字识别