编辑
2025-01-20
记录知识
0

为了调测和学习linux kernel,我们可以使用kgdb,关于kgdb一直没有相关文档,最近有同事产生疑问,故基于rk3399的经验上,在rk3588上尝试使用kgdb来进行内核调试

一、什么是kgdb

我相信大家都了解gdb,用gdb来调试linux 内核,在内核的实现叫做kgdb,kgdb需要host和target两台机器,并且只能通过串口。对于想要进一步了解的,可以查看如下文章,文章内容不多

https://www.kernel.org/pub/linux/kernel/people/jwessel/kgdb/ch01.html

二、内核配置kgdb

为了内核打开kgdb,我们需要配置如下:

CONFIG_KGDB CONFIG_KGDB_SERIAL_CONSOLE CONFIG_KALLSYMS

这样,我们在启动后内核就有如下参数配置项

/sys/module/kgdboc/parameters/kgdboc

我们知道rk的tty叫做ttyFIQ0,所以我们可以设置kgdb管理此tty,如下:

echo ttyFIQ0 > /sys/module/kgdboc/parameters/kgdboc

此时我们主动触发linux进入debug模式即可,如下:

echo g > /proc/sysrq-trigger

然后关闭串口即可

三、arm64主机配置

为了能够使用kgdb,我们需要有一台arm64的机器,我这里是飞腾笔记本。

3.1 sshfs

通常,我们的代码放在了服务器,而我们笔记本如果想访问不是很方便,所以我们要借助sshfs来映射,如下:

mkdir ~/sshfs sshfs root@172.25.130.130:/root/public-workspace/tf/01-3588-x11/squashfs-root/root/kernel/ ~/sshfs

这样我们就能在主机上访问内核代码

3.2 gdb

这里值得注意的是,我们默认的gdb工具不支持1500000的波特率,所以我们需要重新编译gdb工具,补丁如下:

From 78d16865df671f80da8d0a97b18596ef8a3feae3 Mon Sep 17 00:00:00 2001 From: Dan Callaghan <dan.callaghan@morsemicro.com> Date: Mon, 8 May 2023 18:29:45 +1000 Subject: [PATCH] Support higher baud rates when they are defined On Linux at least, baud rate codes are defined up to B4000000. Allow the user to select them if they are present in the system headers. Change-Id: I393ff32e4a4b6127bdf97e3306ad5b6ebf7c934e --- gdb/ser-unix.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/gdb/ser-unix.c b/gdb/ser-unix.c index ede2a58308a..cdc0cf98b7b 100644 --- a/gdb/ser-unix.c +++ b/gdb/ser-unix.c @@ -327,6 +327,72 @@ baudtab[] = 460800, B460800 } , +#endif +#ifdef B500000 + { + 500000, B500000 + } + , +#endif +#ifdef B576000 + { + 576000, B576000 + } + , +#endif +#ifdef B921600 + { + 921600, B921600 + } + , +#endif +#ifdef B1000000 + { + 1000000, B1000000 + } + , +#endif +#ifdef B1152000 + { + 1152000, B1152000 + } + , +#endif +#ifdef B1500000 + { + 1500000, B1500000 + } + , +#endif +#ifdef B2000000 + { + 2000000, B2000000 + } + , +#endif +#ifdef B2500000 + { + 2500000, B2500000 + } + , +#endif +#ifdef B3000000 + { + 3000000, B3000000 + } + , +#endif +#ifdef B3500000 + { + 3500000, B3500000 + } + , +#endif +#ifdef B4000000 + { + 4000000, B4000000 + } + , #endif { -1, -1

此补丁来源gdb上游,如下地址,大家自行合并即可

https://gitlab.com/gnutools/binutils-gdb/-/commit/78d16865df671f80da8d0a97b18596ef8a3feae3

这样我们将gdb拉起sshfs的vmlinux如下:

gdb vmlinux

由于我们的内核配置波特率是1500000,和默认的115200不一致,我们需要单独设置,如下:

set serial baud 1500000

然后直接连接连接kgdb的target即可,如下:

target remote /dev/ttyUSB0

这样我们就正常的kgdb调试linux kernel了。

(gdb) target remote /dev/ttyUSB0 Remote debugging using /dev/ttyUSB0 arch_kgdb_breakpoint () at ./arch/arm64/include/asm/kgdb.h:21 21 asm ("brk %0" : : "I" (KGDB_COMPILED_DBG_BRK_IMM));

3.3 el1_irq异常

关于kgdb的正常使用,有一笔patch一直没办法合入,主要原因是step调试如果禁用中断了,本身就相当于修改了内核行为,它是不应该的,关于具体的内容,可以查看如下:

https://patchwork.kernel.org/project/linux-arm-kernel/patch/20170523043058.5463-3-takahiro.akashi@linaro.org/

关于此讨论,主要是如下补丁:

root@kylin:~/sshfs/kernel# git diff arch/arm64/kernel/kgdb.c diff --git a/arch/arm64/kernel/kgdb.c b/arch/arm64/kernel/kgdb.c index e4e95821b1f6..932b0d9d592f 100644 --- a/arch/arm64/kernel/kgdb.c +++ b/arch/arm64/kernel/kgdb.c @@ -15,10 +15,13 @@ #include <linux/kprobes.h> #include <linux/sched/task_stack.h> +#include <asm/ptrace.h> #include <asm/debug-monitors.h> #include <asm/insn.h> #include <asm/traps.h> +static DEFINE_PER_CPU(unsigned int, kgdb_pstate); + struct dbg_reg_def_t dbg_reg_def[DBG_MAX_REG_NUM] = { { "x0", 8, offsetof(struct pt_regs, regs[0])}, { "x1", 8, offsetof(struct pt_regs, regs[1])}, @@ -206,6 +209,10 @@ int kgdb_arch_handle_exception(int exception_vector, int signo, err = 0; break; case 's': + /* mask interrupts while single stepping */ + __this_cpu_write(kgdb_pstate, linux_regs->pstate); + linux_regs->pstate |= PSR_I_BIT; + /* * Update step address value with address passed * with step packet. @@ -251,9 +258,18 @@ NOKPROBE_SYMBOL(kgdb_compiled_brk_fn); static int kgdb_step_brk_fn(struct pt_regs *regs, unsigned int esr) { + unsigned int pstate; + if (!kgdb_single_step) return DBG_HOOK_ERROR; + /* restore interrupt mask status */ + pstate = __this_cpu_read(kgdb_pstate); + if (pstate & PSR_I_BIT) + regs->pstate |= PSR_I_BIT; + else + regs->pstate &= ~PSR_I_BIT; + kgdb_handle_exception(0, SIGTRAP, 0, regs); return DBG_HOOK_HANDLED; }

