《Linux启动过程分析》内核启动init进程

时间:2021-11-28 16:13:17

2.6.35.11为mstar801平台使用内核版本;也为第一次比较系统学习内核使用版本。在此留念!

一、0号进程idle进程启动,这是系统唯一不通过do_fork创建的进程

kernel2.6.35.11/init/main.c

asmlinkage void __init start_kernel(void)  //内核线程,0号进程idle进程
{
......
tick_init();
boot_cpu_init();
page_address_init(); //内存管理相关 kernel2.6.35.11/mm/highmem.c
......
setup_arch(&command_line); //内核解析uboot参数就在这步;内核页表 kernel2.6.35.11/arch/arm/kernel/setup.c
mm_init_owner(&init_mm,&init_task); //kernel2.6.35.11/kernel/fork.c
mm_init_cpumask(&init_mm);
......
page_alloc_init(); //内存页表相关 kernel2.6.35.11/mm/page_alloc.c
......
vfs_caches_init(totalram_pages); //rootfs创建,并将rootfs设置为系统根文件系统、rootfs根目录设置为系统根目录;这部是决定驱动加载关键
......
mm_init(); //构建整个页表 kernel2.6.35.11/kernel/fork.c
sched_init(); //进程管理相关 kernel2.6.35.11/kernel/sched.c
......
init_timers(); //内核定时器 kernel2.6.35.11/kernel/timer.c
hrtimers_init(); //内核定时器 kernel2.6.35.11/kernel/timer.c
......
timekeeping_init();
time_init();
......
page_cgroup_init(); //内核页表相关 kernel2.6.35.11/mm/page_cgroup.c
......
fork_init(totalram_pages); //kernel2.6.35.11/kernel/fork.c
proc_caches_init(); //kernel2.6.35.11/kernel/fork.c
......
signals_init(); //信号量相关 kernel2.6.35.11/kernel/signal.c
......
rest_init();
}

如上对理解流程比较重要的是如下三个函数:

setup_arch(&command_line):完成对uboot传入参数的解析;

vfs_caches_init(totalram_pages):rootfs的建立,并将rootfs设置为系统根文件系统、rootfs根目录为系统根目录;

rest_init():完成驱动及内核静态模块(ramdisk释放到rootfs;如果rootfs没有/init还需要完成磁盘文件系统的挂载及重新设置系统根文件系统)加载;用户进程init的启动。

1.将rootfs挂载至自己的“/”目录,见Linux内核启动之根文件系统挂载分析

rootfs:它不属于任何设备(包括虚拟内存盘设备initrd)的文件系统,既不是flash的文件系统、也不是虚拟内存盘的文件系统。

引入虚拟rootfs的原因如下:

磁盘(包括初始RAM磁盘initrd)文件系统的挂载需要它们的设备文件、即需要它们的驱动被加载;但它们驱动的加载,又必须有要文件系统、以便创建设备节点。这是矛盾的!

所以,rootfs的目的是创建目录树,如创建/dev、以供设备文件的创建。

kernel2.6.35.11/fs/Dcache.c

void __init vfs_caches_init(unsigned long mempages)
{
......
mnt_init();
......
}

kernel2.6.35.11/fs/Namespace.c

void __init mnt_init(void)
{
......
err = sysfs_init(); //注册sysfs至内核
init_rootfs(); //注册虚拟rootfs至内核
init_mount_tree(); //创建rootfs的根目录“/”,并将rootfs挂载至该目录
}
static void __init init_mount_tree(void)
{
......
mnt = do_kern_mount("rootfs", 0, "rootfs", NULL);
......
set_fs_root(current->fs, &root); //设置rootfs为系统的根文件系统,rootfs的根目录为系统的根目录
}

说明:这部结束以后,就可以加载驱动程序了、因为驱动程序需要在根文件系统上创建设备节点。

2.磁盘(包括初始RAM磁盘initrd)文件系统的挂载、重新设置系统根目录和根文件系统以及init、kthreadd进程启动

kernel2.6.35.11/init/main.c

static noinline void __init_refok rest_init(void)
{
......
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND); //内核线程
......
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES); //内核线程
......
init_idle_bootup_task(current);
......
schedule(); //kernel2.6.35.11/kernel/sched.c 进程调度
......
}

二、由内核进程0创建的内核线程装载可执行程序init,成为一个普通进程——1号进程

kernel2.6.35.11/init/main.c

static int __init kernel_init(void * unused)
{
......
do_basic_setup(); //初始化设备驱动,加载静态内核模块;释放ramdisk到rootfs
......
if (!ramdisk_execute_command) ramdisk_execute_command = "/init";
if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
ramdisk_execute_command = NULL;
prepare_namespace(); //加载磁盘文件系统;也即磁盘的文件系统挂载至rootfs的/root目录,并重新设备系统根文件系统和根目录
}
init_post(); //启动init进程
......
}

