阅读更多
1 How to compile kernel
准备环境:这里我安装的系统是CentOS-7-x86_64-Minimal-1908.iso
第一步:安装编译相关的软件
1 | yum makecache |
第二步:下载内核源码并解压
1 | yum install -y wget |
第三步:配置内核编译参数
1 | cd ~/linux-4.14.134 |
第四步:编译内核
1 | cd ~/linux-4.14.134 |
第五步:更新内核
1 | # 查看rpm包的信息 |
1.1 Reference
- 内核源码下载地址
- Compile Linux Kernel on CentOS7
- How to Compile a Linux Kernel
- CentOS 7上Systemtap的安装
- 如何解压RPM包
- 教你三步在CentOS 7 中安装或升级最新的内核
2 Dynamic Kernel Module Loading
Dynamic Kernel Module Loading (DKML) is a mechanism in operating systems, particularly in Unix-like systems such as Linux, that allows for the loading and unloading of kernel modules at runtime. Kernel modules are pieces of code that can be loaded into the kernel to extend its functionality without the need to reboot the system. This feature is particularly useful for adding support for new hardware, filesystems, or other features without requiring a full kernel rebuild or system restart.
Here are the key aspects of Dynamic Kernel Module Loading:
- Modularity: DKML enhances the modularity of the kernel. Instead of having a monolithic kernel with all functionalities built-in, functionalities can be separated into modules that are loaded as needed.
- Flexibility: It allows for greater flexibility in managing system resources. Modules can be loaded when their functionality is required and unloaded when they’re no longer needed, freeing up memory and other resources.
- Ease of Updates and Maintenance: Updating or adding new features to the kernel becomes easier. Instead of recompiling and rebooting the entire kernel, only the relevant modules need to be updated.
- On-Demand Loading: Many modules are loaded automatically by the system in response to detected hardware or filesystems. This on-demand loading simplifies configuration and ensures that only necessary modules are loaded.
- Commands for Module Management: In Linux, commands like insmod, rmmod, modprobe, and lsmod are used to insert, remove, manage, and list kernel modules, respectively.
- Dependencies Handling: The system handles dependencies between modules, loading any required supporting modules automatically.
- Security Considerations: Loading modules into the kernel space can have security implications, as malicious or faulty modules could affect the stability and security of the system.
- Performance Impacts: While dynamic loading offers flexibility, it can have performance impacts due to the overhead of loading and unloading modules.
- Usage in Various Systems: Beyond Linux, other systems like FreeBSD and Solaris also support dynamic kernel module loading, though with different implementations and utilities.
2.1 Tools
In Unix-like operating systems, several command-line tools are used to manage dynamic kernel module loading. These tools allow users to insert, remove, and manage kernel modules while the system is running. Here’s an introduction to some of the most commonly used tools:
insmod
:
- Purpose: This command is used to insert a module into the Linux kernel.
- Usage:
insmod [module_name]
- Details: When you use
insmod
, you must specify the full path to the module if it is not in the default directory. It does not resolve dependencies, meaning you need to load any dependent modules beforehand.
rmmod
:
- Purpose: This command is used to remove a module from the Linux kernel.
- Usage:
rmmod [module_name]
- Details: It will only remove the module if it is not in use and if no other modules depend on it.
modprobe
:
- Purpose: This command adds or removes modules from the Linux kernel.
- Usage: To insert a module, use
modprobe [module_name]
. To remove a module, usemodprobe -r [module_name]
. - Details: Unlike
insmod
,modprobe
automatically handles dependencies. It checks the module dependencies listed in/lib/modules/$(uname -r)/modules.dep
file and loads them as needed.
lsmod
:
- Purpose: This command is used to show the status of modules in the Linux kernel.
- Usage:
lsmod
- Details: It displays a list of all currently loaded modules, along with module size and information about what other modules are using them.
depmod
:
- Purpose: This tool creates a dependency file for modules.
- Usage:
depmod
- Details: Generally run automatically when installing new modules, it analyzes the modules and builds a list of dependencies, which is then used by
modprobe
.
modinfo
:
- Purpose: Provides detailed information about a kernel module.
- Usage:
modinfo [module_name]
- Details: It displays information such as module description, author, license, and parameters that can be set.
3 systemtap
3.1 How to install
准备环境:这里我安装的系统是CentOS-7-x86_64-Minimal-1810.iso
第一步:安装systemtap以及其他相关依赖
1 | yum makecache |
第二步:下载并安装跟当前内核版本匹配的rpm包,包括kernel-devel-$(uname -r).rpm
、kernel-debuginfo-$(uname -r).rpm
、kernel-debuginfo-common-x86_64-$(uname -r).rpm
,我的内核版本是3.10.0-957.el7.x86_64
1 | wget "ftp://ftp.pbone.net/mirror/ftp.scientificlinux.org/linux/scientific/7.6/x86_64/os/Packages/kernel-devel-$(uname -r).rpm" |
第三步:验证
1 | stap -v -e 'probe vfs.read {printf("read performed\n"); exit()}' |
可以看到报错信息ERROR: module version mismatch (#1 SMP Tue Oct 30 14:13:26 CDT 2018 vs #1 SMP Thu Nov 8 23:39:32 UTC 2018), release 3.10.0-957.el7.x86_64
,这是由于compile.h
文件中的时间与uname -a
中的时间不一致
其中,compile.h
的文件路径为/usr/src/kernels/$(uname -r)/include/generated/compile.h
,我们将该文件中的时间修改为uname -a
中的时间信息。编辑compile.h
文件,将#define UTS_VERSION "#1 SMP Tue Oct 30 14:13:26 CDT 2018"
修改为#define UTS_VERSION "#1 SMP Thu Nov 8 23:39:32 UTC 2018"
再次尝试验证
1 | stap -v -e 'probe vfs.read {printf("read performed\n"); exit()}' |
发现还是报同样的信息,这是因为我们的修改尚未生效,系统读取的是缓存数据,将Pass 3
和Pass 4
中提到的两个缓存文件删除,再重新执行即可
1 | rm -f /root/.systemtap/cache/09/stap_0969603f9a0fb68895de95cd2ffea0a4_2770.c |
3.2 Syntax
3.2.1 Probe Types
begin
:探测开始的地方end
:探测结束的地方kernel.function("sys_open")
:指定系统调用的入口处syscall.close.return
:系统调用close
返回处module("ext3").statement(0xdeadbeef)
:文件系统ext3
驱动的指定位置timer.ms(200)
:定时器,单位毫秒timer.profile
:每个CPU时钟都会触发process("a.out").statement("*@main.c:200")
:二进制程序a.out
的200行的位置
3.3 Reference
4 ftrace
本小节转载摘录自Linux ftrace框架介绍及运用
在日常工作中,经常会需要对内核进行debug、或者进行优化工作。一些简单的问题,可以通过dmesg/printk
查看,优化借助一些工具进行。但是当问题逻辑复杂,优化面宽泛的时候,往往无从下手。需要从上到下、模块到模块之间分析,这时候就不得不借助于Linux提供的静态(trace event
)动态(各种tracer
)工具进行分析。同时还不得不借助工具、或者编写脚本进行分析,以缩小问题范围、发现问题。简单的使用tracepoint
已经不能满足需求,因此就花点精力进行梳理
ftrace
是function trace
的意思,最开始主要用于记录内核函数运行轨迹,随着功能的逐渐增加,演变成一个跟踪框架。包含了静态tracepoint
,针对不同subsystem
提供一个目录进行开关。还包括不同的动态跟踪器,function
、function_graph
、wakeup
等等
ftrace
的帮助文档在Documentation/trace
,ftrace
代码主要在kernel/trace
,ftrace
相关头文件在include/trace
中
4.1 ftrace Framework
整个ftrace
框架可以分为几部分:
ftrace
核心框架:整个ftrace
功能的纽带,包括对内和的修改,tracer
的注册,ring
的控制等等ring buffer
:静态动态ftrace
的载体debugfs
:提供了用户空间对ftrace
设置接口tracepoint
:静态trace
- 他需要提前编译进内核
- 可以定制打印内容,自由添加
- 内核对主要
subsystem
提供了tracepoint
tracer
:包含如下几类函数类
:function
、function_graph
、stack
延时类
:irqsoff
、preemptoff
、preemptirqsoff
、wakeup
、wakeup_rt
、waktup_dl
其他
:nop
、mmiotrace
、blk
4.2 How to use ftrace
/sys/kernel/debug/tracing
目录下提供了ftrace的设置和属性接口,对ftrace
的配置可以通过echo。了解每个文件的作用和如何设置对于理解整个ftrace
框架很有作用
kernel很贴心的在这个目录下准备了一个README文档,查看这个文档就可以看到所有文件的使用方式和具体含义
1 | cat /sys/kernel/debug/tracing/README |
通用配置:
available_tracers
当前编译及内核的跟踪器列表,current_tracer
必须是这里面支持的跟踪器current_tracer
:用于设置或者显示当前使用的跟踪器列表。系统启动缺省值为nop
,使用echo将跟踪器名字写入即可打开。可以通过写入nop
重置跟踪器buffer_size_kb
:用于设置单个CPU所使用的跟踪缓存的大小。跟踪缓存为ring buffer
形式,如果跟踪太多,旧的信息会被新的跟踪信息覆盖掉。需要先将current_trace
设置为nop才可以buffer_total_size_kb
:显示所有的跟踪缓存大小,不同之处在于buffer_size_kb
是单个CPU的,buffer_total_size_kb
是所有CPU的和free_buffer
:此文件用于在一个进程被关闭后,同时释放ring buffer
内存,并将调整大小到最小值hwlat_detector/
:instances/
:创建不同的trace buffer
实例,可以在不同的trace buffers
中分开记录tracing_cpumask
:可以通过此文件设置允许跟踪特定CPU,二进制格式per_cpu
:CPU相关的trace信息,包括stats
、trace
、trace_pipe
和trace_pipe_raw
printk_formats
:提供给工具读取原始格式trace
的文件saved_cmdlines
:存放pid
对应的comm
名称作为ftrace
的cache
,这样ftrace
中不光能显示pid
还能显示comm
saved_cmdlines_size
:saved_cmdlines
的数目snapshot
:是对trace的snapshotecho 0
:清空缓存,并释放对应内存echo 1
:进行对当前trace进行snapshot,如没有内存则分配echo 2
:清空缓存,不释放也不分配内存
trace
:查看获取到的跟踪信息的接口,echo > trace
可以清空当前ring buffer
trace_pipe
:输出和trace
一样的内容,但是此文件输出trace
同时将ring buffer
中的内容删除,这样就避免了ring buffer
的溢出。可以通过cat trace_pipe > trace.txt &
保存文件trace_clock
:显示当前trace
的timestamp
所基于的时钟,默认使用local时钟local
:默认时钟;可能无法在不同CPU间同步global
:不同CUP间同步,但是可能比local慢counter
:这是一个跨CPU计数器,需要分析不同CPU间event顺序比较有效
trace_marker
:从用户空间写入标记到trace中,用于用户空间行为和内核时间同步trace_marker_raw
:以二进制格式写入到trace中trace_options
:控制trace
打印内容或者操作跟踪器,可以通过trace_options
添加很多附加信息options
:trace
选项的一系列文件,和trace_options
对应trace_stat/
:每个CPU的trace
统计信息tracing_max_latency
:记录tracer
的最大延时tracing_on
:用于控制跟踪打开或停止echo 0
:停止跟踪echo 1
:继续跟踪
tracing_thresh
:延时记录trace
的阈值,当延时超过此值时才开始记录trace
。单位是ms,只有非0才起作用
events配置:
available_events
:列出系统中所有可用的trace events
,分两个层级,用冒号隔开events/
:系统trace events
目录,在每个events
下面都有enable
、filter
和fotmat
。enable
是开关;format
是events
的格式,然后根据格式设置filter
set_event
:将trace events
名称直接写入set_event
就可以打开set_event_pid
:指定追踪特定进程的events
function配置:
available_filter_functions
:记录了当前可以跟踪的内核函数,不在该文件中列出的函数,无法跟踪其活动dyn_ftrace_total_info
:显示available_filter_functins
中跟中函数的数目,两者一致enabled_functions
:显示有回调附着的函数名称function_profile_enabled
:打开此选项,在trace_stat
中就会显示function
的统计信息set_ftrace_filter
:用于显示指定要跟踪的函数set_ftrace_notrace
:用于指定不跟踪的函数,缺省为空set_ftrace_pid
:用于指定要追踪特定进程的函数
function graph配置:
max_graph_depth
:函数嵌套的最大深度set_graph_function
:设置要清晰显示调用关系的函数,在使用function_graph
跟踪器是使用,缺省对所有函数都生成调用关系set_graph_notrace
:不跟踪特定的函数嵌套调用
Stack trace设置:
stack_max_size
:当使用stack
跟踪器时,记录产生过的最大stack size
stack_trace
:显示stack
的back trace
stack_trace_filter
:设置stack tracer
不检查的函数名称
4.3 trace-cmd
1 | # 该命令会在当前目录下生成一个trace.dat文件 |
4.4 Reference
- Linux ftrace框架介绍及运用
- Ftrace Linux Kernel Tracing(论文)
- ftrace和trace-cmd:跟踪内核函数的利器
- 使用 ftrace 跟踪内核
- 【Kernel ftrace】使用kernel ftrace追踪IRQ的例子
- 使用 ftrace 来跟踪系统问题 - ftrace 介绍
5 dump
5.1 kdump
如何模拟内核crash?执行下面这个命令即可
1 | # 执行完后,会在/var/crash目录下生成dump文件,并会重启机器 |
下载分析crash文件所需的rpm包
1 | # 首先,我们需要下载带有完整调试信息的内核映像文件(编译时带-g选项),内核调试信息包kernel-debuginfo有两个 |
如何分析系统crash文件
1 | crash /lib/debug/lib/modules/`uname -r`/vmlinux /var/crash/127.0.0.1-2021-07-24-22\:59\:34/vmcore |
bt
:backtrace打印内核栈回溯信息,bt pid
打印指定进程栈信息- 最重要的是RIP信息,指出了发生crash的
function
以及offset
- 最重要的是RIP信息,指出了发生crash的
log
:打印vmcore所在的系统内核日志信息dis
:反汇编出指令所在代码开始,dis -l (function+offset)
,其中function+offset
可以是bt
中RIP对应的信息- 示例:
dis -l sysrq_handle_crash+22
- 示例:
mod
:查看当时内核加载的所有内核模块信息sym
:将地址转换为符号信息,其中地址可以是bt
中RIP对应的信息- 示例:
sym ffffffff8d26d9b6
- 示例:
ps
:打印内核崩溃时,正常的进程信息files
:files pid
打印指定进程所打开的文件信息vm
:vm pid
打印某指定进程当时虚拟内存基本信息task
:查看当前进程或指定进程task_struct
和thread_info
的信息kmem
:查看当时系统内存使用信息- 上述命令的详细用法可以通过
help <cmd>
- 其他命令可以通过
help
查看
5.2 coredump
core dump
又叫核心转储,当程序运行过程中发生异常,程序异常退出时, 由操作系统把程序当前的内存状况存储在一个core文件中,叫core dump
产生core dump
的可能原因
- 内存访问越界
- 多线程程序使用了线程不安全的函数
- 多线程读写的数据未加锁保护
- 非法指针
- 使用空指针
- 随意使用指针转换
- 堆栈溢出
- …
与core dump
相关的配置项
ulimit -c
:若是0,则不支持,可以通过ulimit -c unlimited
或者ulimit -c <size>
来开启- 或者通过编辑
/etc/security/limits.conf
文件来使配置永久生效 echo "* soft core unlimited" >> /etc/security/limits.conf
echo "* hard core unlimited" >> /etc/security/limits.conf
- 或者通过编辑
/proc/sys/kernel/core_pattern
:core dump
的存储路径echo "core" > /proc/sys/kernel/core_pattern
:默认是core
,若程序产生core dump
,那么其存放路径位于当前路径echo "core.%e.%p" > /proc/sys/kernel/core_pattern
:若程序产生core dump
,那么其存放路径位于当前路径。其中%e
表示二进制名称,%p
表示进程id(或线程名)echo "/data/coredump/core.%e.%p" > /proc/sys/kernel/core_pattern
:也可以使用绝对路径,统一存放到指定目录
/proc/sys/kernel/core_pipe_limit
/proc/sys/kernel/core_uses_pid
:如果这个文件的内容被配置成1
,那么即使core_pattern
中没有设置%p
,最后生成的core dump
文件名仍会加上进程id
MacOS
平台下,相关的配置项
ulimit -c
:若是0,则不支持,可以通过ulimit -c unlimited
或者ulimit -c <size>
来开启sudo sysctl -w kern.corefile=core.%N.%P
- 默认的配置是
/cores/core.%P
,无法正常工作
- 默认的配置是
如何分析
1 | # 其中可执行程序<binary>需要通过-g参数编译而来,这样会带上debug信息,才能分析core dump文件 |
5.3 Reference
6 Source Code Analysis
6.1 syscall
系统调用的声明位于include/linux/syscall.h
文件中,但是通过vs code等文本编辑工具无法跳转到定义处,这是因为系统调用的定义使用了非常多的宏
如何找到系统调用的定义:举个例子,对于系统调用open
,它有3个参数,那么就全局搜索SYSCALL_DEFINE3(open
;对于系统调用openat
,它有4个参数,那么就全局搜索SYSCALL_DEFINE4(openat
6.1.1 Reference
6.2 network
6.2.1 tcp
socket对应的file_operations
对象为socket_file_ops
tcp对应的proto_ops
对象为inet_stream_ops
6.2.1.1 create socket
1 | sys_socket | net/socket.c SYSCALL_DEFINE3(socket |
6.2.1.2 write socket
1 | # syscall |
6.2.1.3 read socket
数据从网卡设备流入
1 | # link |
通过系统调用阻塞读取到达的数据
1 | # syscall |
6.2.2 ip
1 | net/ipv4/ip_input.c |
6.2.3 Reference
- Linux 网络协议栈开发(五)—— 二层桥转发蓝图(上)
- 计算机网络基础 — Linux 内核网络协议栈
- TCP/IP协议栈之数据包如何穿越各层协议(绝对干货)
- Linux内核网络栈源代码分析(专栏)
- linux 内核tcp接收数据的实现
- Linux tcp/ip 源码分析 - socket
- linux TCP发送源码学习(1)–tcp_sendmsg
- tcp/ip协议栈–tcp数据发送流程
- 最详细的Linux TCP/IP 协议栈源码分析
- kernel-tcp注释