当前位置:   article > 正文

使用VScode阅读Linux源码

vscode阅读linux源码

安装编译依赖

sudo apt install build-essential libncurses-dev bison flex libssl-dev libelf-dev

下载内核源代码

www.kernel.org下载你想要的版本,这里下载的是5.4.34。

配置内核编译选项

  1. cd linux-5.4.34/
  2. make menuconfig

需要注意的相关配置如下

  1. make menuconfig
  2. # 打开debug相关选项
  3. Kernel hacking --->
  4. Compile-time checks and compiler options --->
  5. [*] Compile the kernel with debug info
  6. [*] Provide GDB scripts for kernel debugging
  7. [*] Kernel debugging
  8. # 关闭KASLR,否则会导致打断点失败
  9. Processor type and features ---->
  10. [] Randomize the address of the kernel image (KASLR)
vim .config

在其中搜索CONFIG_PREEMPTION,设置为y,如果没有添加该项,否则可能导致编译失败

  1. /CONFIG_PREMPTION
  2. CONFIG_PREEMPTION=y

开始编译

make -j10

下载qemu,并测试一下内核能不能正常加载运行,因为没有文件系统最终会kernel panic

  1. sudo apt install qemu-system-x86
  2. qemu-system-x86_64 -kernel arch/x86/boot/bzImage

使用busybox制作文件系统

首先从https://www.busybox.net下载 busybox源代码解压,解压完成后,跟内核一样先配置编译,并安装,这里安装的是1.36.0版本,尽量选择较新的版本进行安装,否则可能出现错误。

  1. cd busybox-1.36.0
  2. make menuconfig

需要注意的相关配置如下,使用静态链接

  1. Settings --->
  2. [*] Build static binary (no shared libs)

开始编译

make -j10

