安装编译依赖

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

使用busybox制作文件系统

首先从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

使用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进行调试

跟踪Linux内核的启动过程

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;elsetrace = 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。

使用VScode阅读Linux源码相关推荐

  1. vscode读linux源码,vscode搭建linux内核开发环境

    vscode在linux下搭建内核驱动开发环境 一.前言 Souce insight是一个阅读.开发linux内核驱动模块的好工具,但是Source insight是收费的软件,而且没有原生linux ...

  2. Linux学习系列十九:如何高效的阅读Linux源码

    1.引言 如何阅读代码还要单独写一篇文章?难道不是随便用一个IDE就可以了吗?回到上一篇文章里介绍的那个问题,需要修改uboot里board_mmc_init函数里的writel(0x66666666 ...

  3. 献给新手,如何阅读Linux源码(转)

    常常有人问:我想学习内核,需要什么基础吗?Linus Torvalds本人是这样回答的:你必须使用过Linux. 这个--还是有点太泛了吧,我想下面几个基础可能还是需要的,尽管不一定必需: 1, 关于 ...

  4. Linux内核基础——Linux源码阅读工具Source Insight4.0

    Linux内核源码阅读工具--source insight4.0 Source insight4.0工具的使用入门 一.Souce insight建立工程.导入源码 二.遍历所有源码文件建立符号索引 ...

  5. 为什么C/C++程序员一定要阅读redis源码?腾讯面试教你做人【linux服务器开发】

    为什么C/C++程序员一定要阅读redis源码?腾讯面试教你做人[linux服务器开发] 专注于服务器后台开发,包括C/C++,Linux,Nginx,ZeroMQ,MySQL,Redis,fastd ...

  6. VSCode 阅读 Linux 代码怎么才不卡顿?这样做才能快的飞起!

    Linux 内核代码用什么编辑器? 分享过怎么学习 Linux 内核代码的思路,当时顺便提了一点,奇伢是用 vscode 看内核代码.有同学对此提出了疑问: vscode 看 Linux 代码不卡吗? ...

  7. 今天聊聊飞哥是怎么阅读内核源码的

    大家好,我是飞哥! 经常在后台收到读者的交流,Linux 源码那么庞大,飞哥你是如何读的呢?由于问这个问题的太多,我想有必要专门写一篇文章聊一聊. 首先,我先说一点,其实我本人不是搞内核相关工作的.我 ...

  8. linux源码包卸载方式

    linux源码包软件的安装与卸载 3人收藏此文章,我要收藏 发表于1年前 , 已有593次阅读 共0个评论 Linux软件安装与卸载(源码包形式):一般情况下linux程序的发布不能像windows那 ...

  9. webuploader 怎么在react中_另辟蹊径搭建阅读React源码调试环境支持所有React版本细分文件断点调试...

    引言(为什么写这篇文章) 若要高效阅读和理解React源码,搭建调试环境是必不可少的一步.而常规方法:使用react.development.js和react-dom.development.js调试 ...

最新文章

  1. 使用C语言来实现模块化
  2. 调用python_「Python 进阶」python 实现链式调用
  3. Linux的find -print 和 -print0区别:换行不换行
  4. 数据存储之SwiftJSON
  5. 如何在Mac上裁剪图片,教你几个技巧
  6. 深度学习、机器学习交流群
  7. 物权法全文内容有哪些呢-广告外链_广告策划包含了哪些内容?
  8. 动易百度快照劫持,百度快照被劫持了怎么办?
  9. 企业基因决定企业命运
  10. linux oracle 查看版本
  11. 2012服务器系统iis500错误,Windows Server IIS站点常见500错误及解决方案
  12. 服务器中的编码解码问题
  13. SAP 采购发票预制
  14. 先尝试一步——简单应用Advanced Installer9.8打包发布vb.net程序(vs2012)
  15. YOLOv3用到的tricks介绍
  16. Mybatis学习文档——(狂神说系列)
  17. 自卑与超越—读书笔记
  18. flutter微信登录
  19. html每行自动向上滑动,用HTML5+CSS3实现上下滑动的箭头
  20. JavaScript运算符的优先级

热门文章

  1. 3分钟拥有专属域名邮箱 / 腾讯云免费企业邮箱服务
  2. 关于VMware 15:在部分链上无法执行所调用的函数,请打开父虚拟磁盘
  3. Java8新特性stream流的优雅操作
  4. 自建网站教程!如何用云服务器搭建个人网站?
  5. day19 文件操作
  6. php基础-GD库-批量制作水印图片
  7. GBase8d产品admin目录下的文件及文件夹说明
  8. 让顶尖设计师教你10个色彩运用秘技,赶紧洗耳恭听吧!
  9. [机器学习]基于OpenCV实现最简单的数字识别
  10. 3D打印肝模型抢救生命