linux系统中有一个很有意思的共享库,名字为linux-vdso.so.1,这个库我们在rootfs中找不到实体,但是每个elf文件都需要链接它。之前和同事讨论的时候,同事想要了解elf的运行原理,我顺便提出了vDSO的这个东西,elf不必多说,相信大家都清楚,本文本着普及了解vDSO的目的,介绍一下什么是vDSO,以及深入了解vDSO。
vDSO是virtual dynamic shared object
,也就是虚拟的动态链接库。
关于vDSO的解释,第一次看到的时候是如下文章,讲解的很仔细,可以看看:
https://www.kernel.org/doc/Documentation/ABI/stable/vdso
对于更详细的文章,可以看如下:
https://lwn.net/Articles/615809/
根据链接的意思,对于每个应用程序,会主动加载vDSO程序到进程空间,这样提供高度优化的syscall方案,也就是加快了系统的syscall的调用性能。
关于arm的实现,我们可以查看如下ppt
对于系统中的vDSO,我们两个地方可以查看。以systemd为例
# ldd /usr/bin/systemd linux-vdso.so.1 (0x0000007f8888a000)
这里我们看到程序未运行时,默认有一个linux-vdso.so.1加载地址
# cat /proc/1/maps | grep "vdso\|vvar" 7f93766000-7f93768000 r--p 00000000 00:00 0 [vvar] 7f93768000-7f93769000 r-xp 00000000 00:00 0 [vdso]
可以看到systemd运行的时候,实际的重定向后的地址是0x7f93768000,这个地址小于ld-2.31,大于其他动态链接库。
7f93736000-7f93737000 rw-p 0000b000 b3:04 143061 /usr/lib/aarch64-linux-gnu/libdrm-cursor.so.1.0.0 7f93737000-7f93738000 rw-p 00000000 00:00 0 7f93738000-7f93759000 r-xp 00000000 b3:04 141832 /usr/lib/aarch64-linux-gnu/ld-2.31.so 7f93759000-7f93765000 rw-p 00000000 00:00 0 7f93766000-7f93768000 r--p 00000000 00:00 0 [vvar] 7f93768000-7f93769000 r-xp 00000000 00:00 0 [vdso] 7f93769000-7f9376a000 r--p 00021000 b3:04 141832 /usr/lib/aarch64-linux-gnu/ld-2.31.so 7f9376a000-7f9376c000 rw-p 00022000 b3:04 141832 /usr/lib/aarch64-linux-gnu/ld-2.31.so
所以这里我们可以获取两个信息:
带着上面的结论,我们可以知道,这个文件应该是在内核的,所以其位置如下:
arch/arm64/kernel/vdso/vdso.so
因为其是动态链接文件,所以是标准的elf文件,我们可以如下查看:
# readelf -l vdso.so Elf 文件类型为 DYN (共享目标文件) Entry point 0x320 There are 4 program headers, starting at offset 64 程序头: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000990 0x0000000000000990 R E 0x10 DYNAMIC 0x0000000000000860 0x0000000000000860 0x0000000000000860 0x0000000000000110 0x0000000000000110 R 0x8 NOTE 0x00000000000002c8 0x00000000000002c8 0x00000000000002c8 0x0000000000000054 0x0000000000000054 R 0x4 GNU_EH_FRAME 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x8 Section to Segment mapping: 段节... 00 .hash .dynsym .dynstr .gnu.version .gnu.version_d .note .text .eh_frame .dynamic .got .got.plt 01 .dynamic 02 .note 03
我们看看其中的符号,如下
# readelf -s vdso.so Symbol table '.dynsym' contains 7 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 OBJECT GLOBAL DEFAULT ABS LINUX_2.6.39 2: 0000000000000780 108 FUNC GLOBAL DEFAULT 7 __kernel_clock_getres@@LINUX_2.6.39 3: 00000000000007f0 8 NOTYPE GLOBAL DEFAULT 7 __kernel_rt_sigreturn@@LINUX_2.6.39 4: 00000000000005c0 424 FUNC GLOBAL DEFAULT 7 __kernel_gettimeofday@@LINUX_2.6.39 5: 0000000000000320 664 FUNC GLOBAL DEFAULT 7 __kernel_clock_gettime@@LINUX_2.6.39
可以发现,这个so就提供了四个函数符号。
也就是说,如果程序调用这四个符号,则默认优先调用vdso,而不是直接系统调用
为了测试验证vDSO的功能,我们以gettimeofday为例,编写程序,用于测试vDSO,如下是代码
#include <sys/syscall.h> #include <sys/time.h> #include <sys/auxv.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> int main(int argc, char *argv[]) { struct timeval tv; int i; unsigned int loop; unsigned long sysinfo_ehdr = getauxval(AT_SYSINFO_EHDR); if(argc==1 || (strcmp(argv[1], "--help")==0)){ printf("Usage:\n"); printf("\t %s %s %s %s\n", argv[0], "vdso|syscall", "count", "loop"); return 0; } if (argc == 3) loop = atoi(argv[2]); else { loop = 1000; } printf("pid=%d sysinfo_ehdr(vdso_addr)=%#lx \n", getpid(), sysinfo_ehdr); if (strcmp(argv[1], "vdso") == 0) { int (*ptr)(struct timeval *, void *) = gettimeofday; printf("gettimeofday addr=%p \n", ptr); for (i = 0; i < loop; i++){ gettimeofday(&tv, NULL); } } else if (strcmp(argv[1], "syscall") == 0){ for (i = 0; i < loop; i++) syscall(__NR_gettimeofday, &tv, NULL); } if(argc == 4 && (strcmp(argv[3], "loop")==0)){ while(1){ sleep(60); } } return 0; }
默认此程序如下提示:
# ./test_vdso Usage: ./test_vdso vdso|syscall count loop
我们可以进行两项基准测试:
vdso syscall
count代表循环的次数,loop代表是否进入死循环。
我们以1次的vdso测试,如下:
# ./test_vdso vdso 1 loop pid=53329 sysinfo_ehdr(vdso_addr)=0x7f96689000 gettimeofday addr=0x7f966895c0
我们拿到了两个地址,一个是vdso_addr=0x7f96689000,一个是函数符号地址 gettimeofday=0x7f966895c0
此时我们可以查看maps,如下:
# cat /proc/$(pidof test_vdso)/maps | grep "\[vdso\]" 7f96689000-7f9668a000 r-xp 00000000 00:00 0 这里看到0x7f96689000能够对应上AT_SYSINFO_EHDR
此时我们查看gettimeofday的符号地址如下:
00000000000005c0 424 FUNC GLOBAL DEFAULT 7 __kernel_gettimeofday@@LINUX_2.6.39
可以发现其计算如下:
0x7f966895c0 = 0x7f96689000 + 00000000000005c0
我们使用ltrace定位如下:
# ltrace ./test_vdso vdso 1 __libc_start_main(0x55756a0a9c, 3, 0x7fddd9a658, 0x55756a0cc0 <unfinished ...> getauxval(33, 0, 0x7fddd9a678, 0x55756a0a9c) = 0x7f80bba000 strcmp("vdso", "--help") = 73 atoi(0x7fddd9b4f9, 0x55756a0d61, 118, 45) = 1 getpid() = 59791 printf("pid=%d sysinfo_ehdr(vdso_addr)=%"..., 59791, 0x7f80bba000pid=59791 sysinfo_ehdr(vdso_addr)=0x7f80bba000 ) = 48 strcmp("vdso", "vdso") = 0 printf("gettimeofday addr=%p \n", 0x7f80bba5c0gettimeofday addr=0x7f80bba5c0 ) = 32 gettimeofday(0x7fddd9a4e8, 0) = 0 __cxa_finalize(0x55756b2008, 0x55756a0a50, 0x11d20, 1) = 0x7f80b4ec70 +++ exited (status 0) +++
这里看到ltrace调用能够定位到其调用了动态库的gettimeofday函数。我们strace查看调用如下:
strace ./test_vdso vdso 1 2>&1 | grep "gettimeofday("
可以发现vdso的时候,调用gettimeofday并不会产生系统调用。
至此,我们可以知道,代码里gettimeofday(&tv, NULL);的调用就是调用的vdso.so里面的__kernel_gettimeofday@@LINUX_2.6.39
此时我们将count放大为1亿次调用,统计时间如下:
# time ./test_vdso vdso 100000000 pid=58465 sysinfo_ehdr(vdso_addr)=0x7f8e1b8000 gettimeofday addr=0x7f8e1b85c0 real 0m3.946s user 0m3.940s sys 0m0.007s 可以发现,用时3.946s
我们以1次的syscall测试,通过strace查看系统调用,如下:
# strace ./test_vdso syscall 1 2>&1 | grep "gettimeofday(" gettimeofday({tv_sec=1734333991, tv_usec=512630}, NULL) = 0
可以发现其通过syscall下发的gettimeofday。
我们尝试看看ltrace的信息
# ltrace ./test_vdso syscall 1 2>&1 __libc_start_main(0x558d780a9c, 3, 0x7fe207c958, 0x558d780cc0 <unfinished ...> getauxval(33, 0, 0x7fe207c978, 0x558d780a9c) = 0x7fb95b3000 strcmp("syscall", "--help") = 70 atoi(0x7fe207d4f9, 0x558d780d61, 115, 45) = 1 getpid() = 63596 printf("pid=%d sysinfo_ehdr(vdso_addr)=%"..., 63596, 0x7fb95b3000pid=63596 sysinfo_ehdr(vdso_addr)=0x7fb95b3000 ) = 48 strcmp("syscall", "vdso") = -3 strcmp("syscall", "syscall") = 0 syscall(169, 0x7fe207c7e8, 0, 0x11b033b440000) = 0 __cxa_finalize(0x558d792008, 0x558d780a50, 0x11d20, 1) = 0x7fb9547c70 +++ exited (status 0) +++
可以发现ltrace这里没有gettimeofday。
此时我们将count放大为1亿次调用,统计时间如下:
# time ./test_vdso syscall 100000000 pid=64134 sysinfo_ehdr(vdso_addr)=0x7fb55c0000 real 0m16.279s user 0m3.927s sys 0m12.352s
可以发现,用时16.279s,主要耗时在syscall上。
我们可以发现,对于vDSO而言,linux设计了一个动态库,使其默认通过vDSO的共享地址调用函数,而不需要使用系统调用,其在1亿次为基准的情况下能够是syscall的5-6倍的性能提升。
tracepoint是驱动编程必学的小技巧,它依附于ftrace系统,本文介绍一个最简单的tracepoint,方便大家在编写subsystem/driver时,为自己的程序安插tracepoint,后面同样会介绍linux中预置的trace event的使用方法。
我们之前聊过blk_update_request
是块设备层的经典函数,在文件系统中,如果读取一个文件,它会先submit_bio
来提交到block层,此后,blk_update_request
用于更新block的request信息后续直接处理IO。
我们以blk_update_request为例,测试一下其本身的tracepoint,如下:
vim block/blk-core.c
先查看头文件
#define CREATE_TRACE_POINTS #include <trace/events/block.h>
此时找到这个h文件,查看如下:
#undef TRACE_SYSTEM #define TRACE_SYSTEM block
然后查看具体函数代码:
bool blk_update_request(struct request *req, blk_status_t error, unsigned int nr_bytes) { int total_bytes; trace_block_rq_complete(req, blk_status_to_errno(error), nr_bytes); if (!req->bio) return false;
我们留意trace_block_rq_complete(req, blk_status_to_errno(error), nr_bytes);
此时我们知道tracepoint是block下的block_rq_complete
根据上面的信息我们找一下路径如下,我们直接进入:
cd /sys/kernel/tracing/events/block/block_rq_complete/
此时我们正常enable即可,
echo 1 > enable
此时我们监听ftrace的日志即可
cat /sys/kernel/debug/tracing/trace_pipe
我们可以看到日志如下:
# cat /sys/kernel/debug/tracing/trace_pipe <idle>-0 [000] ..s. 104.385029: block_rq_complete: 179,0 WS () 13430392 + 96 [0] kworker/3:2H-610 [003] .... 104.385386: block_rq_complete: 179,0 FF () 18446744073709551615 + 0 [0] sshd-3368 [000] ..s. 104.385710: block_rq_complete: 179,0 WFS () 13430488 + 8 [0] sshd-3368 [000] d.s. 104.385733: block_rq_complete: 179,0 WFS () 13430488 + 0 [0] <idle>-0 [000] ..s. 105.877799: block_rq_complete: 179,0 W () 17460720 + 8 [0]
至此tracepoint的演示完成了。其他文章会更详细的演示linux已有的tracepoint
为了在自己的驱动写tracepoint,我们需要注意如下几点:
不清楚的可以参考如下:
https://www.kernel.org/doc/html/latest/trace/tracepoints.html
我们可以编写一个最简单的驱动,使用kthread拉起函数,如下:
#include <linux/init.h> #include <linux/module.h> #include <linux/kthread.h> #include <linux/delay.h> #define CREATE_TRACE_POINTS #include "trace/events/kylin.h" static struct task_struct *thread1; static void test_tracepoint(void) { pr_info("%s starting...\n", __func__); trace_test_kylin("test"); } static int thread_1(void *arg) { while(!kthread_should_stop()){ schedule_timeout_interruptible(msecs_to_jiffies(1000)); test_tracepoint(); } return 0; } static void start_test(void) { thread1 = kthread_run(thread_1, "Thread", "tracepoint-test"); return ; } static int __init test_init(void) { start_test(); return 0; } static void __exit test_exit(void) { kthread_stop(thread1); return; } module_init(test_init); module_exit(test_exit); MODULE_AUTHOR("tangfeng <tangfeng@kylinos.cn>"); MODULE_DESCRIPTION("Test tracepoint"); MODULE_LICENSE("GPL");
这里细节后面可以介绍,先简单看看即可。
vim include/trace/events/kylin.h
#undef TRACE_SYSTEM #define TRACE_SYSTEM kylin #if !defined(_TRACE_KYLIN_H) || defined(TRACE_HEADER_MULTI_READ) #define _TRACE_KYLIN_H #include <linux/tracepoint.h> TRACE_EVENT(test_kylin, TP_PROTO(const char *name), TP_ARGS(name), TP_STRUCT__entry( __field(const char *, name) ), TP_fast_assign( __entry->name = name ), TP_printk("kylin: %s", __entry->name) ); #endif #include <trace/define_trace.h>
我们需要注意以下几点:
关于TRACE_EVENT的编写,参考如下:
https://lwn.net/Articles/379903/ https://lwn.net/Articles/381064/ https://lwn.net/Articles/383362/
我们只需要关注TRACE_EVENT五要素:
对于我们的代码,如下定义:
TRACE_EVENT(test_kylin, TP_PROTO(const char *name), TP_ARGS(name), TP_STRUCT__entry( __field(const char *, name) ), TP_fast_assign( __entry->name = name ), TP_printk("kylin: %s", __entry->name) );
对于prototype:我们使用TP_PROTO的宏定义,传入需要跟踪的函数声明即可,我们打算跟踪的是带字符串的trace如下:
trace_test_kylin("test");
所以我们只需要一个const char* 即可
对于args:我们使用TP_ARGS的宏定义,这里传入参数name
对于struct:我们需要新增一个const char*的成员,故通过TP_STRUCT__entry 定义一个__field即可。
对于assign:我们使用TP_fast_assign来将参数赋值给结构体成员变量
对于print:我们使用TP_printk直接按照printk的方式打印结构体的成员变量
为了使得头文件能够生效,我们定义了TRACE_SYSTEM 为kylin
#undef TRACE_SYSTEM #define TRACE_SYSTEM kylin
这里就要求了头文件的存放位置为include/trace/events
,且名字是kylin
include/trace/events/kylin.h
为了在驱动中使用tracepoint,我们需要在include kylin.h之前添加CREATE_TRACE_POINTS的定义,如下:
#define CREATE_TRACE_POINTS #include "trace/events/kylin.h"
接下来我们就直接使用tracepoint即可,在函数直接安插:
static void test_tracepoint(void) { trace_test_kylin("test"); }
我们写了一个tracepoint,接下来是测试这个tracepoint,当ko加载时,我们可以发现ftrace的events下存在如下文件
# find /sys/kernel/debug/tracing/events/kylin/ /sys/kernel/debug/tracing/events/kylin/ /sys/kernel/debug/tracing/events/kylin/test_kylin /sys/kernel/debug/tracing/events/kylin/test_kylin/format /sys/kernel/debug/tracing/events/kylin/test_kylin/trigger /sys/kernel/debug/tracing/events/kylin/test_kylin/filter /sys/kernel/debug/tracing/events/kylin/test_kylin/id /sys/kernel/debug/tracing/events/kylin/test_kylin/enable /sys/kernel/debug/tracing/events/kylin/enable /sys/kernel/debug/tracing/events/kylin/filter
然后我们准备获取trace的内容,如下:
cat /sys/kernel/debug/tracing/trace_pipe
我们使得我们自己的tracepoint如下:
echo 1 > /sys/kernel/debug/tracing/events/kylin/enable
这样我们trace_pipe可以看到如下日志
# cat /sys/kernel/debug/tracing/trace_pipe | grep test_ thread-141765 [002] .... 9598.458509: test_kylin: kylin: test thread-141765 [002] .... 9599.471840: test_kylin: kylin: test
至此,一个tracepoint的演示完全完成。想必能够有效的帮助大家去编写driver
系统中存在很多linux内核默认预设的trace events,其目的是方便大家查相同问题时能够复用他们的tracepoint,基于此,本文章根据linux内核现成的trace event做分享,主要聊聊如何使用人家定义好的event,从而方便大家调试问题
trace events的目录如下:
# ls /sys/kernel/debug/tracing/events/ alarmtimer fib6 namei sched android_fs filelock napi scmi asoc filemap neigh signal avc ftrace net skb binder gadget nfs smbus block gpio nfs4 sock bpf_test_run header_event nfsd spi bpf_trace header_page nvme sunrpc bridge hwmon oom swiotlb btrfs i2c pagefault sync_trace cfg80211 initcall page_isolation task cgroup iomap pagemap tcp cifs iommu page_pool thermal clk io_uring percpu thermal_ipa_power cma ipi power thermal_power_allocator compaction irq printk timer cpufreq_interactive jbd2 pwm udp cpuhp kmem qdisc v4l2 devfreq kvm ras vb2 dma_fence kyber raw_syscalls virtio_gpu drm mac80211 rcu vmscan dwc3 mali regmap workqueue emulation mdio regulator writeback enable migrate rpcgss xdp error_report mmap rpm xfs ext4 mmc rseq xhci-hcd fib module rtc
我们可以看到linux提供了107个trace system,我们在查问题的时候可以先考虑这些trace system
这里还是以经典函数block_rq_complete为例,可以看到其内容如下:
# find /sys/kernel/debug/tracing/events/block/block_rq_complete/ /sys/kernel/debug/tracing/events/block/block_rq_complete/ /sys/kernel/debug/tracing/events/block/block_rq_complete/format /sys/kernel/debug/tracing/events/block/block_rq_complete/trigger /sys/kernel/debug/tracing/events/block/block_rq_complete/filter /sys/kernel/debug/tracing/events/block/block_rq_complete/id /sys/kernel/debug/tracing/events/block/block_rq_complete/enable
关于trace evemt的文章,可以查看内核官方文档如下,这里就不按照文档重复讨论了:
https://www.kernel.org/doc/html/latest/trace/events.html
本章主要聚焦在下面这几个文件的使用上:
enable filter format id trigger
顾名思义,这个就是打开此event的事件,方法如下:
echo 1 > enable
这里和tracepoint基础编程介绍有点重复,就无需展开了。
这里能够查看这个event的格式,我们在tracepoint基础编程介绍可以知道定义一个TRACE_EVENT需要包含五要素:name/prototype/args/struct/assign/print
,这里是具体展现。如下示例:
# cat format name: block_rq_complete ID: 1350 format: field:unsigned short common_type; offset:0; size:2; signed:0; field:unsigned char common_flags; offset:2; size:1; signed:0; field:unsigned char common_preempt_count; offset:3; size:1; signed:0; field:int common_pid; offset:4; size:4; signed:1; field:dev_t dev; offset:8; size:4; signed:0; field:sector_t sector; offset:16; size:8; signed:0; field:unsigned int nr_sector; offset:24; size:4; signed:0; field:int error; offset:28; size:4; signed:1; field:char rwbs[8]; offset:32; size:8; signed:0; field:__data_loc char[] cmd; offset:40; size:4; signed:0; print fmt: "%d,%d %s (%s) %llu + %u [%d]", ((unsigned int) ((REC->dev) >> 20)), ((unsigned int) ((REC->dev) & ((1U << 20) - 1))), REC->rwbs, __get_str(cmd), (unsigned long long)REC->sector, REC->nr_sector, REC->error
这里我们知道如下信息:
这个trace里面,结构体提供了从common_type到cmd的所有字段域 提供了这个struct的offset,也就是具体位置 提供了打印格式
这里提供了id,这个id代表这个trace的id,我们可以从format上看到,如下:
ID: 1350
这个id是只读的,所以我们只能读到这个id,用作filter过滤
# cat id 1350
这个用作过滤,我们可以知道,如果直接enable,我们没办法进行过滤,所以可以在这里添加多个过滤条件,如下:
假设我们只想看idle进程的信息,则如下:
echo "common_pid==0" > filter
如果想要关闭filter,直接echo 0即可
echo 0 > filter
注意,这里的filter只能是struct的成员,如果不是,就会失效,所以提前需要cat format看一下struct哪些成员可以过滤
trigger是trace默认提供的触发类型,我们可以通过cat获取可以使用的trigger类型,如下:
# cat trigger # Available triggers: # traceon traceoff stacktrace enable_event disable_event
这里提供了6个trigger,我们如果想要echo trigger,这里需要指定的语法格式,如下:
echo 'command[:count] [if filter]' > trigger
这里可以看到,我们需要填入command: trigger类型,count
次数 if filter 过滤条件。这里是检测过滤条件满足的时候,一定次数下直接打开此trace,如下示例
echo 'traceon:5 if common_pid==0' > trigger
这里意思是如果pid为idle下触发blk_update_request达到5次,则主动打开此trace event
echo 'traceoff:5 if common_pid==0' > trigger
这里意思是如果pid为idle下触发blk_update_request达到5次,则主动关闭此trace event
这里是打印此函数的堆栈,可以示例如下:
echo "stacktrace:1 if common_pid==0" > trigger
这里是当pid是idle的时候,打印一次堆栈,这样日志如下:
<idle>-0 [000] .Ns. 3780.543922: <stack trace> => trace_event_buffer_commit => trace_event_raw_event_block_rq_complete => blk_update_request => mmc_blk_cqe_complete_rq => mmc_blk_mq_complete => blk_done_softirq => __do_softirq => irq_exit => __handle_domain_irq => gic_handle_irq => el1_irq => cpuidle_enter_state => cpuidle_enter => call_cpuidle => do_idle => cpu_startup_entry => rest_init => arch_call_rest_init => start_kernel
这是打开对于的event的trigger,如下:
echo "enable_event:block:block_rq_complete:2" > trigger
当block_rq_complete被触发两次时,打开event
对于我们有关闭event的trigger,如下:
echo 'disable_event:block:block_rq_complete:2' > trigger
当block_rq_complete被触发两次时,关闭event
至此,我们简单了解了trace event的使用。
自己经常使用windows平台,通过工具putty和mtputty已经能够做到很好的切屏和操作,最近为了在调试openharmony的时候,经常需要编译,而putty放在后台的编译需要自己手拖,这不是很方便,最近同事分享了tmux,原来虽然自己了解过tmux,但是鉴于第一次学习时觉得比较复杂,就没有使用下来,最近恰巧网上系统的学习了tmux的技巧,编写了适用于自己的tmux配置,目前感觉tmux还是有一定的优势的,故分享自己的tmux配置和用途。
安装tmux比较简单,如下
apt install tmux
为了让tmux在我的环境良好的工作,我自己按照自己习惯设计了如下配置,将其填入
~/.tmux.conf
即可
# 参考:https://louiszhai.github.io/2017/09/30/tmux/ # ctrl+b alt+方向键 调整切屏大小 # 设置第二个prefix为` set-option -g prefix2 ` # 重载配置文件 unbind r bind r source-file ~/.tmux.conf \; display "Configuration Reloaded!" # 开启256 colors set -g default-terminal "screen-256color" # 按竖分割 unbind % bind | split-window -h # 按行分割 unbind '"' bind - split-window -v # 按竖分割,这里默认左边只有40 bind v split-window -v -l 40 # 按行分割,这里默认上面只有120 bind h split-window -h -l 120 # 状态栏左对齐 set -g status-justify left # 非当前窗口有内容更新时在状态栏通知 setw -g monitor-activity on # 关闭rename机制,提高性能 setw -g automatic-rename off setw -g allow-rename off # 绑定C为清空缓冲区 bind C send-keys -R \; send-keys C-l \; clear-history \; send-keys
tmux默认使用ctrl+b作为命令前缀,可以设置第二个命令前缀为`,这样敲快捷键的时候,就不用一直敲ctrl+b那么麻烦了
set-option -g prefix2 `
原始配置重新加载需要
ctrl+b : source-file ~/.tmux.conf
比较麻烦,这里使用r键作为快捷键,只需要
`+r
即可,如下是配置详情
unbind r bind r source-file ~/.tmux.conf \; display "Configuration Reloaded!"
通常来说,我们的terminal带点颜色好看的
为了快捷按键按竖分割,这里设置了|,所以我们可以如下
`+|
实现按竖分割
效果如下
如果我们是看代码,可以按照代码行数分割,如下
此时命令是
`+h
为了快捷按键按行分割,这里设置了-,所以我们可以如下
`+-
效果如下
如果我们是在运行编译命令,则可以将编译命令放上面,如下
`+v
效果如下
这样上面编译代码,下面看代码就行了
当我们开了多窗口下,我们还是需要切换窗口,可以使用如下命令
`+alt+(上下左右)
如果我们开了多个tmux,我们可以使用n来切换,如下
`+n
通过C命令可以清空缓冲区,如下
`+C
如果我们需要检索缓冲区的内容,可以通过如下
`+[ # 进入查看模式 `+s # 开始搜索
在调试内核时,我们有时候需要更详细的内容展示,这时候crash工具就上场了,crash工具在RK平台默认是不可用的,本文主要说明在rk平台上麒麟系统开启crash工具的基本方法
我们直接安装一个依赖空包即可
apt install linux-crashdump crash
这个包会主动安装
apt install kdump-tools kexec-tools makedumpfile crash
此时我们查看如下工具正常运行即可:
# crash --version crash 7.2.8 Copyright (C) 2002-2020 Red Hat, Inc. Copyright (C) 2004, 2005, 2006, 2010 IBM Corporation Copyright (C) 1999-2006 Hewlett-Packard Co Copyright (C) 2005, 2006, 2011, 2012 Fujitsu Limited Copyright (C) 2006, 2007 VA Linux Systems Japan K.K. Copyright (C) 2005, 2011 NEC Corporation Copyright (C) 1999, 2002, 2007 Silicon Graphics, Inc. Copyright (C) 1999, 2000, 2001, 2002 Mission Critical Linux, Inc. This program is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Enter "help copying" to see the conditions. This program has absolutely no warranty. Enter "help warranty" for details. GNU gdb (GDB) 7.6 Copyright (C) 2013 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "aarch64-unknown-linux-gnu".
# kexec --version kexec-tools 2.0.29
# kdump-config --help Usage: kdump-config {help|test|show|status|load|unload|savecore|propagate|symlinks kernel-version}" help - print this page test - Do a dry-run of kdump kernel load command by showing the kernels and parameters that will be used and echo'ing the kexec command. The kexec command will not be executed. If using fadump, check if required sysfs directories exist. show - Show dump mode, status, any current parameters. Show kexec command for kdump. status - evaluate /sys/kernel/{kexec_crash_loaded,fadump_registered} depending on dump mode. Print appropriate message load - Locate the kdump kernel, debug kernel, and establish links for makedumpfile. Then load the kdump kernel using kexec If using fadump, register. unload - unload the kdump kernel using kexec If using fadump, unregister. savecore - use previously made links to save /proc/vmcore propagate - Send public ssh key to remote host for passwordless connection symlinks - Verify and create vmlinuz and initrd.img links in /var/lib/kdump according to the provided kernel version. If the target initrd.img file is absent, it will create it.
接下来我们会用到crash工具
为了使得crash工具正常,我们需要打开内核的配置,如下:
CONFIG_KEXEC=y CONFIG_SYSFS=y CONFIG_DEBUG_INFO=y CONFIG_CRASH_DUMP=y CONFIG_PROC_VMCORE=y
为了vmcore,我们可以在bootargs添加crashkernel=256M,用作core的收集
重新编译内核即可。 我们需要boot.img和vmlinux两个文件
上述准备好了之后,我们还需要vmlinux用来加载crash,如下:
crash ./vmlinux /proc/kcore
稍等一会儿,我们加载好所有符号即可开始crash调试
crash 7.2.8 Copyright (C) 2002-2020 Red Hat, Inc. Copyright (C) 2004, 2005, 2006, 2010 IBM Corporation Copyright (C) 1999-2006 Hewlett-Packard Co Copyright (C) 2005, 2006, 2011, 2012 Fujitsu Limited Copyright (C) 2006, 2007 VA Linux Systems Japan K.K. Copyright (C) 2005, 2011 NEC Corporation Copyright (C) 1999, 2002, 2007 Silicon Graphics, Inc. Copyright (C) 1999, 2000, 2001, 2002 Mission Critical Linux, Inc. This program is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Enter "help copying" to see the conditions. This program has absolutely no warranty. Enter "help warranty" for details. GNU gdb (GDB) 7.6 Copyright (C) 2013 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "aarch64-unknown-linux-gnu"... please wait... (patching 189292 gdb minimal_symbol values) SYSTEM MAP: /boot/System.map DEBUG KERNEL: /root/vmlinux (5.10.198) DUMPFILE: /proc/kcore CPUS: 8 DATE: Mon Nov 25 17:36:10 2024 UPTIME: 00:12:52 LOAD AVERAGE: 0.73, 0.35, 0.21 TASKS: 659 NODENAME: kylin RELEASE: 5.10.198 VERSION: #4 SMP Tue Nov 26 00:17:27 CST 2024 MACHINE: aarch64 (unknown Mhz) MEMORY: 4 GB PID: 15712 COMMAND: "crash" TASK: ffffff8062f84880 [THREAD_INFO: ffffff8062f84880] CPU: 6 STATE: TASK_RUNNING (ACTIVE) crash>
为了简单演示crash,我随便介绍几个示例,有兴趣了解crash的,可以私下咨询,或查看官方文档如下:
https://crash-utility.github.io/crash_whitepaper.html
crash> p saved_command_line saved_command_line = $1 = 0xffffff81feee7dc0 "storagemedia=emmc androidboot.storagemedia=emmc androidboot.mode=normal dsi-0=2 storagenode=/mmc@fe2e0000 androidboot.verifiedbootstate=orange androidboot.serialno=cc45dde649cc1b19 ro rootwait earlycon=uart8250,mmio32,0xfeb50000 console=ttyFIQ0 irqchip.g"...
crash> dis blk_update_request 0xffffffc0087d2fb8 <blk_update_request>: mov x9, x30 0xffffffc0087d2fbc <blk_update_request+4>: nop 0xffffffc0087d2fc0 <blk_update_request+8>: sub sp, sp, #0x60 0xffffffc0087d2fc4 <blk_update_request+12>: stp x29, x30, [sp,#16] 0xffffffc0087d2fc8 <blk_update_request+16>: add x29, sp, #0x10 0xffffffc0087d2fcc <blk_update_request+20>: stp x19, x20, [sp,#32] ....................
crash> struct task_struct ffffff8026600000 struct task_struct { thread_info = { flags = 0, addr_limit = 549755813887, { preempt_count = 4294967296, preempt = { count = 0, need_resched = 1 } } }, state = 264, stack = 0xffffffc00f248000, .......................
假设我们有驱动如下操作:
test = kmalloc(128, GFP_KERNEL);; memcpy(test, "helloworld", 128);
此时我们加载ko,获取test 的虚拟地址0xffffff806f63eb00,crash内直接打印即可
crash> rd -8 0xffffff806f63eb00 10 ffffff806f63eb00: 68 65 6c 6c 6f 77 6f 72 6c 64 helloworld
至此,我们可以发现crash对于调试内核来说还是挺方便的。