然后制作内存根文件系统镜像,大致过程如下:

  1. mkdir rootfs
  2. cd rootfs
  3. cp ../busybox-1.36.0/_install/* ./ -rf
  4. mkdir dev proc sys home
  5. sudo cp -a /dev/{null,console,tty,tty1,tty2,tty3,tty4} dev/

准备init脚本文件放在根文件系统跟目录下(rootfs/init),添加如下内容到init文件。

  1. #!/bin/sh
  2. mount -t proc none /proc
  3. mount -t sysfs none /sys
  4. echo "526zhugeLab3"
  5. echo "--------------------"
  6. cd home
  7. /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

使用VSCode打开linux-5.4.34文件夹

在linux-5.4.34文件夹上创建.vscode文件夹,加入以下配置文件

c_cpp_properties.json

  1. {
  2. "configurations": [
  3. {
  4. "name": "Linux",
  5. "includePath": [
  6. "${workspaceFolder}/arch/x86/include/**",
  7. "${workspaceFolder}/include/**",
  8. "${workspaceFolder}/include/linux/**",
  9. "${workspaceFolder}/arch/x86/**",
  10. "${workspaceFolder}/**"
  11. ],
  12. "cStandard": "c11",
  13. "intelliSenseMode": "gcc-x64",
  14. "compileCommands": "${workspaceFolder}/compile_commands.json"
  15. }
  16. ],
  17. "version": 4
  18. }

compile_commands.json

借助一个 Python 脚本来生成 compile_commands.json 文件帮助 Intellisense 正常提示(包括头文件和宏定义等)。在Linux源代码目录下直接运行如下命令就可以生成 compile_commands.json 了。

python3 ./scripts/gen_compile_commands.py

launch.json

由于直接在preLaunchTask中使用qemu可能导致阻塞,这里将改任务取消了,使用手动方式开启。

  1. {
  2. // Use IntelliSense to learn about possible attributes.
  3. // Hover to view descriptions of existing attributes.
  4. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
  5. "version": "0.2.0",
  6. "configurations": [
  7. {
  8. "name": "(gdb) linux",
  9. "type": "cppdbg",
  10. "request": "launch",
  11. //"preLaunchTask": "vm",
  12. "program": "${workspaceRoot}/vmlinux",
  13. "miDebuggerServerAddress": "localhost:1234",
  14. "args": [],
  15. "stopAtEntry": true,
  16. "cwd": "${workspaceFolder}",
  17. "environment": [],
  18. "externalConsole": false,
  19. "MIMode": "gdb",
  20. "miDebuggerArgs": "-n",
  21. "targetArchitecture": "x64",
  22. "setupCommands": [
  23. {
  24. "text": "set arch i386:x86-64:intel",
  25. "ignoreFailures": false
  26. },
  27. {
  28. "text": "dir .",
  29. "ignoreFailures": false
  30. },
  31. {
  32. "text": "add-auto-load-safe-path ./",
  33. "ignoreFailures": false
  34. },
  35. {
  36. "text": "-enable-pretty-printing",
  37. "ignoreFailures": true
  38. }
  39. ]
  40. }
  41. ]
  42. }

tasks.json

  1. {
  2. // See https://go.microsoft.com/fwlink/?LinkId=733558
  3. // for the documentation about the tasks.json format
  4. "version": "2.0.0",
  5. "tasks": [
  6. {
  7. "label": "vm",
  8. "type": "shell",
  9. "command": "qemu-system-x86_64 -kernel ${workspaceFolder}/arch/x86/boot/bzImage -initrd ../rootfs.cpio.gz -S -s -nographic -append \"console=ttyS0\"",
  10. "presentation": {
  11. "echo": true,
  12. "clear": true,
  13. "group": "vm"
  14. },
  15. "isBackground": true,
  16. "problemMatcher": [
  17. {
  18. "pattern": [
  19. {
  20. "regexp": ".",
  21. "file": 1,
  22. "location": 2,
  23. "message": 3
  24. }
  25. ],
  26. "background": {
  27. "activeOnStart": true,
  28. "beginsPattern": ".",
  29. "endsPattern": ".",
  30. }
  31.           }
  32. ]
  33. },
  34. {
  35. "label": "build linux",
  36. "type": "shell",
  37. "command": "make",
  38. "group": {
  39. "kind": "build",
  40. "isDefault": true
  41. },
  42. "presentation": {
  43. "echo": false,
  44. "group": "build"
  45. }
  46. }
  47. ]
  48. }

settings.json

  1. {
  2. "search.exclude": {
  3. "**/.git": true,
  4. "**/.svn": true,
  5. "**/.DS_Store": true,
  6. "**/drivers": true,
  7. "**/sound": true,
  8. "**/tools": true,
  9. "**/arch/alpha": true,
  10. "**/arch/arc": true,
  11. "**/arch/c6x": true,
  12. "**/arch/h8300": true,
  13. "**/arch/hexagon": true,
  14. "**/arch/ia64": true,
  15. "**/arch/m32r": true,
  16. "**/arch/m68k": true,
  17. "**/arch/microblaze": true,
  18. "**/arch/mn10300": true,
  19. "**/arch/nds32": true,
  20. "**/arch/nios2": true,
  21. "**/arch/parisc": true,
  22. "**/arch/powerpc": true,
  23. "**/arch/s390": true,
  24. "**/arch/sparc": true,
  25. "**/arch/score": true,
  26. "**/arch/sh": true,
  27. "**/arch/um": true,
  28. "**/arch/unicore32": true,
  29. "**/arch/xtensa": true
  30. },
  31. "files.exclude": {
  32. "**/.*.*.cmd": true,
  33. "**/.*.d": true,
  34. "**/.*.o": true,
  35. "**/.*.S": true,
  36. "**/.git": true,
  37. "**/.svn": true,
  38. "**/.DS_Store": true,
  39. "**/drivers": true,
  40. "**/sound": true,
  41. "**/tools": true,
  42. "**/arch/alpha": true,
  43. "**/arch/arc": true,
  44. "**/arch/c6x": true,
  45. "**/arch/h8300": true,
  46. "**/arch/hexagon": true,
  47. "**/arch/ia64": true,
  48. "**/arch/m32r": true,
  49. "**/arch/m68k": true,
  50. "**/arch/microblaze": true,
  51. "**/arch/mn10300": true,
  52. "**/arch/nds32": true,
  53. "**/arch/nios2": true,
  54. "**/arch/parisc": true,
  55. "**/arch/powerpc": true,
  56. "**/arch/s390": true,
  57. "**/arch/sparc": true,
  58. "**/arch/score": true,
  59. "**/arch/sh": true,
  60. "**/arch/um": true,
  61. "**/arch/unicore32": true,
  62. "**/arch/xtensa": true
  63. },
  64. "[c]": {
  65. "editor.detectIndentation": false,
  66. "editor.tabSize": 8,
  67. "editor.insertSpaces": false
  68. },
  69. "C_Cpp.errorSquiggles": "disabled"
  70. }

现在可以启动调试了,在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进行调试

跟踪Linux内核的启动过程

init_task位于init/init_task.c文件夹下,是手动创建的静态进程,是linux系统中所有进程的祖先。

init_task是Linux内核中的第一个线程,它贯穿于整个Linux系统的初始化过程中,该进程也是Linux系统中唯一一个没有用kernel_thread()函数创建的内核态进程(内核线程)。

