赞
踩
sudo apt install build-essential libncurses-dev bison flex libssl-dev libelf-dev
从www.kernel.org下载你想要的版本,这里下载的是5.4.34。
- cd linux-5.4.34/
- make menuconfig
需要注意的相关配置如下
- make menuconfig
- # 打开debug相关选项
- Kernel hacking --->
- Compile-time checks and compiler options --->
- [*] Compile the kernel with debug info
- [*] Provide GDB scripts for kernel debugging
- [*] Kernel debugging
- # 关闭KASLR,否则会导致打断点失败
- Processor type and features ---->
- [] Randomize the address of the kernel image (KASLR)
vim .config
在其中搜索CONFIG_PREEMPTION,设置为y,如果没有添加该项,否则可能导致编译失败
- /CONFIG_PREMPTION
- CONFIG_PREEMPTION=y
开始编译
make -j10
下载qemu,并测试一下内核能不能正常加载运行,因为没有文件系统最终会kernel panic
- sudo apt install qemu-system-x86
- qemu-system-x86_64 -kernel arch/x86/boot/bzImage
首先从https://www.busybox.net下载 busybox源代码解压,解压完成后,跟内核一样先配置编译,并安装,这里安装的是1.36.0版本,尽量选择较新的版本进行安装,否则可能出现错误。
- cd busybox-1.36.0
- make menuconfig
需要注意的相关配置如下,使用静态链接
- Settings --->
- [*] Build static binary (no shared libs)
开始编译
make -j10
然后制作内存根文件系统镜像,大致过程如下:
- mkdir rootfs
- cd rootfs
- cp ../busybox-1.36.0/_install/* ./ -rf
- mkdir dev proc sys home
- sudo cp -a /dev/{null,console,tty,tty1,tty2,tty3,tty4} dev/
准备init脚本文件放在根文件系统跟目录下(rootfs/init),添加如下内容到init文件。
- #!/bin/sh
- mount -t proc none /proc
- mount -t sysfs none /sys
- echo "526zhugeLab3"
- echo "--------------------"
- cd home
- /bin/sh
给init脚本添加可执行权限
chmod +x init
打包成内存根文件系统镜像
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../rootfs.cpio.gz
测试挂载根文件系统,看内核启动完成后是否执行init脚本
qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz
可以启动
使用VSCode打开linux-5.4.34文件夹
在linux-5.4.34文件夹上创建.vscode文件夹,加入以下配置文件
c_cpp_properties.json
{ "configurations": [ { "name": "Linux", "includePath": [ "${workspaceFolder}/arch/x86/include/**", "${workspaceFolder}/include/**", "${workspaceFolder}/include/linux/**", "${workspaceFolder}/arch/x86/**", "${workspaceFolder}/**" ], "cStandard": "c11", "intelliSenseMode": "gcc-x64", "compileCommands": "${workspaceFolder}/compile_commands.json" } ], "version": 4 }
compile_commands.json
借助一个 Python 脚本来生成 compile_commands.json 文件帮助 Intellisense 正常提示(包括头文件和宏定义等)。在Linux源代码目录下直接运行如下命令就可以生成 compile_commands.json 了。
python3 ./scripts/gen_compile_commands.py
launch.json
由于直接在preLaunchTask中使用qemu可能导致阻塞,这里将改任务取消了,使用手动方式开启。
{ // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "(gdb) linux", "type": "cppdbg", "request": "launch", //"preLaunchTask": "vm", "program": "${workspaceRoot}/vmlinux", "miDebuggerServerAddress": "localhost:1234", "args": [], "stopAtEntry": true, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": false, "MIMode": "gdb", "miDebuggerArgs": "-n", "targetArchitecture": "x64", "setupCommands": [ { "text": "set arch i386:x86-64:intel", "ignoreFailures": false }, { "text": "dir .", "ignoreFailures": false }, { "text": "add-auto-load-safe-path ./", "ignoreFailures": false }, { "text": "-enable-pretty-printing", "ignoreFailures": true } ] } ] }
tasks.json
{ // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format "version": "2.0.0", "tasks": [ { "label": "vm", "type": "shell", "command": "qemu-system-x86_64 -kernel ${workspaceFolder}/arch/x86/boot/bzImage -initrd ../rootfs.cpio.gz -S -s -nographic -append \"console=ttyS0\"", "presentation": { "echo": true, "clear": true, "group": "vm" }, "isBackground": true, "problemMatcher": [ { "pattern": [ { "regexp": ".", "file": 1, "location": 2, "message": 3 } ], "background": { "activeOnStart": true, "beginsPattern": ".", "endsPattern": ".", } } ] }, { "label": "build linux", "type": "shell", "command": "make", "group": { "kind": "build", "isDefault": true }, "presentation": { "echo": false, "group": "build" } } ] }
settings.json
{ "search.exclude": { "**/.git": true, "**/.svn": true, "**/.DS_Store": true, "**/drivers": true, "**/sound": true, "**/tools": true, "**/arch/alpha": true, "**/arch/arc": true, "**/arch/c6x": true, "**/arch/h8300": true, "**/arch/hexagon": true, "**/arch/ia64": true, "**/arch/m32r": true, "**/arch/m68k": true, "**/arch/microblaze": true, "**/arch/mn10300": true, "**/arch/nds32": true, "**/arch/nios2": true, "**/arch/parisc": true, "**/arch/powerpc": true, "**/arch/s390": true, "**/arch/sparc": true, "**/arch/score": true, "**/arch/sh": true, "**/arch/um": true, "**/arch/unicore32": true, "**/arch/xtensa": true }, "files.exclude": { "**/.*.*.cmd": true, "**/.*.d": true, "**/.*.o": true, "**/.*.S": true, "**/.git": true, "**/.svn": true, "**/.DS_Store": true, "**/drivers": true, "**/sound": true, "**/tools": true, "**/arch/alpha": true, "**/arch/arc": true, "**/arch/c6x": true, "**/arch/h8300": true, "**/arch/hexagon": true, "**/arch/ia64": true, "**/arch/m32r": true, "**/arch/m68k": true, "**/arch/microblaze": true, "**/arch/mn10300": true, "**/arch/nds32": true, "**/arch/nios2": true, "**/arch/parisc": true, "**/arch/powerpc": true, "**/arch/s390": true, "**/arch/sparc": true, "**/arch/score": true, "**/arch/sh": true, "**/arch/um": true, "**/arch/unicore32": true, "**/arch/xtensa": true }, "[c]": { "editor.detectIndentation": false, "editor.tabSize": 8, "editor.insertSpaces": false }, "C_Cpp.errorSquiggles": "disabled" }
现在可以启动调试了,在init/main.c中的start_kernel()函数内添加断点,在终端中输入
qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S -s -nographic -append \"console=ttyS0\"
然后在VSCode中按下F5进行调试
init_task位于init/init_task.c文件夹下,是手动创建的静态进程,是linux系统中所有进程的祖先。
init_task是Linux内核中的第一个线程,它贯穿于整个Linux系统的初始化过程中,该进程也是Linux系统中唯一一个没有用kernel_thread()函数创建的内核态进程(内核线程)。
接下来start_kernel依次执行内核各个重要子系统的初始化,比如mm, cpu, sched, irq等等。最后会调用一个rest_init剩余部分初始化。
- asmlinkage __visible void __init start_kernel(void)
- {
- char *command_line;
- char *after_dashes;
- set_task_stack_end_magic(&init_task);
- smp_setup_processor_id();
- debug_objects_early_init();
-
- cgroup_init_early();
-
- local_irq_disable();
- ..........
- //剩余初始化
- arch_call_rest_init();
- }
-
- void __init __weak arch_call_rest_init(void)
- {
- rest_init();
- }
- noinline void __ref rest_init(void)
- {
- struct task_struct *tsk;
- 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.
- */
- pid = kernel_thread(kernel_init, NULL, CLONE_FS);
- /*
- * Pin init on the boot CPU. Task migration is not properly working
- * until sched_init_smp() has been run. It will set the allowed
- * CPUs for init to the non isolated CPUs.
- */
- rcu_read_lock();
- tsk = find_task_by_pid_ns(pid, &init_pid_ns);
- set_cpus_allowed_ptr(tsk, cpumask_of(smp_processor_id()));
- rcu_read_unlock();
-
- 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();
-
- /*
- * Enable might_sleep() and smp_processor_id() checks.
- * They cannot be enabled earlier because with CONFIG_PREEMPTION=y
- * kernel_thread() would trigger might_sleep() splats. With
- * CONFIG_PREEMPT_VOLUNTARY=y the init task might have scheduled
- * already, but it's stuck on the kthreadd_done completion.
- */
- system_state = SYSTEM_SCHEDULING;
-
- complete(&kthreadd_done);
-
- /*
- * The boot idle thread must execute schedule()
- * at least once to get things moving:
- */
- schedule_preempt_disabled();
- /* Call into cpu_idle with preempt disabled */
- cpu_startup_entry(CPUHP_ONLINE);
- }
在rest_init中实际只需关心两行
- //第12行
- pid = kernel_thread(kernel_init, NULL, CLONE_FS);
- //第24行
- pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
查看kernel_thread函数
- /*
- * Create a kernel thread.
- */
- pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
- {
- struct kernel_clone_args args = {
- .flags = ((flags | CLONE_VM | CLONE_UNTRACED) & ~CSIGNAL),
- .exit_signal = (flags & CSIGNAL),
- .stack = (unsigned long)fn,
- .stack_size = (unsigned long)arg,
- };
-
- return _do_fork(&args);
- }
查看_do_fork()函数
- /*
- * Ok, this is the main fork-routine.
- *
- * It copies the process, and if successful kick-starts
- * it and waits for it to finish using the VM if required.
- *
- * args->exit_signal is expected to be checked for sanity by the caller.
- */
- long _do_fork(struct kernel_clone_args *args)
- {
- u64 clone_flags = args->flags;
- struct completion vfork;
- struct pid *pid;
- struct task_struct *p;
- int trace = 0;
- long nr;
-
- /*
- * Determine whether and which event to report to ptracer. When
- * called from kernel_thread or CLONE_UNTRACED is explicitly
- * requested, no event is reported; otherwise, report if the event
- * for the type of forking is enabled.
- */
- if (!(clone_flags & CLONE_UNTRACED)) {
- if (clone_flags & CLONE_VFORK)
- trace = PTRACE_EVENT_VFORK;
- else if (args->exit_signal != SIGCHLD)
- trace = PTRACE_EVENT_CLONE;
- else
- trace = PTRACE_EVENT_FORK;
-
- if (likely(!ptrace_event_enabled(current, trace)))
- trace = 0;
- }
-
- p = copy_process(NULL, trace, NUMA_NO_NODE, args);
- add_latent_entropy();
-
- if (IS_ERR(p))
- return PTR_ERR(p);
-
- /*
- * Do this prior waking up the new thread - the thread pointer
- * might get invalid after that point, if the thread exits quickly.
- */
- trace_sched_process_fork(current, p);
-
- pid = get_task_pid(p, PIDTYPE_PID);
- nr = pid_vnr(pid);
-
- if (clone_flags & CLONE_PARENT_SETTID)
- put_user(nr, args->parent_tid);
-
- if (clone_flags & CLONE_VFORK) {
- p->vfork_done = &vfork;
- init_completion(&vfork);
- get_task_struct(p);
- }
-
- wake_up_new_task(p);
-
- /* forking complete and child started to run, tell ptracer */
- if (unlikely(trace))
- ptrace_event_pid(trace, pid);
-
- if (clone_flags & CLONE_VFORK) {
- if (!wait_for_vfork_done(p, &vfork))
- ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
- }
-
- put_pid(pid);
- return nr;
- }
_do_fork()调用copy_process(),执行生成子进程的实际工作,并根据指定的标志复制父进程的数据。在子进程生成后,调用 wake_up_new_task 将子进程加入调度器,为之分配 CPU。最后返回子进程pid。
接下来查看kernel_init()和kthreadd()函数,这两个函数作为进程被启动。
- static int __ref kernel_init(void *unused)
- {
- int ret;
-
- kernel_init_freeable();
- /* need to finish all async __init code before freeing the memory */
- async_synchronize_full();
- ftrace_free_init_mem();
- free_initmem();
- mark_readonly();
-
- /*
- * Kernel mappings are now finalized - update the userspace page-table
- * to finalize PTI.
- */
- pti_finalize();
-
- system_state = SYSTEM_RUNNING;
- numa_default_policy();
-
- rcu_end_inkernel_boot();
-
- if (ramdisk_execute_command) {
- ret = run_init_process(ramdisk_execute_command);
- if (!ret)
- return 0;
- pr_err("Failed to execute %s (error %d)\n",
- ramdisk_execute_command, ret);
- }
-
- /*
- * 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) {
- ret = run_init_process(execute_command);
- if (!ret)
- return 0;
- panic("Requested init %s failed (error %d).",
- execute_command, ret);
- }
- if (!try_to_run_init_process("/sbin/init") ||
- !try_to_run_init_process("/etc/init") ||
- !try_to_run_init_process("/bin/init") ||
- !try_to_run_init_process("/bin/sh"))
- return 0;
-
- panic("No working init found. Try passing init= option to kernel. "
- "See Linux Documentation/admin-guide/init.rst for guidance.");
- }
执行各种外设驱动的初始化,挂载根文件系统,按顺序执行/init可以执行文件,完成用户态初始化。
- int kthreadd(void *unused)
- {
- struct task_struct *tsk = current;
-
- /* Setup a clean context for our children to inherit. */
- set_task_comm(tsk, "kthreadd");
- ignore_signals(tsk);
- set_cpus_allowed_ptr(tsk, cpu_all_mask);
- set_mems_allowed(node_states[N_MEMORY]);
-
- current->flags |= PF_NOFREEZE;
- cgroup_init_kthreadd();
-
- for (;;) {
- set_current_state(TASK_INTERRUPTIBLE);
- if (list_empty(&kthread_create_list))
- schedule();
- __set_current_state(TASK_RUNNING);
-
- spin_lock(&kthread_create_lock);
- while (!list_empty(&kthread_create_list)) {
- struct kthread_create_info *create;
-
- create = list_entry(kthread_create_list.next,
- struct kthread_create_info, list);
- list_del_init(&create->list);
- spin_unlock(&kthread_create_lock);
-
- create_kthread(create);
-
- spin_lock(&kthread_create_lock);
- }
- spin_unlock(&kthread_create_lock);
- }
-
- return 0;
- }
设置当前进程的名字为"kthreadd",循环设置当前的进程的状态是TASK_INTERRUPTIBLE是可以中断的,判断kthread_create_list链表是不是空,如果是空则就调度出去,让出cpu,如果不是空,则从链表中取出一个,然后调用kthread_create去创建一个内核线程。所以说所有的内核线程的父进程都是kthreadd。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。