蒋迪的博客
| 关于 | 归档

#Android Android 开发笔记

Android 与 Linux 区别

在说明Android与Linux区别之前,先理解一下Linux指代什么,通常Linux的定义通常指代两个东西:

  • Linux内核(GNU/Linux);
  • Linux发行版本(Debian,Ubuntu等);

安卓实际上是基于Linux的2.6内核开发的一个操作系统,在开发安卓系统的过程中,还给Linux内核增加了一些自己特有的功能和驱动:

  1. 独特的内存和进程管理方案;
  2. 基于SeAndroid权限的安全机制;
  3. 支持共享库;

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_initkernel_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)
  1. LOCAL_PATH:=$(call my-dir) 通常Android.mk 是以LOCAL_PATH开始,设置编译的路径为当前文件夹;
  2. include $(CLEAR_VARS) 清空编译环境的变量(此前其他模块可能设置过LOCAL_XX之类的);
  3. LOCAL_MODULE_TAGS := optional 指定在什么版本下编译;
    1. user/debug/eng: 则模块只有在对应版本下才会编译;
    2. optional:则模块在所有版本下都会编译;
  4. LOCAL_MODULE:=hello_jiangdi 模块的名称;
  5. LOCAL_SRC_FILES := $(call all-subdir-c-files) 模块编译所需的C源文件;
  6. 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脚本中在echomkdir命令前面的@符号。
  • makefile foreach 函数: 将后面的变量值逐行读出。

参考资料:

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

参考资料