接下来start_kernel依次执行内核各个重要子系统的初始化,比如mm, cpu, sched, irq等等。最后会调用一个rest_init剩余部分初始化。

  1. asmlinkage __visible void __init start_kernel(void)
  2. {
  3. char *command_line;
  4. char *after_dashes;
  5. set_task_stack_end_magic(&init_task);
  6. smp_setup_processor_id();
  7. debug_objects_early_init();
  8. cgroup_init_early();
  9. local_irq_disable();
  10. ..........
  11. //剩余初始化
  12. arch_call_rest_init();
  13. }
  14. void __init __weak arch_call_rest_init(void)
  15. {
  16. rest_init();
  17. }
  1. noinline void __ref rest_init(void)
  2. {
  3. struct task_struct *tsk;
  4. int pid;
  5. rcu_scheduler_starting();
  6. /*
  7. * We need to spawn init first so that it obtains pid 1, however
  8. * the init task will end up wanting to create kthreads, which, if
  9. * we schedule it before we create kthreadd, will OOPS.
  10. */
  11. pid = kernel_thread(kernel_init, NULL, CLONE_FS);
  12. /*
  13. * Pin init on the boot CPU. Task migration is not properly working
  14. * until sched_init_smp() has been run. It will set the allowed
  15. * CPUs for init to the non isolated CPUs.
  16. */
  17. rcu_read_lock();
  18. tsk = find_task_by_pid_ns(pid, &init_pid_ns);
  19. set_cpus_allowed_ptr(tsk, cpumask_of(smp_processor_id()));
  20. rcu_read_unlock();
  21. numa_default_policy();
  22. pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
  23. rcu_read_lock();
  24. kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
  25. rcu_read_unlock();
  26. /*
  27. * Enable might_sleep() and smp_processor_id() checks.
  28. * They cannot be enabled earlier because with CONFIG_PREEMPTION=y
  29. * kernel_thread() would trigger might_sleep() splats. With
  30. * CONFIG_PREEMPT_VOLUNTARY=y the init task might have scheduled
  31. * already, but it's stuck on the kthreadd_done completion.
  32. */
  33. system_state = SYSTEM_SCHEDULING;
  34. complete(&kthreadd_done);
  35. /*
  36. * The boot idle thread must execute schedule()
  37. * at least once to get things moving:
  38. */
  39. schedule_preempt_disabled();
  40. /* Call into cpu_idle with preempt disabled */
  41. cpu_startup_entry(CPUHP_ONLINE);
  42. }

在rest_init中实际只需关心两行

  1. //第12行
  2. pid = kernel_thread(kernel_init, NULL, CLONE_FS);
  3. //第24行
  4. pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);

查看kernel_thread函数

  1. /*
  2. * Create a kernel thread.
  3. */
  4. pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
  5. {
  6. struct kernel_clone_args args = {
  7. .flags = ((flags | CLONE_VM | CLONE_UNTRACED) & ~CSIGNAL),
  8. .exit_signal = (flags & CSIGNAL),
  9. .stack = (unsigned long)fn,
  10. .stack_size = (unsigned long)arg,
  11. };
  12. return _do_fork(&args);
  13. }

查看_do_fork()函数

  1. /*
  2. * Ok, this is the main fork-routine.
  3. *
  4. * It copies the process, and if successful kick-starts
  5. * it and waits for it to finish using the VM if required.
  6. *
  7. * args->exit_signal is expected to be checked for sanity by the caller.
  8. */
  9. long _do_fork(struct kernel_clone_args *args)
  10. {
  11. u64 clone_flags = args->flags;
  12. struct completion vfork;
  13. struct pid *pid;
  14. struct task_struct *p;
  15. int trace = 0;
  16. long nr;
  17. /*
  18. * Determine whether and which event to report to ptracer. When
  19. * called from kernel_thread or CLONE_UNTRACED is explicitly
  20. * requested, no event is reported; otherwise, report if the event
  21. * for the type of forking is enabled.
  22. */
  23. if (!(clone_flags & CLONE_UNTRACED)) {
  24. if (clone_flags & CLONE_VFORK)
  25. trace = PTRACE_EVENT_VFORK;
  26. else if (args->exit_signal != SIGCHLD)
  27. trace = PTRACE_EVENT_CLONE;
  28. else
  29. trace = PTRACE_EVENT_FORK;
  30. if (likely(!ptrace_event_enabled(current, trace)))
  31. trace = 0;
  32. }
  33. p = copy_process(NULL, trace, NUMA_NO_NODE, args);
  34. add_latent_entropy();
  35. if (IS_ERR(p))
  36. return PTR_ERR(p);
  37. /*
  38. * Do this prior waking up the new thread - the thread pointer
  39. * might get invalid after that point, if the thread exits quickly.
  40. */
  41. trace_sched_process_fork(current, p);
  42. pid = get_task_pid(p, PIDTYPE_PID);
  43. nr = pid_vnr(pid);
  44. if (clone_flags & CLONE_PARENT_SETTID)
  45. put_user(nr, args->parent_tid);
  46. if (clone_flags & CLONE_VFORK) {
  47. p->vfork_done = &vfork;
  48. init_completion(&vfork);
  49. get_task_struct(p);
  50. }
  51. wake_up_new_task(p);
  52. /* forking complete and child started to run, tell ptracer */
  53. if (unlikely(trace))
  54. ptrace_event_pid(trace, pid);
  55. if (clone_flags & CLONE_VFORK) {
  56. if (!wait_for_vfork_done(p, &vfork))
  57. ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
  58. }
  59. put_pid(pid);
  60. return nr;
  61. }

