Android 与 Linux 区别
在说明Android与Linux区别之前,先理解一下Linux指代什么,通常Linux的定义通常指代两个东西:
- Linux内核(GNU/Linux);
- Linux发行版本(Debian,Ubuntu等);
安卓实际上是基于Linux的2.6内核开发的一个操作系统,在开发安卓系统的过程中,还给Linux内核增加了一些自己特有的功能和驱动:
- 独特的内存和进程管理方案;
- 基于SeAndroid权限的安全机制;
- 支持共享库;
Android核心驱动主要包括:
- Android Binder:提供Android平台间的进程通信;
- Android电源管理:基于标准Linux电源管理系统的电源管理驱动;
- Android 低内存管理:Low Memory Killer 根据需要杀死进程来释放需要的内存(比Linux的out of memory更加灵活);
- Android 匿名共享内存:内核内存管理和回收;
- Android PMEM:向用户空间提供连续的物理内存;
- Android Logger:用于抓取安卓系统的日志;
- Android Alarm:定时器;
- USB Gadeget:USB驱动;
- 等等;
此外,安卓系统开发了自己的Dalvik虚拟据,而不是java虚拟机;
所以综上我们可以得出,Android系统是基于Linux,Linux提供核心服务:安全,内存管理,进程管理,网络和驱动模型等;Android系统则基于移动设备(嵌入式设备)的需求,在文件系统,内存管理,进程管理通信和电源管理上做了优化和修改;
Android 启动流程
从uboot跳转执行到Linux内核后,入口位于head.S
中,具体源码如下所示:
代码目录\kernel\linux-3.10.y\arch\arm64\kernel\head.S
/*
* The following fragment of code is executed with the MMU on in MMU mode, and
* uses absolute addresses; this is not position independent.
*/
__mmap_switched:
adr x3, __switch_data + 8
ldp x6, x7, [x3], #16
1: cmp x6, x7
b.hs 2f
str xzr, [x6], #8 // Clear BSS
b 1b
2:
ldp x4, x5, [x3], #16
ldr x6, [x3], #8
ldr x16, [x3]
mov sp, x16
str x22, [x4] // Save processor ID
str x21, [x5] // Save FDT pointer
str x24, [x6] // Save PHYS_OFFSET
mov x29, #0
b start_kernel
ENDPROC(__mmap_switched)
在最后一步b start_kernel
跳转到 start_kernel
处(内核C代码的入口)。
start_kernel
定义在kernel\linux-3.10.y\init\main.c
中,
asmlinkage void __init start_kernel(void)
{
char * command_line;
extern const struct kernel_param __start___param[], __stop___param[];
/*
* Need to run as early as possible, to initialize the
* lockdep hash:
*/
lockdep_init();
smp_setup_processor_id();
debug_objects_early_init();
/*
* Set up the the initial canary ASAP:
*/
boot_init_stack_canary();
cgroup_init_early();
local_irq_disable();
early_boot_irqs_disabled = true;
/*
* Interrupts are still disabled. Do necessary setups, then
* enable them
*/
boot_cpu_init();
page_address_init();
pr_notice("%s", linux_banner);
setup_arch(&command_line);
mm_init_owner(&init_mm, &init_task);
mm_init_cpumask(&init_mm);
setup_command_line(command_line);
setup_nr_cpu_ids();
setup_per_cpu_areas();
smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */
build_all_zonelists(NULL, NULL);
page_alloc_init();
pr_notice("Kernel command line: %s\n", boot_command_line);
parse_early_param();
parse_args("Booting kernel", static_command_line, __start___param,
__stop___param - __start___param,
-1, -1, &unknown_bootoption);
jump_label_init();
/*
* These use large bootmem allocations and must precede
* kmem_cache_init()
*/
setup_log_buf(0);
pidhash_init();
vfs_caches_init_early();
sort_main_extable();
trap_init();
mm_init();
#ifdef CONFIG_LEADCORE_IOTRACE
iotrace_init();
#endif
/*
* Set up the scheduler prior starting any interrupts (such as the
* timer interrupt). Full topology setup happens at smp_init()
* time - but meanwhile we still have a functioning scheduler.
*/
sched_init();
/*
* Disable preemption - early bootup scheduling is extremely
* fragile until we cpu_idle() for the first time.
*/
preempt_disable();
if (WARN(!irqs_disabled(), "Interrupts were enabled *very* early, fixing it\n"))
local_irq_disable();
idr_init_cache();
perf_event_init();
rcu_init();
tick_nohz_init();
radix_tree_init();
/* init some links before init_ISA_irqs() */
early_irq_init();
init_IRQ();
tick_init();
init_timers();
hrtimers_init();
softirq_init();
timekeeping_init();
time_init();
profile_init();
call_function_init();
WARN(!irqs_disabled(), "Interrupts were enabled early\n");
early_boot_irqs_disabled = false;
local_irq_enable();
kmem_cache_init_late();
/*
* HACK ALERT! This is early. We're enabling the console before
* we've done PCI setups etc, and console_init() must be aware of
* this. But we do want output early, in case something goes wrong.
*/
console_init();
if (panic_later)
panic(panic_later, panic_param);
lockdep_info();
/*
* Need to run this when irqs are enabled, because it wants
* to self-test [hard/soft]-irqs on/off lock inversion bugs
* too:
*/
locking_selftest();
#ifdef CONFIG_BLK_DEV_INITRD
if (initrd_start && !initrd_below_start_ok &&
page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {
pr_crit("initrd overwritten (0x%08lx < 0x%08lx) - disabling it.\n",
page_to_pfn(virt_to_page((void *)initrd_start)),
min_low_pfn);
initrd_start = 0;
}
#endif
page_cgroup_init();
debug_objects_mem_init();
kmemleak_init();
setup_per_cpu_pageset();
numa_policy_init();
if (late_time_init)
late_time_init();
sched_clock_init();
calibrate_delay();
pidmap_init();
anon_vma_init();
#ifdef CONFIG_X86
if (efi_enabled(EFI_RUNTIME_SERVICES))
efi_enter_virtual_mode();
#endif
#ifdef CONFIG_X86_ESPFIX64
/* Should be run before the first non-init thread is created */
init_espfix_bsp();
#endif
thread_info_cache_init();
cred_init();
fork_init(totalram_pages);
proc_caches_init();
buffer_init();
key_init();
security_init();
dbg_late_init();
vfs_caches_init(totalram_pages);
signals_init();
/* rootfs populating might need page-writeback */
page_writeback_init();
#ifdef CONFIG_PROC_FS
proc_root_init();
#endif
cgroup_init();
cpuset_init();
taskstats_init_early();
delayacct_init();
check_bugs();
acpi_early_init(); /* before LAPIC and SMP init */
sfi_init_late();
if (efi_enabled(EFI_RUNTIME_SERVICES)) {
efi_late_init();
efi_free_boot_services();
}
ftrace_init();
/* Do the rest non-__init'ed, we're now alive */
rest_init();
}
在最后一个rest_init()
中,启动了内核线程kernel_init
(kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
)完成内核的初始化启动,
static noinline void __init_refok rest_init(void)
{
int pid;
rcu_scheduler_starting();
/*
* We need to spawn init first so that it obtains pid 1, however
* the init task will end up wanting to create kthreads, which, if
* we schedule it before we create kthreadd, will OOPS.
*/
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
numa_default_policy();
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
rcu_read_lock();
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
rcu_read_unlock();
complete(&kthreadd_done);
/*
* The boot idle thread must execute schedule()
* at least once to get things moving:
*/
init_idle_bootup_task(current);
schedule_preempt_disabled();
/* Call into cpu_idle with preempt disabled */
cpu_startup_entry(CPUHP_ONLINE);
}
在kernel_init
中,会执行很多内核的初始化操作,init
进程也在这个函数的最后一步启动。
static int __ref kernel_init(void *unused)
{
kernel_init_freeable();
/* need to finish all async __init code before freeing the memory */
async_synchronize_full();
free_initmem();
mark_rodata_ro();
system_state = SYSTEM_RUNNING;
numa_default_policy();
flush_delayed_fput();
if (ramdisk_execute_command) {
if (!run_init_process(ramdisk_execute_command))
return 0;
pr_err("Failed to execute %s\n", ramdisk_execute_command);
}
/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
*/
if (execute_command) {
if (!run_init_process(execute_command))
return 0;
pr_err("Failed to execute %s. Attempting defaults...\n",
execute_command);
}
if (!run_init_process("/sbin/init") ||
!run_init_process("/etc/init") ||
!run_init_process("/bin/init") ||
!run_init_process("/bin/sh"))
return 0;
panic("No init found. Try passing init= option to kernel. "
"See Linux Documentation/init.txt for guidance.");
}
init
源码在 system\core\init\init.cpp
中,主要负责两件事:
- 挂载文件系统;
- 解析
init.rc
启动脚本(位于system\core\rootdir\init.rc),其中.rc文件时Android使用的初始化脚本文件,有特定的语法规则;
Android 编译
通常,android 编译依靠如下指令
# 初始化环境变量,并且重新加载
source setenv.sh
# 指定编译的目标设备
lunch
# 更新API
make update-api
# 编译整个系统
make
代码编译方法
- mm 编译当前路径下的模块
- mmm module_path 编译指定路径下的模块
- make module_name 编译指定模块
编译产物
- system.img 一些可执行文件
- ramdisk.img 包含init.rc SePolicy策略文件
- userdata.img 用户及程序相关数据
- boot.img uboot
- kernel.img 内核
Android.mk 浅析
下面是我写的一个简单编译C程序的Android.mk
LOCAL_PATH:=$(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE:=hello_jiangdi
LOCAL_SRC_FILES := $(call all-subdir-c-files)
include $(BUILD_EXECUTABLE)
- LOCAL_PATH:=$(call my-dir) 通常Android.mk 是以LOCAL_PATH开始,设置编译的路径为当前文件夹;
- include $(CLEAR_VARS) 清空编译环境的变量(此前其他模块可能设置过LOCAL_XX之类的);
- LOCAL_MODULE_TAGS := optional 指定在什么版本下编译;
- user/debug/eng: 则模块只有在对应版本下才会编译;
- optional:则模块在所有版本下都会编译;
- LOCAL_MODULE:=hello_jiangdi 模块的名称;
- LOCAL_SRC_FILES := $(call all-subdir-c-files) 模块编译所需的C源文件;
- include $(BUILD_EXECUTABLE) 负责收集自上次调用
include $(CLEAR_VARS)
后的所有LOCAL_XXX信息,并决定编译成什么样的模块;- BUILD_STATIC_LIBRARY: 编译静态库;
- BUILD_SHARED_LIBRARY: 编译为动态库;
- BUILD_EXECUTABLE: 编译为可执行文件;
编译完成后,编译输出文件则存在该目录下./out/target/product/devices_xxx/system/bin/hello_jiangdi
。
- LOCAL_PATH:当前编译路径;
- LOCAL_SRC_FILES:当前编译源码文件;
- LOCAL_MODULE:当前模块名称,具有唯一性;
- LOCAL_PACKAGE_NAME:当前APK应用的名称,具有唯一性;
- LOCAL_C_INCLUES:C/C++所需头文件所在路径;
- LOCAL_STATIC_LIBRARIES:需要的静态链接库
- LOCAL_SHARED_LIBRARIES:需要的动态链接库;
- LOCAL_STATIC_JAVA_LIBRARIES:JAVA静态库;
- LOCAL_JAVA_LIBRARIES:JAVA动态库;
- LOCAL_CERTIFICATE:证书认证;
- LOCAL_MODULE_TAGS:模块标签;
相应的,存在一个快捷的环境变量:
- $(call my-dir) 获取当前文件夹路径
- $(call all-java-files-under) 获取当前路径下所有的java源文件
- $(call all-f-files-under) 获取当前路径下所有的C源文件
- $(call alll-laidl-filed-under)
- $(call all-makefiles-under)
其他编译命令浅析
PRODUCT_COPY_FILES += $(call find-copy-subdir-files,*,devices/common/arm64/jiangdi,/system/etc)
拷贝文件到系统
或者使用devices/common/arm64/jiangdi/test_init:system/etc/jiangdi
Android 分区与文件系统
- boot
- system 存放Android系统框架等相关配置,库,应用程序等;
- recovery
- data 用户数据分区
- cache 系统缓存区域
- …
配置文件相关方式
以xxx.cfg举例说明配置文件的使用方式
在device.mk
中,使用编译命令PRODUCT_COPY_FILES += devices/xxxx/xxx.cfg:system/etc/xxx.cfg
将配置文件拷贝到system
分区。
然后在系统启动配置相关程序后,程序会检查data
分区下是否存在该配置文件。
如若不存在,则从system/etc/xxx.cfg
拷贝一份配置文件到data/etc/xxx.cfg
。
反之则跳过此操作;
相关代码路径:jiangdi\tools\xx-xxx\src\main.c
Android 属性值/配置值
增删改属性值相关文件路径
build\tools\buildinfo.sh
利用echo 命令在编译时生成相关属性值;build\core\version_defaults.mk
版本相关默认属性值位置;build\core\Makefile
利用编译宏PRODUCT_DEFAULT_PROPERTY_OVERRIDES
或者PRODUCT_PROPERTY_OVERRIDES
增加属性值;device\leadcore\common\device.mk
设备相关属性值;
其他补充:
- makefile strip 函数:去除多余空格合并为一个空格;
- makefile collapse-pairs :
- makefile $(hide)意思是不在屏幕回显后台执行。类似shell脚本中在
echo
和mkdir
命令前面的@符号。 - makefile foreach 函数: 将后面的变量值逐行读出。
参考资料:
- property_get/property_set
- Android属性之build.prop生成过程分析
- 【Android】使用persist属性来调用脚本文件
- Android属性系统简介
- Android 系统属性SystemProperty分析
- 02 Android系统之添加系统属性
- Android Build System[二]
- Android编译过程中build.prop文件的生成
Android 安全机制
getenforce #查看SEAndroid是否开启
setenforce 0 # 关闭SEAndroid
Android ADB命令OTA升级
adb root
adb push F:\update.zip /data/update.zip
adb shell
echo "--update_package=/data/update.zip" > /cache/recovery/command
sync
reboot recovery
其他recovery命令
--wipe_data
--wipe_cache
参考资料
- 图解Android和Linux发行版的区别
- Android 系统启动流程简析
- Android系统启动流程分析
- Android启动流程-init进程分析
- Android如何配置init.rc中的开机启动进程(service)
- Android核心服务解析篇(二)——Android源码结构分析
- Android核心服务解析篇(三)——Android系统的启动
- Android核心服务解析篇(一)——下载Android源代码
- How to interrupt Android boot sequence to force it boot to Linux kernel only
- 理解Android编译命令
- 安卓到底是不是Linux
- Android与Linux的区别
- android和linux操作系统的区别
- Android与linux的区别与联系
- Android系统与Linux系统关系
- Android Recovery升级原理
- Android OTA升级原理 – 实现流程(整理一)