上面代码很容易理解,我们知道AARCH64有PSSTATE寄存器

armv8_arm_v8.6.pdf D1.7 Process state, PSTATE .................................................................................. D1-2330

这里描述如下:

image.png 这里简单直接的在step的时候禁用了IRQ。

这里我们为了gdb能够step调试,能够承担这样的风险,所以合并了这个patch。

四、基本演示

当一切准备好了之后,我们直接看到如下信息:

# gdb vmlinux GNU gdb (Kylin 9.1-0kylin1) 9.1 Copyright (C) 2020 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-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from vmlinux... (gdb) set serial baud 1500000 (gdb) target remote /dev/ttyUSB0 Remote debugging using /dev/ttyUSB0 arch_kgdb_breakpoint () at ./arch/arm64/include/asm/kgdb.h:21 21 asm ("brk %0" : : "I" (KGDB_COMPILED_DBG_BRK_IMM)); (gdb) 这里以我常用的do_sys_openat2为例子,如下: (gdb) b do_sys_openat2 Breakpoint 1 at 0xffffffc0082c78e0: do_sys_openat2. (2 locations) (gdb) c Continuing. [Switching to Thread 669] Thread 238 hit Breakpoint 1, 0xffffffc0082c78e0 in do_sys_openat2 () at fs/open.c:1098 1098 if (flags & O_DIRECTORY)

堆栈如下:

(gdb) bt #0 0xffffffc0082c78e0 in do_sys_openat2 () at fs/open.c:1098 #1 0xffffffc0082c7e7c in do_sys_open (mode=<optimized out>, flags=<optimized out>, filename=<optimized out>, dfd=<optimized out>) at fs/open.c:1002 #2 __do_sys_openat (mode=<optimized out>, flags=<optimized out>, filename=<optimized out>, dfd=<optimized out>) at fs/open.c:1249 #3 __se_sys_openat (mode=<optimized out>, flags=<optimized out>, filename=<optimized out>, dfd=<optimized out>) at fs/open.c:1244 #4 __arm64_sys_openat (regs=<optimized out>) at fs/open.c:1244 #5 0xffffffc008026074 in __invoke_syscall (syscall_fn=<optimized out>, regs=0xffffffc00dd4beb0) at arch/arm64/kernel/syscall.c:48 #6 invoke_syscall (syscall_table=0xffffffc009510888 <sys_call_table>, sc_nr=449, scno=<optimized out>, regs=0xffffffc00dd4beb0) at arch/arm64/kernel/syscall.c:48 #7 el0_svc_common (regs=0xffffffc00dd4beb0, scno=<optimized out>, syscall_table=0xffffffc009510888 <sys_call_table>, sc_nr=449) at arch/arm64/kernel/syscall.c:155 #8 0xffffffc0080261a0 in do_el0_svc (regs=<optimized out>) at arch/arm64/kernel/syscall.c:194 #9 0xffffffc0094eea70 in el0_svc (regs=0xffffffc00dd4beb0) at arch/arm64/kernel/entry-common.c:357 #10 0xffffffc0094eefbc in el0_sync_handler (regs=<optimized out>) at arch/arm64/kernel/entry-common.c:373 #11 0xffffffc008011e18 in el0_sync () at arch/arm64/kernel/entry.S:788 Backtrace stopped: Cannot access memory at address 0xffffffc00dd4c0c8

这里可以看到很多optimized out,可能和内核默认的O2有关系,这里就没有演示把内核编译成O1了。

这里我们step一下看看,如下:

(gdb) s Thread 238 hit Breakpoint 1, do_sys_openat2 (dfd=-100, filename=0x55580b8700 <error: Cannot access memory at address 0x55580b8700>, how=how@entry=0xffffffc00dd4bdf8) at fs/open.c:1203 1203 {

可以看到这里信息一切准确。

我们看看汇编和代码

(gdb) disassemble Dump of assembler code for function do_sys_openat2: => 0xffffffc0082c78e0 <+0>: mov x9, x30 0xffffffc0082c78e4 <+4>: nop 0xffffffc0082c78e8 <+0>: stp x29, x30, [sp, #-80]! 0xffffffc0082c78ec <+4>: mov x29, sp 0xffffffc0082c78f0 <+8>: stp x19, x20, [sp, #16] 0xffffffc0082c78f4 <+12>: mrs x20, sp_el0 0xffffffc0082c78f8 <+16>: mov x19, x2 0xffffffc0082c78fc <+20>: stp x21, x22, [sp, #32] 0xffffffc0082c7900 <+24>: mov x21, x1 0xffffffc0082c7904 <+28>: mov w22, w0 0xffffffc0082c7908 <+32>: ldr x1, [x20, #1344] 0xffffffc0082c790c <+36>: str x1, [sp, #72] 0xffffffc0082c7910 <+40>: mov x1, #0x0 // #0 0xffffffc0082c7914 <+44>: mov x0, x2 0xffffffc0082c7918 <+48>: add x1, sp, #0x34 0xffffffc0082c791c <+52>: bl 0xffffffc0082c7740 <build_open_flags> 0xffffffc0082c7920 <+56>: cbz w0, 0xffffffc0082c794c <do_sys_openat2+100> 0xffffffc0082c7924 <+60>: sxtw x0, w0 0xffffffc0082c7928 <+64>: add x20, x20, #0x540 0xffffffc0082c792c <+68>: ldr x2, [sp, #72] 0xffffffc0082c7930 <+72>: ldr x1, [x20] 0xffffffc0082c7934 <+76>: eor x1, x2, x1 0xffffffc0082c7938 <+80>: cbnz x1, 0xffffffc0082c7a34 <do_sys_openat2+332> 0xffffffc0082c793c <+84>: ldp x19, x20, [sp, #16] 0xffffffc0082c7940 <+88>: ldp x21, x22, [sp, #32] 0xffffffc0082c7944 <+92>: ldp x29, x30, [sp], #80 0xffffffc0082c7948 <+96>: ret 0xffffffc0082c794c <+100>: mov x0, x21 ............

对于代码:

static long do_sys_openat2(int dfd, const char __user *filename, struct open_how *how) { struct open_flags op; int fd = build_open_flags(how, &op); struct filename *tmp; if (fd) return fd; ............
编辑
2025-01-20
记录知识
0

我们系统虽然已经经历过一次qt化,但是仍然有一些默认的程序还是基于gtk的,例如mate-terminal和filezilla,这些程序在我们系统上,针对一些使用习惯上的问题,还是存在一些bug的,对于这些问题,我们是操作系统的提供者,所以有必要查看原因和解决。

例如filezilla程序在我们默认kylin-virtual-keyboard程序上会默认自动唤起虚拟键盘,经过定位我们发现此行为的原因是filezilla主动发起了focus事件,所以对于kylin-virtual-keyboard的行为是正常的,这种情况下,我们就需要调试filezilla。而针对gtk程序,通常情况下,我们有一个检查程序inspector,这个程序可以给我们初步定位问题,本文章主要介绍一下inspector的使用说明。便于大家针对gtk的程序来进行初步问题筛查

一、关于GTK调试

针对gtk类的程序,我们都可以用gtk debug来尝试调试,gtk包含了多个调试选项,这里就不一一列举了,可以参考如下文章,我们以gtk3为例

https://docs.gtk.org/gtk3/running.html

我们关心的是inspector,所以如下:

interactive: Open the interactive debugger

关于检查器的官方介绍文档,可以参考如下:

https://developer.gnome.org/documentation/tools/inspector.html

二、检查器打开方式

inspector有多种打开方式,主要如下:

2.1 通过命令行

我们可以通过--gtk-debug=interactive打开程序的检查器,以filezilla为例

# filezilla --gtk-debug=interactive

2.2 通过宏

可以通过赋予GTK_DEBUG的值来打开调试,如下:

# GTK_DEBUG=interactive filezilla

2.3 通过快捷键

为了支持快捷键打开检查器,我们需要先将gtk的gsettings值打开,如下:

gsettings set org.gtk.Settings.Debug enable-inspector-keybinding true

此时我们可以通过如下两种方式打开(在运行程序内部按下快捷键)

ctrl + shift + i / ctrl + shift + d

三、检查器界面

打开后调试界面如下所示:

image.png 通过上述图片我们可以发现几个要素,如下:

  • 对象
  • 统计信息
  • 资源
  • CSS
  • 可视化
  • 常规
  • 选择对象
  • 细节

3.1 对象

我们打开检查器时,默认就打开了对象信息,如上图,未展开情况下我们可以知道此程序顶层存在几个widget,可以发现filezilla总共存在三个GtkWindow顶层窗口。这里两个置灰的窗口意味着未正常显示。所以如果我们点击非置灰的GtkWindow,我们可以看到filezilla程序会闪烁如下:

image.png 根据上述,我们可以知道第二个GtkWindow就是filezilla的顶层窗口,我们可以进一步跟踪,直到找到远程站点的文件列表的GtkWidget是什么类型为止,如下图,我们发现远程站点的文件列表实际上是一个GtkScrolledWindow类型。

image.png

3.2 统计信息

如果需要显示统计信息,则需要将glib设置为debug版本,也就是提供 --enable-debug参数,并且程序在运行时需要提供如下配置:

GOBJECT_DEBUG=instance-count

这里我们的系统没有把glib编译带此参数,故无法演示

3.3 资源

对于资源,我们可以理解和qt的res一样,也就是这个程序需要加载的动效,图片,和css,主要是一个统计作用,效果如下:

image.png

3.4 CSS

样式表我们可以给对应的窗口设置相应的样式用作测试css效果。

3.5 可视化

这里有多种关于主题,窗口属性,渲染细节的设置,细节不一一展示了,如下所示。

image.png

3.6 常规

这里提供了gtk版本等一系列的基本信息的查看,如下:

image.png

3.7 选择对象

inspector可以支持直接选择对象,我们点击右上角的准星按钮如下:

image.png 此时我们的光标变成准星来选择窗口,如下

image.png 当选择完毕,则提供这个窗口的显示细节,如下显示

image.png 以属性为例,我们可以动态的修改某个子窗口的属性值如下

image.png

3.8 细节

细节是靠近选择对象的按钮,它可以将窗口转到窗口的具体细节,例如杂项,属性,信号,子元素属性,类层级结构,CSS选择器,CSS节点,放大镜,如下图:

image.png

四、总结

至此,我们介绍了inspector的基本操作,希望这些介绍有助于我们调试linux上的gtk程序

编辑
2025-01-20
记录知识
0

我们基于2004的gstreamer版本在1.16.2/1.16.3上,为了提高gstreamer的可用性,我们跟进了rk的补丁,这样gstreamer可以良好的运行在系统上,所以我们需要升级gstreamer到1.20上,然后提供1.20的测试命令

一、升级方式

1.1 源版本

针对1.20.1的版本选择,我们需要根据ubuntu的发行release来进行跟进,这里跟进基于jammy上,所以是1.20.1。如下以gstreamer1.0-plugins-bad为示例

image.png 针对此,我们所有的代码溯源来自于https://launchpad.net/ubuntu/内的release源地址的gstreamer包,这个包的版本是1.20.1-1系列

1.2 补丁来源

为了使得这个版本能够合入rk的补丁,我们需要找到rk补丁地址如下:

https://github.com/JeffyCN/meta-rockchip/tree/master/recipes-multimedia/gstreamer

针对此,我们需要找到1.20.7的版本补丁来进行1.20.1的微调,如下:

对于补丁的批量合入,我们如下脚本:

for p in *.patch; do patch -p1 < $p done

这里需要留意的是,如果出现了rej,请格外小心,注意合入

如果出现了orig,则对照上下文,判断自动合入是否存在问题

1.3 依赖来源

对于上述包的升级,我们还需要针对依赖来进行依赖包的rebuild操作,这里来源仍是https://launchpad.net/ubuntu/内的release源地址

二、构建结果

根据ppa的本地构建,这里已经完成了构建,ppa地址如下:

https://dev.kylinos.cn/~tangfeng/+archive/kylin-desktop/gstreamer-rk-patch

具体包和依赖图示如下:

image.png 至此,gstreamer已经完全正常,我们只需要将源更新如下,即可升级1.20.1-1的gstreamer源码

deb http://ppa.launchpad.dev/tangfeng/gstreamer-rk-patch/kylin-desktop v101 main

三、测试命令

我们良好的构建了gstreamer后,需要进行一系列的命令测试,如下:

3.1 kmssink

为了支持kms,例如开机动画等操作,我们可以如下:

gst-launch-1.0 videotestsrc ! kmssink force-modesetting=true fullscreen=true

如有测试视频,可以如下:

gst-play-1.0 -q --no-interactive --audiosink=fakesink --videosink="kmssink force-modesetting=true fullscreen=true" /usr/local/test.mp4

3.2 xvimagesink

简单的测试一个视频的命令如下:

gst-play-1.0 --flags=3 /usr/local/test.mp4

如果需要看到fps值,如下:

GST_DEBUG=fpsdisplaysink:7 gst-launch-1.0 uridecodebin uri=file:///usr/local/test.mp4 ! fpsdisplaysink video-sink="xvimagesink" text-overlay=false signal-fps-measurements=true

如果测试最大fps值,则如下:

GST_DEBUG=fpsdisplaysink:7 gst-launch-1.0 uridecodebin uri=file:///usr/local/test.mp4 ! fpsdisplaysink video-sink="fakesink" text-overlay=false signal-fps-measurements=true sync=false

测试isp摄像头,因为出图是NV12的,所以需要指定:

gst-launch-1.0 v4l2src device=/dev/video-camera0 ! video/x-raw,format=NV12,width=640,height=480,framerate=30/1 ! xvimagesink

测试usb摄像头,因为出图是mjpeg的,所以命令如下

gst-launch-1.0 v4l2src device=/dev/video0 ! image/jpeg ! jpegparse ! mppjpegdec ! xvimagesink sync=false

3.3 rkximagesink

如果需要rkximagesink,如下:

GST_DEBUG=rkximagesink:2 gst-launch-1.0 uridecodebin uri=file:///usr/local/test.mp4 ! fpsdisplaysink video-sink="rkximagesink" text-overlay=false signal-fps-measurements=true

注意,如果使用rkximagesink,请内核提供良好的图层让其分配,否则会提示no window。

image.png

编辑
2025-01-20
记录知识
0

本文以初学者的角色对glibc的malloc进行简单的解析,从而了解malloc的简要知识,根据此文章,可以知道什么是fastbin,smallbin,largebin等等。

一、malloc时的内存布局

我们需要知道的是,通过glibc的malloc申请的内存,其返回的指针地址只是整个内存的chunk的userdata,其整体布局应该如下:

image.png 根据上图,我们可以知道如下信息:

  • userdata是malloc返回的指针
  • malloc申请的内存,包含多个chunk
  • 多个chunk上下相连
  • 每个chunk都包含一个prev_size
  • prev_size本身存放了上一个未使用的chunk大小
  • prev_size的下一个字节存放的是本chunk的size和AMP标志
  • 其中A标志代表此内存是否来自main arena/main heap
  • 其中M标志代表此内存是否来自mmap syscall
  • 其中P标志代表此内存的prev chunk是否正在使用,如果正在使用,则prev_size的值无效 根据此,我们知道了malloc时的内存布局情况,下面看看free时,指针的布局情况

二、free时的内存布局

我们需要知道的是,通过glibc的free释放的内存,它不会直接返还给操作系统从而让其他程序使用,而是简单的标记这块地址为reused,只有程序的内存到达一定的阈值情况下,或者程序申请内存时当前chunk不足以满足的情况下,触发glibc的consolidate。如下是free时的指针布局情况,如下:

image.png 根据上图,结合https://sourceware.org/glibc/wiki/MallocInternals,我们可以知道如下信息:

  • M标志位和free chunk无关,因为mmap的内存通过munmap来实现,不是通过free
  • free chunk维护了一个fwd/bck指针,这里fwd是forward,bck是backup,用于构造双向循环链表或单链表
  • 如果P标志未置位,则代表free chunk的上一个chunk是unused的,也就是free的,那么可以向上合并这两个chunk
  • 同理,如果下一个chunk是free chunk,那么也会被下一个chunk合并
  • 我们知道所有的chunk来自于top chunk,所以如何free chunk和top chunk相邻,则合并到top chunk
  • 如果tcache有空闲就,那么会将此free chunk放在tcache中
  • 判断此free chunk大小,放入对应的bin中,例如(fastbin,smallbin,largebin) 根据此,我们可以知道系统中的malloc和free的基本行为如下:
  1. malloc分配了一个地址给应用使用,实际还包含了一个chunk struct
  2. free并没有释放内存给系统,而是等到consolidate时尝试合并和释放

三、各类bin的情况

根据上面的信息,我们提到了各类的bin,当我们在malloc和free时,其实对应的是每个chunk,而每个chunk都根据chunk_size代表一个bin类型。注意,这里的chunk_size是包含chunk结构体的size大小,而不是给用户的内存地址和偏移下的大小。

对于常规bin,例如smallbin,largebin等一共是126个,如下:

image.png 对于fastbin,一个10个,所以总共有136个bin,如下:

image.png 这里我们谈论的bin的个数其实是bin的数组,实际上不同的bin数组内部是用不同的链表实现。例如fastbin使用单链表,其他bin使用双向循环链表

因为fastbin是单链表,所以结构体中的bck指针是没用的,只用到了fwd指针,如下:

image.png 对于其他的bin,它使用了双向循环链表,如下:

image.png 这里比较清晰的是,在free chunk中,fwd和bck指针都用到了。

除了各种bin的链表管理方式不同之外,我们还需要知道区分其bin的方式,通过内存大小,如下:

32 <= fastbin_size <= 128 128 < smallbin_size <= 1024 1024 < largebin_size <= 128*1024

注意,根据上面提到的,这个chunk size是包括chunk struct的size,而不是用户malloc的size。

至此,我们可以知道,通过malloc和free管理的内存,分为多个bin,有fastbin,smallbin,largebin等等。下面通过代码的方式来验证一下

四、测试程序

我们想要写一个测试程序,用来验证我们上述情况,首先,我们需要拿到chunk struct,这里以glibc 2.31为例,其结构体如下

struct malloc_chunk { size_t mchunk_prev_size; /* Size of previous chunk (if free). */ size_t mchunk_size; /* Size in bytes, including overhead. */ struct malloc_chunk* fd; /* double links -- used only if free. */ struct malloc_chunk* bk; /* Only used for large blocks: pointer to next larger size. */ struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */ struct malloc_chunk* bk_nextsize; };

根据上面我们可以看到,在64位系统上malloc_chunk的大小是48(6*8)

为了解析chunk,我编写了inpect_chunk函数,如下

static void inspect_chunk(void *ptr) { struct malloc_chunk *chunk = (struct malloc_chunk *)((char *)ptr - 2*sizeof(size_t)); size_t chunk_size = chunk->mchunk_size & ~0x7; // Mask out the metadata bits int prev_inuse = chunk->mchunk_size & 1; int is_mmapped = chunk->mchunk_size & 2; int main_arena = chunk->mchunk_size & 4; printf("Chunk address=%p. ", (void *)chunk); printf("size=%zu. AMP=%#lx: \n\t", chunk_size, chunk->mchunk_size & 0x7); printf("main-arena=%s. ", main_arena ? "[No]" : "[Yes]"); printf("with_mmap=%s. ", is_mmapped ? "[Yes]" : "[No]"); printf("prev_in_use=%s. ", prev_inuse ? "[Yes]" : "[No]"); if (chunk_size <= 128) { printf("is fast bin.\n"); } else if (chunk_size <= 1024) { printf("is small bin.\n"); } else { printf("is large bin.\n"); } }

我对用户malloc的指针向前推进了16字节,此时我们得到一个chunk指针,它的类型是malloc_chunk。然后我用chunk来进行判断

为了进行一系列的bin测试,我编写了测试函数如下:

void main() { malloc_stats(); int* a = malloc(sizeof(int)); inspect_chunk(a); free(a); a = malloc(0); inspect_chunk(a); free(a); a = malloc(128-8); inspect_chunk(a); free(a); a = malloc(128-7); inspect_chunk(a); free(a); a = malloc(1024-8); inspect_chunk(a); free(a); a = malloc(1024-7); inspect_chunk(a); free(a); a = malloc(128*1024-23); inspect_chunk(a); free(a); printf("%ld <= fastbin_size <= %ld\n", MIN_CHUNK_SIZE, DEFAULT_MXFAST); printf("%ld < smallbin_size <= %ld\n", DEFAULT_MXFAST, MIN_LARGE_SIZE); printf("%ld < largebin_size <= 128*1024 \n", MIN_LARGE_SIZE); malloc_stats(); }

我分别malloc了0,128,1024,128*1024等相关字节

为了让这个代码正常运行,需要包含malloc.h头文件和必要的宏定义,宏定义需要从glibc源码中摘抄如下:

#include <stdio.h> #include <malloc.h> #include <string.h> #include <stdlib.h> #include <stddef.h> # define INTERNAL_SIZE_T size_t #define SIZE_SZ (sizeof (INTERNAL_SIZE_T)) #define MALLOC_ALIGNMENT (2 * SIZE_SZ < __alignof__ (long double) \ ? __alignof__ (long double) : 2 * SIZE_SZ) #define NSMALLBINS 64 #define SMALLBIN_WIDTH MALLOC_ALIGNMENT #define SMALLBIN_CORRECTION (MALLOC_ALIGNMENT > 2 * SIZE_SZ) #define MIN_LARGE_SIZE ((NSMALLBINS - SMALLBIN_CORRECTION) * SMALLBIN_WIDTH) #define MIN_CHUNK_SIZE (offsetof(struct malloc_chunk, fd_nextsize)) #define DEFAULT_MXFAST (64 * SIZE_SZ / 4)

至此,我们可以轻松的运行这个程序,如下:

gcc test_malloc_chunk.c -o test_malloc_chunk && ./test_malloc_chunk

从而得到输出如下:

Arena 0: system bytes = 135168 in use bytes = 3680 Total (incl. mmap): system bytes = 135168 in use bytes = 3680 max mmap regions = 0 max mmap bytes = 0 --------------------------------------------- Chunk address=0x559f9f0270. size=32. AMP=0x1: main-arena=[Yes]. with_mmap=[No]. prev_in_use=[Yes]. is fast bin. Chunk address=0x559f9f0270. size=32. AMP=0x1: main-arena=[Yes]. with_mmap=[No]. prev_in_use=[Yes]. is fast bin. Chunk address=0x559f9f0290. size=128. AMP=0x1: main-arena=[Yes]. with_mmap=[No]. prev_in_use=[Yes]. is fast bin. Chunk address=0x559f9f0310. size=144. AMP=0x1: main-arena=[Yes]. with_mmap=[No]. prev_in_use=[Yes]. is small bin. Chunk address=0x559f9f03a0. size=1024. AMP=0x1: main-arena=[Yes]. with_mmap=[No]. prev_in_use=[Yes]. is small bin. Chunk address=0x559f9f07a0. size=1040. AMP=0x1: main-arena=[Yes]. with_mmap=[No]. prev_in_use=[Yes]. is large bin. Chunk address=0x7f96c52000. size=135168. AMP=0x2: main-arena=[Yes]. with_mmap=[Yes]. prev_in_use=[No]. is large bin. 32 <= fastbin_size <= 128 128 < smallbin_size <= 1024 1024 < largebin_size <= 128*1024 --------------------------------------------- Arena 0: system bytes = 135168 in use bytes = 7088 Total (incl. mmap): system bytes = 135168 in use bytes = 7088 max mmap regions = 1 max mmap bytes = 135168

注意,这里我使用了glibc的malloc信息函数malloc_stats,它能够打印当前的malloc信息,我解析如下:

Arena 0: //Arena ID. There is only one thread. system bytes = 135168 //Dynamic memory obtained by the thread from the OS. in use bytes = 7088 //Dynamic memory used by the thread. Total (incl. mmap): //Total usage of the dynamic memory, that is, the accumulated dynamic memory used by each thread. system bytes = 135168 //Dynamic memory obtained by the process from the OS. in use bytes = 7088 //Dynamic memory used by the process. max mmap regions = 1 //Maximum number of mmap regions max mmap bytes = 135168 //Size of the memory corresponding to mmap regions

这里值得注意的是

1. 为什么in use bytes差8字节,这8字节在哪里?

这里7088 - 3680 = 2408,但实际上 1024+1040+144+128+32+32=2400。多了一个8字节

这是因为最开始的chunk,我们需要一个空的prev_size,这个prev_size的类型是size_t,也就是8。

2. 为什么是135168,系统字节是135168,mmap字节是135168

这里我们知道一个常识是,当内存超过128k时,系统通过mmap系统调用来分配内存,这时候是128*1024=131072

但是管理mmap这么多内存需要一个结构体,这样同时知道分配内存的最小单位是4k,那么4k为4*1024=4096

那么131072+4096=135168。

另一方面,我们在使用glibc程序时,默认程序启动时,glibc的内存管理提供你128k内存供你使用,这里还需要附带1个4k页。然后程序的malloc行为实际上是通过top chunk来进行分配即可。

所以一个程序运行时,默认glibc管理程序会给这个程序提供135168字节的system bytes

编辑
2025-01-20
记录知识
0

作为程序员,我们每天都在和git打交道,无论是自己在编写代码时和同事的协作情况下还是在合并别人的提交的时候,都很容易出现冲突的情况,我们在将整个操作系统进行同步上游的时候,冲突几乎是必定出现,这里介绍一下出现冲突应该怎么应对

一、什么是冲突

当我们敲入

git merge tangfeng

将tangfeng分钟merge到自己分支的时候,如果代码存在不能自动合并的情况下,我们需要自己解决冲突,主要情况如下:

# git status 位于分支 egf/v101-tablet-dev 您的分支与上游分支 'origin/egf/v101-tablet-dev' 一致。 您有尚未合并的路径。 (解决冲突并运行 "git commit") (使用 "git merge --abort" 终止合并) 要提交的变更: 修改: windowsview/qml/AppArea.qml 修改: windowsview/qml/AppPreviewWindow.qml 修改: windowsview/qml/TabletPreviewWindow.qml 修改: windowsview/qml/TabletViewMain.qml 修改: windowsview/qml/multitaskview.qml 修改: windowsview/ukui-window-switch_bo_CN.ts 修改: windowsview/ukui-window-switch_zh_CN.ts 未合并的路径: (使用 "git add <文件>..." 标记解决方案) 双方修改: debian/changelog

我们留意两方面的信息

  • 您有尚未合并的路径,这里提示你merge是失败了,需要手动解决
  • 双方修改: debian/changelog,这里提示具体的冲突情况 至此,我们以最常见的changelog冲突为例展示了一次冲突,我们接下来看冲突的具体内容
# git diff debian/changelog diff --cc debian/changelog index 9bb9968,f1c60be..0000000 --- a/debian/changelog +++ b/debian/changelog @@@ -1,14 -1,15 +1,29 @@@ ++<<<<<<< HEAD +ukui-window-switch (3.1.0.1-0k0.1tablet7rk1.egf0.1) v101; urgency=medium + + * No-Change rebuild for ci. + + -- yangquan <yangquan@kylinos.cn> Thu, 14 Mar 2024 15:16:56 +0800 + +ukui-window-switch (3.1.0.1-0k0.1tablet7rk1) v101; urgency=medium + + * Support RK3588 + + -- tangfeng <tangfeng@kylinos.cn> Wed, 09 Aug 2023 12:56:59 +0800 ++======= + ukui-window-switch (3.1.0.1-0k0.1tablet8) v101; urgency=medium + + * BUG: + - 209242 【多任务视图】【TM】改变系统字体,多任务视图未随之变化 + - 209246 【多任务视图】【TM】改变系统字号,多任务视图中字号无变化 + - 209264 【多任务视图】PC模式,传书和计算器在多任务视图中显示为尖角 + - 209266 【多任务视图】PC模式,应用缩略图左右两端显示不完整 + * 需求号: + * 其他改动说明: + * 其他改动影响域:自身 + + -- jiaodian <jiaodian@kylinos.cn> Mon, 15 Jan 2024 09:02:51 +0800 ++>>>>>>> up

第一次看到这些信息先不要害怕,我们接下来介绍

二、解决冲突

我们首先看下面这个图

image.png

这里我标注了1,2,3。也就是说,任意的冲突,我们都可以拆分为1,2,3三个框架

  • 标注1:这里代表我们的HEAD分支目前的改动,<<<<<是开始 这里示例如下:
<<<<<<< local branch
  • 标注2:这里代表是分割线,也就是HEAD分支改动的最后 这里示例如下:
=======
  • 标注3:这里代表是我们上游分支的结尾,>>>>>>>是结束 这里示例如下:
>>>>>>> upstream branch

至此,我们知道了冲突的格式,接下来是解决

2.1 具体语义具体分析

涉及到冲突的解决,我们需要根据上下文代码来分析, 这里我们举例的是changelog,所以以这里来看

首先,我们知道,我们的版本号是逐步递增的,所以我们不能一味的选中HEAD或者up分支的内容,也就是我们需要都合并

其次,我们合并需要有顺序关系,也就是3.1.0.1-0k0.1tablet7--->3.1.0.1-0k0.1tablet7rk1--->3.1.0.1-0k0.1tablet7rk1.egf0.1--->3.1.0.1-0k0.1tablet8

再者,我们的changelog有明确的命名规范,3.1.0.1-0k0.1tablet8应该修改成3.1.0.1-0k0.1tablet8rk1.egf0.1

至此我们应该修改如下:

  1. 删除<<<<<<< HEAD
  2. 删除=======
  3. 删除>>>>>>> up
  4. 将up的改动放在HEAD之上
  5. 将版本号3.1.0.1-0k0.1tablet8修改为3.1.0.1-0k0.1tablet8rk1.egf0.1 改后的diff如下:
diff --cc debian/changelog index 9bb9968,f1c60be..0000000 --- a/debian/changelog +++ b/debian/changelog @@@ -1,15 -1,16 +1,28 @@@ -ukui-window-switch (3.1.0.1-0k0.1tablet8) v101; urgency=medium ++ukui-window-switch (3.1.0.1-0k0.1tablet8rk1.egf0.1) v101; urgency=medium + + * BUG: + - 209242 【多任务视图】【TM】改变系统字体,多任务视图未随之变化 + - 209246 【多任务视图】【TM】改变系统字号,多任务视图中字号无变化 + - 209264 【多任务视图】PC模式,传书和计算器在多任务视图中显示为尖角 + - 209266 【多任务视图】PC模式,应用缩略图左右两端显示不完整 + * 需求号: + * 其他改动说明: + * 其他改动影响域:自身 + + -- jiaodian <jiaodian@kylinos.cn> Mon, 15 Jan 2024 09:02:51 +0800 + +ukui-window-switch (3.1.0.1-0k0.1tablet7rk1.egf0.1) v101; urgency=medium + + * No-Change rebuild for ci. + + -- yangquan <yangquan@kylinos.cn> Thu, 14 Mar 2024 15:16:56 +0800 + +ukui-window-switch (3.1.0.1-0k0.1tablet7rk1) v101; urgency=medium + + * Support RK3588 + + -- tangfeng <tangfeng@kylinos.cn> Wed, 09 Aug 2023 12:56:59 +0800 + ukui-window-switch (3.1.0.1-0k0.1tablet7) v101; urgency=medium * BUG:

改好之后,我们通过git add来提交,代表我们解决了冲突

git add debian/changelog

然后我们需要使用git commit来执行合并

git commit

接下来我们先不提交pr,需要本地编译用于此测试验证

dpkg-buildpackage -uc -us

我们知道ukui-window-switch用作系统的多任务窗口的程序,所以我们自测如下:

scp ../ukui-kwin-effects_3.1.0.1-0k0.1tablet8rk1.egf0.1_all.deb ../ukui-window-switch_3.1.0.1-0k0.1tablet8rk1.egf0.1_arm64.deb root@172.25.83.91:~

然后机器上安装

dpkg -i ukui-kwin-effects_3.1.0.1-0k0.1tablet8rk1.egf0.1_all.deb ukui-window-switch_3.1.0.1-0k0.1tablet8rk1.egf0.1_arm64.deb

然后重启开始自测

systemctl restart lightdm

在平板模式和桌面模式上都点击多任务,这里不展示每个自测,只展示如下bug的自测:

PC模式,应用缩略图左右两端显示不完整 改变系统字体,多任务视图未随之变化 改变系统字号,多任务视图中字号无变化 传书和计算器在多任务视图中显示为尖角

自测完毕之后,直接提交branch,如下:

git branch -m tangfeng git push origin tangfeng

此时我们登录网页提交对egf/v101-tablet-dev的pr请求后提交ci

待ci完成之后,我们填写集成单,事情即完成。

三、建议

我们在合入分支的时候,并不是每一笔都能够正常合入,所以我们必须要根据合入的代码进行review和理解,这同样对于代码的提交者和审核者都具备此要求。

3.1 单笔合入解决冲突

但是我们在git merge的时候,还是会出现了大量无法合并的情况,或者我们针对上下文无法进行分析或代码能力不够的前提下,不清楚补丁改了什么的时候,这时候更建议按照一个一个补丁的方式合入,如下:

首先切到上游分支

git checkout up

然后我们得在上游分支上看领先了多少提交

commit cd205b17db39334e2f07a9cc474822015b705ddd (HEAD -> up, tag: build/3.1.0.1-0k0.1tablet8, origin_upstream/yhkylin/v101-tablet) Author: jiaodian <jiaodian@kylinos.cn> Date: Mon Jan 15 09:03:44 2024 +0800 [Chore]: xctablet changelog 3.1.0.1-0k0.1tablet8 commit 3c993741e0fd5edcb5bf939fd99c727e03611abd Merge: 954a1c6 ad3c421 Author: 庞毅 <pangyi@kylinos.cn> Date: Wed Jan 10 02:36:08 2024 +0000 Merge branch 'yhkylin/v101-tablet-font' into 'yhkylin/v101-tablet' 平板模式增加适配字体字号变化的功能 See merge request kylinos-src/ukui-window-switch!131 commit 954a1c671a355ac6335274b9355e59fac081fb83 Merge: 75404f5 deaf58a Author: 庞毅 <pangyi@kylinos.cn> Date: Wed Jan 10 02:35:10 2024 +0000 Merge branch 'yhkylin/v101-tablet' into 'yhkylin/v101-tablet' 同步主线缩略图的大小以及圆角改动 See merge request kylinos-src/ukui-window-switch!130 commit ad3c421e7e3dd74030cd755a72e39d5e6118e3f3 Author: jiaodian <jiaodian@kylinos.cn> Date: Wed Jan 10 09:52:18 2024 +0800 平板模式增加适配字体字号变化的功能 commit deaf58a71bf71367490a5550f1ee2d3b97416c1f Author: pang_yi <pangyi@kylinos.cn> Date: Fri Nov 17 15:25:17 2023 +0800 Fix: bug 209264 传书和计算器缩略图不是圆角 commit 9b3deeb06f75dd22b862fda2f3b2d4545968b92f Author: jiaodian <jiaodian@kylinos.cn> Date: Wed Jul 5 14:31:04 2023 +0800 修改缩略图大小 commit 75404f571fd82fe35085395804289036a9d39bed (tag: build/3.1.0.1-0k0.1tablet7, origin/yhkylin/v101-tablet)

这里我们留意最后一行:

commit 75404f571fd82fe35085395804289036a9d39bed (tag: build/3.1.0.1-0k0.1tablet7, origin/yhkylin/v101-tablet)

可以知道,我们的提交领先到这里了,此时我们生成patch,如下:

git format-patch 75404f571fd82fe35085395804289036a9d39bed

此时我们会提供HEAD到75404f571fd82fe35085395804289036a9d39bed的所有patch文件,我们逐一合入:

patch -p1 < 0001-xxx.patch patch -p1 < 0002-xxx.patch patch -p1 < 0003-xxx.patch patch -p1 < 0004-xxx.patch

此时我们可以把之前的git merge的冲突拆解为这四笔提交的冲突,相当于将问题一分为四。

3.2 放弃自己改动,先合入上游再合入自己改动

如果我们发现上游更新太多了,理解代码太困难了,我们可以先合上游,再提交自己的补丁,如下:

此时我们在tangfeng分支 查看日志如下:

commit f5eb14a05d9f1bdaa8527317d8c6d2b6c1868430 (tag: build/3.1.0.1-0k0.1tablet7rk1, origin/3588-tablet) Author: Your Name <you@example.com> Date: Wed Aug 9 12:57:51 2023 +0800 支持RK3588平台 commit 381dee6e9d9e417404be791f041daf62ab4bf884 Merge: 7cf2d58 75404f5 Author: Your Name <you@example.com> Date: Wed Aug 9 10:53:51 2023 +0800 Merge branch 'tablet' into 3588 commit 75404f571fd82fe35085395804289036a9d39bed (tag: build/3.1.0.1-0k0.1tablet7, origin/yhkylin/v101-tablet)

这里可以看到从origin/yhkylin/v101-tablet到build/3.1.0.1-0k0.1tablet7rk1我们总共两笔提交,其中一笔是merge的日志。也就是一笔提交,我们可以将其生成补丁,如下:

git format-patch 75404f571fd82fe35085395804289036a9d39bed

此时我们存在一个patch,如下:

0001-xxx.patch

然后我们将代码直接reset/revert,如下:

如果是reset,则:

git reset 75404f571fd82fe35085395804289036a9d39bed git checkout .

如果是revert,则

git revert f5eb14a05d9f1bdaa8527317d8c6d2b6c1868430

至此,我们回到了和主线分支完全一致的状态,然后我们合入主线

git merge up

这里因为没有我们的任何一笔改动,所以merge一定是成功的。

然后我们合入自己的补丁,如下

patch -p1 < 0001-xxx.patch

此时我们的补丁会产生冲突,我们解决自己的冲突即可。

在解决好冲突之后,我们需要rebase来解决问题。如下:

git rebase git push -f

值得注意的是,我们在敲git rebase的时候请与管理员先沟通,再git push -f强推。

四、总结

本文描述了我们通常情况下在合入补丁时的解决方法,我们提供了三种思路,处理问题的优先级按照描述的优先级

  • 我们默认优先的方法是git merge,然后在遇到冲突的时候解决冲突。
  • 其次优先是通过git am或patch命令来单笔合入
  • 如果都无法合入,尝试将自己的改动reset/revert,然后合入上游后,将自己的patch合入,解决自己patch带来的冲突 但是也需要值得注意的是,我们在运行git rebase之前,请通知团队的管理员。避免给仓库带来损失。