1.内核静态模块加载、驱动加载等

kernel2.6.35.11/init/main.c

static void __init do_basic_setup(void)
{
cpuset_init_smp();
usermodehelper_init();
init_tmpfs();
driver_init(); //设备驱动初始化 kernel2.6.35.11/drivers/base/init.c
init_irq_proc();
do_ctors();
do_initcalls(); //加载内核模块
}
static void __init do_initcalls(void)
{
......
for (fn = __early_initcall_end; fn < __initcall_end; fn++)
do_one_initcall(*fn);
......
}

在do_initcalls中,是否支持ramdisk取决与kernel2.6.35.11/init/Makefile;以下是释放ramdisk至rootfs:

kernel2.6.35.11/init/initramfs.c

rootfs_initcall(populate_rootfs);
static int __init populate_rootfs(void)
{
char *err = unpack_to_rootfs(__initramfs_start, __initramfs_size); //加压cpio包
......
if (initrd_start) { //uboot将启动ramdisk文件系统拷贝到了initrd_start处
......
err = unpack_to_rootfs((char *)initrd_start,
initrd_end - initrd_start);
......
fd = sys_open((const char __user __force *) "/initrd.image",
O_WRONLY|O_CREAT, 0700);
if (fd >= 0) {
sys_write(fd, (char *)initrd_start,
initrd_end - initrd_start);
sys_close(fd);
free_initrd();
}
......
}
return 0;
}

注意:此时系统的根文件系统还是rootfs,系统根目录还是rootfs的根目录

补充:设备及总线驱动

kernel2.6.35.11/drivers/base/init.c

void __init driver_init(void)
{
/* These are the core pieces */
devtmpfs_init();
devices_init(); //kernel2.6.35.11/drivers/core.c
buses_init(); //kernel2.6.35.11/drivers/bus.c
classes_init();
firmware_init();
hypervisor_init();
/* These are also core pieces, but must come after the
* core core pieces.
*/
platform_bus_init(); //kernel2.6.35.11/drivers/platform.c
system_bus_init(); //kernel2.6.35.11/drivers/sys.c
cpu_dev_init();
memory_dev_init();
}

2.如果系统当前根文件系统也就是rootfs文件系统中没有init,就要挂载其他磁盘文件系统了

kernel2.6.35.11/init/do_mounts.c

void __init prepare_namespace(void)
{
......
if (root_delay) {
printk(KERN_INFO "Waiting %dsec before mounting root device...\n",
root_delay);
ssleep(root_delay);
}
......
wait_for_device_probe();
......
if (saved_root_name[0]) {
root_device_name = saved_root_name;
if (!strncmp(root_device_name, "mtd", 3) ||
!strncmp(root_device_name, "ubi", 3)) {
mount_block_root(root_device_name, root_mountflags);
goto out;
}
ROOT_DEV = name_to_dev_t(root_device_name);
if (strncmp(root_device_name, "/dev/", 5) == 0)
root_device_name += 5;
}
if (initrd_load())
goto out;
if ((ROOT_DEV == 0) && root_wait) {
printk(KERN_INFO "Waiting for root device %s...\n",
saved_root_name);
while (driver_probe_done() != 0 ||
(ROOT_DEV = name_to_dev_t(saved_root_name)) == 0)
msleep(100);
async_synchronize_full();
}
......
mount_root();//将磁盘文件系统挂载到rootfs的/root目录
sys_chroot(".");//将系统的根文件系统和根目录切换为磁盘的文件系统和目录、也就是rootfs的/root目录
}

上处代码执行完后,系统的根文件系统将从rootfs切换到实际的、同时系统根目录也会切换!

3.init进程启动

kernel2.6.35.11/init/main.c

static noinline int init_post(void)
{
......
if (ramdisk_execute_command) {
(void) sys_dup(0);
run_init_process(ramdisk_execute_command);
printk(KERN_WARNING "Failed to execute %s\n",
ramdisk_execute_command);
}
......
}
static void run_init_process(const char *init_filename)
{
argv_init[0] = init_filename;
kernel_execve(init_filename, argv_init, envp_init); //内核空间调用用户空间函数kernel_execve
}

kernel2.6.35.11/arch/arm/kernel/sys_arm.c