_do_fork()调用copy_process(),执行生成子进程的实际工作,并根据指定的标志复制父进程的数据。在子进程生成后,调用 wake_up_new_task 将子进程加入调度器,为之分配 CPU。最后返回子进程pid。

接下来查看kernel_init()和kthreadd()函数,这两个函数作为进程被启动。

  1. static int __ref kernel_init(void *unused)
  2. {
  3. int ret;
  4. kernel_init_freeable();
  5. /* need to finish all async __init code before freeing the memory */
  6. async_synchronize_full();
  7. ftrace_free_init_mem();
  8. free_initmem();
  9. mark_readonly();
  10. /*
  11. * Kernel mappings are now finalized - update the userspace page-table
  12. * to finalize PTI.
  13. */
  14. pti_finalize();
  15. system_state = SYSTEM_RUNNING;
  16. numa_default_policy();
  17. rcu_end_inkernel_boot();
  18. if (ramdisk_execute_command) {
  19. ret = run_init_process(ramdisk_execute_command);
  20. if (!ret)
  21. return 0;
  22. pr_err("Failed to execute %s (error %d)\n",
  23. ramdisk_execute_command, ret);
  24. }
  25. /*
  26. * We try each of these until one succeeds.
  27. *
  28. * The Bourne shell can be used instead of init if we are
  29. * trying to recover a really broken machine.
  30. */
  31. if (execute_command) {
  32. ret = run_init_process(execute_command);
  33. if (!ret)
  34. return 0;
  35. panic("Requested init %s failed (error %d).",
  36. execute_command, ret);
  37. }
  38. if (!try_to_run_init_process("/sbin/init") ||
  39. !try_to_run_init_process("/etc/init") ||
  40. !try_to_run_init_process("/bin/init") ||
  41. !try_to_run_init_process("/bin/sh"))
  42. return 0;
  43. panic("No working init found. Try passing init= option to kernel. "
  44. "See Linux Documentation/admin-guide/init.rst for guidance.");
  45. }

执行各种外设驱动的初始化,挂载根文件系统,按顺序执行/init可以执行文件,完成用户态初始化。

  1. int kthreadd(void *unused)
  2. {
  3. struct task_struct *tsk = current;
  4. /* Setup a clean context for our children to inherit. */
  5. set_task_comm(tsk, "kthreadd");
  6. ignore_signals(tsk);
  7. set_cpus_allowed_ptr(tsk, cpu_all_mask);
  8. set_mems_allowed(node_states[N_MEMORY]);
  9. current->flags |= PF_NOFREEZE;
  10. cgroup_init_kthreadd();
  11. for (;;) {
  12. set_current_state(TASK_INTERRUPTIBLE);
  13. if (list_empty(&kthread_create_list))
  14. schedule();
  15. __set_current_state(TASK_RUNNING);
  16. spin_lock(&kthread_create_lock);
  17. while (!list_empty(&kthread_create_list)) {
  18. struct kthread_create_info *create;
  19. create = list_entry(kthread_create_list.next,
  20. struct kthread_create_info, list);
  21. list_del_init(&create->list);
  22. spin_unlock(&kthread_create_lock);
  23. create_kthread(create);
  24. spin_lock(&kthread_create_lock);
  25. }
  26. spin_unlock(&kthread_create_lock);
  27. }
  28. return 0;
  29. }

设置当前进程的名字为"kthreadd",循环设置当前的进程的状态是TASK_INTERRUPTIBLE是可以中断的,判断kthread_create_list链表是不是空,如果是空则就调度出去,让出cpu,如果不是空,则从链表中取出一个,然后调用kthread_create去创建一个内核线程。所以说所有的内核线程的父进程都是kthreadd。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/239908
推荐阅读
相关标签
  

闽ICP备14008679号