在Linux内核启动的最后一步中,将调用Start_kernel来初始化配置:
/kernel/init/Main.c
asmlinkage void __init start_kernel(void)
{
...
rest_init();
...
}
start_kernel函数调用一些初始化函数完成初始化工作后,调用rest_init()函数来创建新的进程:
static noinline void __init_refok rest_init(void)
{
int pid;
rcu_scheduler_starting();
/* 创建内核线程kernel_init,该线程最终会启动用户空间的init进程 */
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
numa_default_policy();
/* 创建一个kthreadd内核线程,用于创建新的内核进程 */
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);
preempt_enable_no_resched();
schedule();
preempt_disable();
/* Call into cpu_idle with preempt disabled */
cpu_idle();
内核加载结束时会启动init进程:
static int __init kernel_init(void * unused)
{
/*
* Wait until kthreadd is all set-up.
*/
wait_for_completion(&kthreadd_done);
/* Now the scheduler is fully set up and can do blocking allocations */
gfp_allowed_mask = __GFP_BITS_MASK;
/*
* init can allocate pages on any node
*/
set_mems_allowed(node_states[N_HIGH_MEMORY]);
/*
* init can run on any cpu.
*/
set_cpus_allowed_ptr(current, cpu_all_mask);
cad_pid = task_pid(current);
smp_prepare_cpus(setup_max_cpus);
do_pre_smp_initcalls();
lockup_detector_init();
smp_init();
sched_init_smp();
do_basic_setup();
/* Open the /dev/console on the rootfs, this should never fail */
if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
printk(KERN_WARNING "Warning: unable to open an initial console.\n");
(void) sys_dup(0);
(void) sys_dup(0);
if (!ramdisk_execute_command)
ramdisk_execute_command = "/init";
/* 判断系统中是否存在/init的命令,如果不存在的话,会将ramdisk_execute_command置为NUll 并且会去继续寻找,检查是否有没有挂载的文件系统,initrd就是在这个函数中被挂载的 */
if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
ramdisk_execute_command = NULL;
prepare_namespace();
}
init_post();
return 0;
}
init_post()如下
static noinline int init_post(void)
{
...
/* 根据上面设置的ramdisk_execute_command,启动/init进程 */
if (ramdisk_execute_command) {
run_init_process(ramdisk_execute_command);
printk(KERN_WARNING "Failed to execute %s\n",
ramdisk_execute_command);
}
...
}
run_init_process()如下:
static void run_init_process(const char *init_filename)
{
argv_init[0] = init_filename;
/* kernel_execve()来调用用户空间的各种init进程 */
kernel_execve(init_filename, argv_init, envp_init);
}
这样,init进程就启动了
system/core/init目录下的Android.mk会生成init可执行程序,该进程的主要功能如下:
1) 分析init.rc启动脚本文件,根据文件内容执行相应的功能;
2) 当一些关键进程死亡时,重启该进程;
3) 提供Android系统的属性服务;
int main(int argc, char **argv)
{
/* 此时kernel已经成功启动,实体的根文件系统已经挂载好了,shell还没有启动 */
/* 启动ueventd 和 watchdogd 时也需要通过init可执行程序来执行 */
if (!strcmp(basename(argv[0]), "ueventd"))
return ueventd_main(argc, argv);
if (!strcmp(basename(argv[0]), "watchdogd"))
return watchdogd_main(argc, argv);
/* 设置init进程创建文件时的属性,为0表示创建文件的默认属性777 */
umask(0);
/* 挂载tmpfs,devpts,proc,sysfs 4类文件系统 */
mkdir("/dev", 0755);
mkdir("/proc", 0755);
mkdir("/sys", 0755);
/* 挂载虚拟文件系统 */
mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
mkdir("/dev/pts", 0755);
mkdir("/dev/socket", 0755);
mount("devpts", "/dev/pts", "devpts", 0, NULL);
mount("proc", "/proc", "proc", 0, NULL);
mount("sysfs", "/sys", "sysfs", 0, NULL);
/* 因为此时shell还没有启动,所以标准输入输出都不能进行,在这里就直接屏蔽了,即标准的输入输出定向到NULL设备 */
open_devnull_stdio();
/* log初始化,将init启动时的log重定向到/dev/kmsg,可以从/proc/kmsg读取init进程启动时的log */
klog_init();
/* 属性存储空间初始化 */
property_init();
/* 读取机器硬件名称 */
get_hardware_name(hardware, &revision);
/* 从/proc/cmdline中读取内核参数,并将androidboot开头的部分添加到属性空间中 */
process_kernel_cmdline();
}
在android平台中,为了让所有的进程共享系统运行是所需要的各种设置值,系统开辟了属性存储区域,并提供访问该区域的API。属性由键(key)和值(value)构成,其表现形式是“键=值”。系统中所有进程都可以访问属性值,但只有init进程才可以修改属性值。在此过程中,init进程首先会检查各属性的访问权限,然后再修改属性值,当属性值修改之后,如果定义在init.rc中的某个特定条件得到满足,则与此条件相匹配的动作就会发生,
static int init_property_area(void)
{
if (property_area_inited)
return -1;
/* 初始化工作空间,大小为32768
* 创建文件/dev/__properties__,设置大小为128K
*/
if(__system_property_area_init())
return -1;
/* pa_workspace.size = 0
* pa_workspace.fd = open(/dev/__properties__);
*/
if(init_workspace(&pa_workspace, 0))
return -1;
/* 设置工作空间句柄fd的属性值
* 由于调用exec函数时,函数不会返回,因此文件描述符就没有机会被关闭了,
* FD_CLOEXEC这个属性的设置会使得文件描述符在调用exec函数结束后自动关闭
*/
fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC);
property_area_inited = 1;
return 0;
}
本Markdown编辑器使用StackEdit修改而来,用它写博客,将会带来全新的体验哦:
Ctrl + B
Ctrl + I
Ctrl + Q
Ctrl + L
Ctrl + K
Ctrl + G
Ctrl + H
Ctrl + O
Ctrl + U
Ctrl + R
Ctrl + Z
Ctrl + Y
Markdown 是一种轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档,然后转换成格式丰富的HTML页面。 —— [ 维基百科 ]
使用简单的符号标识不同的标题,将某些文字标记为粗体或者斜体,创建一个链接等,详细语法参考帮助?。
本编辑器支持 Markdown Extra , 扩展了很多好用的功能。具体请参考Github.
Markdown Extra 表格语法:
项目 | 价格 |
---|---|
Computer | $1600 |
Phone | $12 |
Pipe | $1 |
可以使用冒号来定义对齐方式:
项目 | 价格 | 数量 |
---|---|---|
Computer | 1600 元 | 5 |
Phone | 12 元 | 12 |
Pipe | 1 元 | 234 |
定义 D
定义D内容
代码块语法遵循标准markdown代码,例如:
@requires_authorization
def somefunc(param1='', param2=0):
'''A docstring'''
if param1 > param2: # interesting
print 'Greater'
return (param2 - param1 + 1) or None
class SomeClass:
pass
>>> message = '''interpreter
... prompt'''
生成一个脚注1.
用 [TOC]
来生成目录:
使用MathJax渲染LaTex 数学公式,详见math.stackexchange.com.
更多LaTex语法请参考 这儿.
可以渲染序列图:
或者流程图:
即使用户在没有网络的情况下,也可以通过本编辑器离线写博客(直接在曾经使用过的浏览器中输入write.blog.csdn.net/mdeditor即可。Markdown编辑器使用浏览器离线存储将内容保存在本地。
用户写博客的过程中,内容实时保存在浏览器缓存中,在用户关闭浏览器或者其它异常情况下,内容不会丢失。用户再次打开浏览器时,会显示上次用户正在编辑的没有发表的内容。
博客发表后,本地缓存将被删除。
用户可以选择 把正在写的博客保存到服务器草稿箱,即使换浏览器或者清除缓存,内容也不会丢失。
注意:虽然浏览器存储大部分时候都比较可靠,但为了您的数据安全,在联网后,请务必及时发表或者保存到服务器草稿箱。