int kernel_execve(const char *filename,
const char *const argv[],
const char *const envp[])
{
struct pt_regs regs;
int ret;

memset(®s, 0, sizeof(struct pt_regs));
ret = do_execve(filename,
(const char __user *const __user *)argv,
(const char __user *const __user *)envp, ®s);
if (ret < 0)
goto out;

/*
* Save argc to the register structure for userspace.
*/
regs.ARM_r0 = ret;

/*
* We were successful. We won't be returning to our caller, but
* instead to user space by manipulating the kernel stack.
*/
asm( "add r0, %0, %1\n\t"
"mov r1, %2\n\t"
"mov r2, %3\n\t"
"bl memmove\n\t" /* copy regs to top of stack */
"mov r8, #0\n\t" /* not a syscall */
"mov r9, %0\n\t" /* thread structure */
"mov sp, r0\n\t" /* reposition stack pointer */
"b ret_to_user"
:
: "r" (current_thread_info()),
"Ir" (THREAD_START_SP - sizeof(regs)),
"r" (®s),
"Ir" (sizeof(regs))
: "r0", "r1", "r2", "r3", "ip", "lr", "memory");

out:
return ret;
}

注意:上处代码决定了init进程是一个用户态进程;即进程调度它执行时ARM是在用户模式、Linux是在用户态。

三、由内核进程0创建的内核线程kthreadd——2号进程

注意:区别与init进程(用户态普通进程),该进程是一个内核进程;主要作用是管理调度其他的内核线程。

kernel2.6.35.11/kernel/kthread.c

int kthreadd(void *unused)
{
......
struct kthread_create_info *create;
create = list_entry(kthread_create_list.next,
struct kthread_create_info, list);
list_del_init(&create->list);
create_kthread(create);
......
}
static void create_kthread(struct kthread_create_info *create)
{
int pid;
#ifdef CONFIG_NUMA
current->pref_node_fork = create->node;
#endif
/* We want our own signal handler (we take no signals by default). */
pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);
if (pid < 0) {
create->result = ERR_PTR(pid);
complete(&create->done);
}
}

补充说明,附:

一、内核空间执行用户空间的一段应用程序有两种方法:

1. call_usermodehelper

2. kernel_execve

二、kernel_thread函数实现

kernel2.6.35.11/arch/arm/kernel/process.c

pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
{
struct pt_regs regs;

memset(®s, 0, sizeof(regs));

regs.ARM_r4 = (unsigned long)arg;
regs.ARM_r5 = (unsigned long)fn;
regs.ARM_r6 = (unsigned long)kernel_thread_exit;
regs.ARM_r7 = SVC_MODE | PSR_ENDSTATE | PSR_ISETSTATE;
regs.ARM_pc = (unsigned long)kernel_thread_helper;
regs.ARM_cpsr = regs.ARM_r7 | PSR_I_BIT;

return do_fork(flags|CLONE_VM|CLONE_UNTRACED, 0, ®s, 0, NULL, NULL);
}
EXPORT_SYMBOL(kernel_thread);

三、kthreadd进程簇

1.keventd进程

执行keventd_wq工作队列中的函数。

2.kapmd

处理与高级电源管理相关的事件。

3.kswapd

执行内存周期回收。

4.pdflush

刷新“脏”缓冲区的内容到磁盘以回收内存。

5.kblockd

执行kblockd_workqueue工作队列中的函数。实质上,它周期性地激活块设备驱动程序。

6.ksoftirqd

运行tasklet;系统中每个CPU都有这样一个内核线程。

四、关于内核函数do_fork

  其实,供用户创建进程的系统调用fork()函数的响应函数sys_fork()、sys_clone、sys_vfork都是通过调用内核函数do_fork()来实现的。

即:在内核编程中使用内核函数kernel_thread创建内核线程与在用户空间编程中使用fork传见用户线程是一样的。

1.jb/bionic/libc/bionic/fork.c

#include <unistd.h>
#include "pthread_internal.h"
#include "bionic_pthread.h"
#include "cpuacct.h"
extern int __fork(void);
int fork(void)
{
......
int ret;
ret = __fork();
......
}

2.jb/bionic/libc/arch-arm/syscalls/__fork.S

ENTRY(__fork)
.save {r4, r7}
stmfd sp!, {r4, r7}
ldr r7, =__NR_fork
swi #0
ldmfd sp!, {r4, r7}
movs r0, r0
bxpl lr
b __set_syscall_errno
END(__fork)

============================================

3.系统调用表

kernel2.6.35.11/arch/arm/include/asm/unistd.h

#define __NR_fork                       (__NR_SYSCALL_BASE+  2)

kernel2.6.35.11/arch/arm/kernel/calls.S

CALL(sys_fork_wrapper)

4.实现

kernel2.6.35.11/arch/arm/kernel/entry-common.S

sys_fork_wrapper:
add r0, sp, #S_OFF
b sys_fork
ENDPROC(sys_fork_wrapper)

kernel2.6.35.11/arch/arm/kernel/sys_arm.c

asmlinkage int sys_fork(struct pt_regs *regs)
{
#ifdef CONFIG_MMU
return do_fork(SIGCHLD, regs->ARM_sp, regs, 0, NULL, NULL);
#else
/* can not support in nommu mode */
return(-EINVAL);
#endif
}