Linxu: Dynamic debug 简介

文章目录

  • 1. 前言
  • 2. 什么是 Dynamic debug (dyndbg) ?
  • 3. Dynamic debug (dyndbg) 的使用
    • 3.1 开启 Dynamic debug (dyndbg) 功能
    • 3.2 使用 Dynamic debug (dyndbg) 功能
  • 4. Dynamic debug (dyndbg) 的实现
    • 4.1 内核接口 dynamic_pr_debug() 的实现
    • 4.2 debugfs 导出控制节点 control 实现
      • 4.2.1 Dynamic debug 初始化
      • 4.2.2 Dynamic debug 的用户空间接口
        • 4.2.2.1 control 文件的建立
        • 4.2.2.2 control 文件的查询(读)
        • 4.2.2.3 control 文件的修改(写)
    • 4.3 外置模块的 dyndbg 参数
      • 4.3.1 外置模块 dyndbg 参数的解析
      • 4.3.2 外置模块 dyndbg 参数的提供方式
        • 4.3.2.1 通过模块配置文件
        • 4.3.2.2 通过内核命令行参数
        • 4.3.2.3 通过模块参数
  • 5. Dynamic debug (dyndbg) 的优缺点
  • 6. 参考资料

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. 什么是 Dynamic debug (dyndbg) ?

Dynamic debug(dyndbg) 功能,简单来讲,就是允许用户空间通过 debugfs 导出的文件节点 /sys/kernel/debug/dynamic_debug/control动态的、在运行时控制 Linux 内核 KERN_DEBUG 类型日志的开启和关闭。在没开启 Dynamic debug 时,直到下次重新编译内核做出改变之前,内核 KERN_DEBUG 类型日志要么一直关闭,要么一直开启,无法在运行时做出调整。

3. Dynamic debug (dyndbg) 的使用

3.1 开启 Dynamic debug (dyndbg) 功能

来看 Linux 内核配置文件 lib/Kconfig.debug 中,配置项 CONFIG_DYNAMIC_DEBUG 的定义:

config DYNAMIC_DEBUGbool "Enable dynamic printk() support"default ndepends on PRINTKdepends on DEBUG_FShelpCompiles debug level messages into the kernel, which would nototherwise be available at runtime. These messages can then beenabled/disabled based on various levels of scope - per source file,function, module, format string, and line number. This mechanismimplicitly compiles in all pr_debug() and dev_dbg() calls, whichenlarges the kernel text size by about 2%.If a source file is compiled with DEBUG flag set, anypr_debug() calls in it are enabled by default, but can bedisabled at runtime as below.  Note that DEBUG flag isturned on by many CONFIG_*DEBUG* options.Usage:......     

要开启 Dynamic debug 功能,要开启配置项 CONFIG_DYNAMIC_DEBUG,其依赖于配置项 CONFIG_PRINTKCONFIG_DEBUG_FS。由于最终调用的打印接口都是 printk(),所以要开启 CONFIG_PRINTK;另外 Dynamic debug 通过 debugfs 导出的文件节点 /sys/kernel/debug/dynamic_debug/control 控制每个 KERN_DEBUG 调试语句,所以需要启用 CONFIG_DEBUG_FS
学习一样技能,最好先学会如何使用它,这往往是一个不错的起点。本文采用 QEMU + vexpress-a9 + Linux 4.14.132 + ubuntu-base-16.04-core-armhf.tar.gz 组合来进行 Dynamic debug 功能测试。如何使用 QEMU 搭建需要的测试环境,不在本文的讨论范围之内,读者可以自行查询相关资料或去笔者的 Gitee 仓库 去拿一个构建脚本。
开启 CONFIG_DYNAMIC_DEBUG 和其依赖配置项 CONFIG_PRINTKCONFIG_DEBUG_FS。这里只展示 CONFIG_DYNAMIC_DEBUG 的开启,CONFIG_PRINTKCONFIG_DEBUG_FS 默认处于开启状态。

$ cd linux-4.14.132
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- O=output menuconfig

在这里插入图片描述
然后编译运行内核:

$ cd linux-4.14.132
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- O=output -j8
$ cd ..
$ sudo qemu-system-arm \-M vexpress-a9 -smp 4 -m 512M \-kernel linux-4.14.132/output/arch/arm/boot/zImage \-dtb linux-4.14.132/output/arch/arm/boot/dts/vexpress-v2p-ca9.dtb \-nographic \-append "root=/dev/mmcblk0 rw rootfstype=ext4 console=ttyAMA0" \-sd rootfs/arm-ubuntu-16.04.img

其中,rootfs/arm-ubuntu-16.04.img 是根文件系统,可通过前面笔者提供的脚本构建,或者读者自行查阅相关资料。运行并登录后,查看 /sys/kernel/debug 目录,会发现一个名为 dynamic_debug 的目录,目录下有一个 control 文件:

root@qemu-ubuntu:~# ls /sys/kernel/debug/ -l
......
drwxr-xr-x  2 root root 0 Jan  1  1970 dynamic_debug
......
root@qemu-ubuntu:~# ls /sys/kernel/debug/dynamic_debug/ -l
total 0
-rw-r--r-- 1 root root 0 Jan  1  1970 control

可以看到,文件 /sys/kernel/debug/dynamic_debug/control 具有可读写权限,读下它看它包含些什么内容:

root@qemu-ubuntu:~# cat /sys/kernel/debug/dynamic_debug/control
# filename:lineno [module]function flags format
init/main.c:743 [main]initcall_blacklist =p "blacklisting initcall %s\012"
init/main.c:774 [main]initcall_blacklisted =p "initcall %s blacklisted\012"
init/initramfs.c:485 [initramfs]unpack_to_rootfs =_ "Detected %s compressed data\012"
arch/arm/vfp/vfpmodule.c:300 [vfp]vfp_emulate_instruction =_ "VFP: emulate: INST=0x%08x SCR=0x%08x\012"
[......]
lib/kobject_uevent.c:392 [kobject_uevent]kobject_uevent_env =_ "kobject: '%s' (%p): %s: unset subsystem caused the event to drop!\012"
lib/kobject_uevent.c:434 [kobject_uevent]kobject_uevent_env =_ "kobject: '%s' (%p): %s: uevent() returned %d\012"

输出内容实在有点太多了,这里只截取一部分。对于每行输出内容的格式,第一行给出了注释:

# filename:lineno [module]function flags format

用这个格式解释下第二行内容:

init/main.c:743 [main]initcall_blacklist =p "blacklisting initcall %s\012"
filename => init/main.c
lineno => 743
[module]function => [main]initcall_blacklist
flags => =p
format => "blacklisting initcall %s\012"

让人很好奇不是,文件 init/main.c743 行,函数 initcall_blacklist() 到底包含是什么内容?来看一下:
在这里插入图片描述
都猜到了吧?正是一个 pr_debug() 调用。

3.2 使用 Dynamic debug (dyndbg) 功能

/sys/kernel/debug/dynamic_debug/control 文件,将列举内核中当前可通过 control 文件使能、关闭的调试语句信息。对 control 文件的分别代表不同的含义。对 control 文件写入操作的语法如下:

command ::= match-spec* flags-specmatch-spec ::= 'func' string |'file' string |'module' string |'format' string |'line' line-rangeline-range ::= lineno |'-'lineno |lineno'-' |lineno'-'linenolineno ::= unsigned-int// 对于 flags-spec,内核文档不知道是不是漏掉了,
// 这里是笔者给出的定义,不太准确,读者能理解意思即可。
flags-spec ::= flag-ops flags
flags-ops ::= - | + | =
flags ::= p | f | l | m | t | _

flags 支持如下 flag-ops 操作,包括 - (移除)+ (添加)= (赋值)

-    remove the given flags
+    add the given flags
=    set the flags to the given flags

有下列可选 flags

p    enables the pr_debug() callsite.
f    Include the function name in the printed message
l    Include line number in the printed message
m    Include module name in the printed message
t    Include thread ID in messages not generated from interrupt context
_    No flags are set. (Or'd with others on input)

由于内核原有原有代码的 pr_debug(), dev_debug() 等调试语句,要依赖一定条件才能触发,对于演示 Dynamic debug 功能的使用不是很方便,也不够直观,所以本文通过编写一个测试模块来演示 Dynamic debug 功能的使用。Dynamic debug 功能测试模块的代码 dynamic_debug_example.c 如下:

#include <linux/module.h>
#include <linux/sched.h>
#include <linux/kthread.h>
#include <linux/delay.h>static struct task_struct *dyndbg_tsk;static int dyndbg_task_fn(void *ignored)
{int count = 0;int ret = 0;while (!kthread_should_stop()) {pr_debug("dyndbg test message %d\n", count++);msleep(2000);}return ret;
}static int __init dyndbg_test_init(void)
{int ret = 0;printk(KERN_INFO "dyndbg test module init.\n");dyndbg_tsk = kthread_run(dyndbg_task_fn, NULL, "dyndbg_test");if (IS_ERR(dyndbg_tsk)) {ret = PTR_ERR(dyndbg_tsk);printk(KERN_ERR "%s: Failed to create kernel thread, ret = [%d]\n", __func__, ret);}return ret;
}module_init(dyndbg_test_init);
module_exit(dyndbg_test_exit);MODULE_LICENSE("GPL");

编译模块并添加到根文件系统镜像,然后启动 QEMU 运行:

root@qemu-ubuntu:~# lsmod
Module                  Size  Used by
root@qemu-ubuntu:~# cat /sys/kernel/debug/dynamic_debug/control | grep "dynamic_debug_example"

通过查看 control 文件,没有看到关于测试模块 dynamic_debug_example.c 的任何信息,这是因为测试模块还没有加载,加载模块后再来看一下:

root@qemu-ubuntu:~# modprobe dynamic_debug_example   
root@qemu-ubuntu:~# lsmod
Module                  Size  Used by
dynamic_debug_example    16384  0
root@qemu-ubuntu:~# cat /sys/kernel/debug/dynamic_debug/control | grep "dynamic_debug_example"
samples/dynamic_debug/dynamic_debug_example.c:16 [dynamic_debug_example]dyndbg_task_fn =_ "dyndbg test message %d\012"

看看,samples/dynamic_debug/dynamic_debug_example.c 已经出现在 control输出中了。但是查看当前的内核日志,还是没发现测试模块的日志:

root@qemu-ubuntu:~# dmesg | grep "dyndbg test message"

我们注意到,control输出中关于 dynamic_debug_example.c 的信息中,带有 =_ 标记,这表示 dynamic_debug_example.c 文件中第 16 行的 pr_debug() 没有开启。是时候施展魔法标记 +p 来开启这个调试语句,来看看效果:

root@qemu-ubuntu:~# echo -n "file samples/dynamic_debug/dynamic_debug_example.c +p" > /sys/kernel/debug/dynamic_debug/controlroot@qemu-ubuntu:~# dmesg | tail -n 8
[ 1677.219423] dyndbg test message 2
[ 1679.410816] dyndbg test message 3
[ 1681.580533] dyndbg test message 4
[ 1683.792157] dyndbg test message 5
[ 1686.052255] dyndbg test message 6
[ 1688.202825] dyndbg test message 7
[ 1690.373784] dyndbg test message 8
[ 1692.562494] dyndbg test message 9root@qemu-ubuntu:~# cat /sys/kernel/debug/dynamic_debug/control | grep "dynamic_debug_example"
samples/dynamic_debug/dynamic_debug_example.c:16 [dynamic_debug_example]dyndbg_task_fn =p "dyndbg test message %d\012"

可以看到,现在有了测试模块的日志;另外,读 control 文件的输出, dynamic_debug_example.c16 行调试语句的标志已经变为了 =p 了,证明已经开启了。
嗯,越打越多,有点烦人,通过 -p 标记关闭这个调试语句:

root@qemu-ubuntu:~# echo -n "file samples/dynamic_debug/dynamic_debug_example.c -p" > /sys/kernel/debug/dynamic_debug/control
root@qemu-ubuntu:~# cat /sys/kernel/debug/dynamic_debug/control | grep "dynamic_debug_example"
samples/dynamic_debug/dynamic_debug_example.c:16 [dynamic_debug_example]dyndbg_task_fn =_ "dyndbg test message %d\012"

查看调试语句的标记已经变为 =_ 了,同时也确实没有日志输出了。通过 +pflm 标记玩一个花一点的输出:

root@qemu-ubuntu:~# echo -n "file samples/dynamic_debug/dynamic_debug_example.c +pflm" > /sys/kernel/debug/dynamic_debug/control
[ 2287.948918] dynamic_debug_example:dyndbg_task_fn:16: dyndbg test message 158
[ 2290.159226] dynamic_debug_example:dyndbg_task_fn:16: dyndbg test message 159
[ 2292.440786] dynamic_debug_example:dyndbg_task_fn:16: dyndbg test message 160
[ 2294.640683] dynamic_debug_example:dyndbg_task_fn:16: dyndbg test message 161
[ 2296.801579] dynamic_debug_example:dyndbg_task_fn:16: dyndbg test message 162
root@qemu-ubuntu:~# cat /sys/kernel/debug/dynamic_debug/control | grep "dynamic_debug_example"
samples/dynamic_debug/dynamic_debug_example.c:16 [dynamic_debug_example]dyndbg_task_fn =pmfl "dyndbg test message %d\012"
root@qemu-ubuntu:~# echo -n "file samples/dynamic_debug/dynamic_debug_example.c -pflm" > /sys/kernel/debug/dynamic_debug/control

+pflm 标记的加持下,调试日志相对之前的输出,增加了 模块名(+m)、函数名(+f)、行号(+l) 的输出。
本文对 Dynamic debug 输出的示范,就演示到此。关注 Dynamic debug 输出更多用法的读者,可参考 Linux 内核文档 Dynamic debug 。

4. Dynamic debug (dyndbg) 的实现

4.1 内核接口 dynamic_pr_debug() 的实现

对于 Linux 内核的 Dynamic debug 功能,我们可能既熟悉又陌生。来看下 include/linux/printk.hinclude/linux/device.h 中的相关定义:

/* include/linux/printk.h *//* If you are writing a driver, please use dev_dbg instead */
#if defined(CONFIG_DYNAMIC_DEBUG)
#include <linux/dynamic_debug.h>/* dynamic_pr_debug() uses pr_fmt() internally so we don't need it here */
#define pr_debug(fmt, ...) \dynamic_pr_debug(fmt, ##__VA_ARGS__)
#elif defined(DEBUG)
#define pr_debug(fmt, ...) \printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
#else
#define pr_debug(fmt, ...) \no_printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
#endif
/* include/linux/device.h */#if defined(CONFIG_DYNAMIC_DEBUG)
#define dev_dbg(dev, format, ...)		     \
do {						     \dynamic_dev_dbg(dev, format, ##__VA_ARGS__); \
} while (0)
#elif defined(DEBUG)
#define dev_dbg(dev, format, arg...)		\dev_printk(KERN_DEBUG, dev, format, ##arg)
#else
#define dev_dbg(dev, format, arg...)				\
({								\if (0)							\dev_printk(KERN_DEBUG, dev, format, ##arg);	\
})
#endif

从上面 pr_debug()dev_dbg() 定义看到,开启 Dynamic debug 的情形下(即配置 CONFIG_DYNAMIC_DEBUG=y),pr_debug() 实现为 dynamic_pr_debug()dev_dbg() 实现为 dynamic_dev_dbg(),这意味着:

1. Dynamic debug 只影响 KERN_DEBUG 类型的内核日志。
2. Dynamic debug 影响所有基于 pr_debug(), dev_dbg() 的 API 接口。

继续看 dynamic_pr_debug() 的实现细节:

/* include/linux/dynamic_debug.h */#ifdef HAVE_JUMP_LABEL#define dd_key_init(key, init) key = (init)#ifdef DEBUG
#define DEFINE_DYNAMIC_DEBUG_METADATA(name, fmt) \DEFINE_DYNAMIC_DEBUG_METADATA_KEY(name, fmt, .key.dd_key_true, \(STATIC_KEY_TRUE_INIT))#define DYNAMIC_DEBUG_BRANCH(descriptor) \static_branch_likely(&descriptor.key.dd_key_true)
#else /* !DEBUG */
#define DEFINE_DYNAMIC_DEBUG_METADATA(name, fmt) \DEFINE_DYNAMIC_DEBUG_METADATA_KEY(name, fmt, .key.dd_key_false, \(STATIC_KEY_FALSE_INIT))#define DYNAMIC_DEBUG_BRANCH(descriptor) \static_branch_unlikely(&descriptor.key.dd_key_false)
#endif /* DEBUG */#else /* !HAVE_JUMP_LABEL */#define dd_key_init(key, init)#define DEFINE_DYNAMIC_DEBUG_METADATA(name, fmt) \DEFINE_DYNAMIC_DEBUG_METADATA_KEY(name, fmt, 0, 0)#ifdef DEBUG
#define DYNAMIC_DEBUG_BRANCH(descriptor) \likely(descriptor.flags & _DPRINTK_FLAGS_PRINT)
#else /* !DEBUG */
#define DYNAMIC_DEBUG_BRANCH(descriptor) \unlikely(descriptor.flags & _DPRINTK_FLAGS_PRINT)
#endif /* DEBUG */#endif /* HAVE_JUMP_LABEL */#define dynamic_pr_debug(fmt, ...)				\
do {								\DEFINE_DYNAMIC_DEBUG_METADATA(descriptor, fmt);		\if (DYNAMIC_DEBUG_BRANCH(descriptor))			\__dynamic_pr_debug(&descriptor, pr_fmt(fmt),	\##__VA_ARGS__);		\
} while (0)

dynamic_pr_debug() 的定义按 HAVE_JUMP_LABELDEBUG 的不同组合展开,得到如下 4 种可能的代码片段:

/* (1) HAVE_JUMP_LABEL && DEBUG */static struct _ddebug  __aligned(8)
__attribute__((section("__verbose"))) descriptor = {.modname = KBUILD_MODNAME,.function = __func__,.filename = __FILE__,.format = (fmt),.lineno = __LINE__,	.flags = _DPRINTK_FLAGS_PRINT,.key.dd_key_true = STATIC_KEY_TRUE_INIT, /* 调试语句 默认为 启用 状态 */
};
if (static_branch_likely(&descriptor.key.dd_key_true))__dynamic_pr_debug(&descriptor, pr_fmt(fmt), ##__VA_ARGS__);
/* (2) HAVE_JUMP_LABEL && !DEBUG */static struct _ddebug  __aligned(8)
__attribute__((section("__verbose"))) descriptor = {.modname = KBUILD_MODNAME,.function = __func__,.filename = __FILE__,.format = (fmt),.lineno = __LINE__,	.flags = 0,.key.dd_key_true = STATIC_KEY_FALSE_INIT, /* 调试语句 默认为 关闭 状态 */
};
if (static_branch_unlikely(&descriptor.key.dd_key_false))__dynamic_pr_debug(&descriptor, pr_fmt(fmt), ##__VA_ARGS__);
/* (3) !HAVE_JUMP_LABEL && DEBUG */static struct _ddebug  __aligned(8)
__attribute__((section("__verbose"))) descriptor = {.modname = KBUILD_MODNAME,.function = __func__,.filename = __FILE__,.format = (fmt),.lineno = __LINE__,	.flags = _DPRINTK_FLAGS_PRINT, /* 调试语句 默认为 启用 状态 */
};
if (likely(descriptor.flags & _DPRINTK_FLAGS_PRINT))__dynamic_pr_debug(&descriptor, pr_fmt(fmt), ##__VA_ARGS__);
/* (4) !HAVE_JUMP_LABEL && !DEBUG */static struct _ddebug  __aligned(8)
__attribute__((section("__verbose"))) descriptor = {.modname = KBUILD_MODNAME,.function = __func__,.filename = __FILE__,.format = (fmt),.lineno = __LINE__,	.flags = 0, /* 调试语句 默认为 关闭 状态 */
};
if (unlikely(descriptor.flags & _DPRINTK_FLAGS_PRINT))__dynamic_pr_debug(&descriptor, pr_fmt(fmt), ##__VA_ARGS__);

从上面的代码片段看到,内核为每个 pr_debug()(, dev_dbg()) 语句建立了一个 struct _ddebug 对象来管理它们,在编译时struct _ddebug 放置在名为 "__verbose"section 内,在链接时通过 include/asm-generic/vmlinux.lds.h 如下链接脚本片段

/** .data section*/
#define DATA_DATA							\...... \/* implement dynamic printk debug */				\. = ALIGN(8);                                                   \VMLINUX_SYMBOL(__start___jump_table) = .;                       \KEEP(*(__jump_table))                                           \VMLINUX_SYMBOL(__stop___jump_table) = .;                        \. = ALIGN(8);							\VMLINUX_SYMBOL(__start___verbose) = .;                          \KEEP(*(__verbose))                                              \VMLINUX_SYMBOL(__stop___verbose) = .;				\......

将所有编译输出的、包含 struct _ddebug"__verbose" section 全部放入区间 [__start___verbose, __stop___verbose) 内。这些信息的用途将在后面分析章节进一步讨论,这里暂不做展开。
从上面的 4 种不同配置组合的代码片段,从上面的分析,dynamic_pr_debug() 的逻辑就很清楚了:通过判定一个标记,来决定是否调用 __dynamic_pr_debug() 打印调试输出。当然,对于上面 4 种可能出现的组合,分别在 HAVE_JUMP_LABEL!HAVE_JUMP_LABEL 时,判定的标记有所不同。对于 HAVE_JUMP_LABEL 的场景,稍显复杂,更多的细节读者可自行查阅 Jump label 相关资料,或参考博文 Linux: Jump label实现简析 ;而对于 !HAVE_JUMP_LABEL 场景,则是一目了然,不过是对 descriptor.flags 的判定。
最后看一下 __dynamic_pr_debug() 实现:

/* lib/dynamic_debug.c */static char *dynamic_emit_prefix(const struct _ddebug *desc, char *buf)
{int pos_after_tid;int pos = 0;*buf = '\0';if (desc->flags & _DPRINTK_FLAGS_INCL_TID) { /* 't' 标记 */if (in_interrupt())pos += snprintf(buf + pos, remaining(pos), "<intr> "); /* 在中断上下文,用 "<intr>" 代替 线程 ID */elsepos += snprintf(buf + pos, remaining(pos), "[%d] ", /* 线程 ID */task_pid_vnr(current));}pos_after_tid = pos;if (desc->flags & _DPRINTK_FLAGS_INCL_MODNAME) /* 'm' 标记 */pos += snprintf(buf + pos, remaining(pos), "%s:",desc->modname); /* 模块名 */if (desc->flags & _DPRINTK_FLAGS_INCL_FUNCNAME) /* 'f' 标记 */pos += snprintf(buf + pos, remaining(pos), "%s:",desc->function); /* 函数名 */if (desc->flags & _DPRINTK_FLAGS_INCL_LINENO) /* 'l' 标记 */pos += snprintf(buf + pos, remaining(pos), "%d:",desc->lineno); /* 行号 */if (pos - pos_after_tid)pos += snprintf(buf + pos, remaining(pos), " ");if (pos >= PREFIX_SIZE)buf[PREFIX_SIZE - 1] = '\0';return buf;
}void __dynamic_pr_debug(struct _ddebug *descriptor, const char *fmt, ...)
{va_list args;struct va_format vaf;char buf[PREFIX_SIZE];BUG_ON(!descriptor);BUG_ON(!fmt);va_start(args, fmt);vaf.fmt = fmt;vaf.va = &args;/* 最终调用 printk() 输出 */printk(KERN_DEBUG "%s%pV", dynamic_emit_prefix(descriptor, buf), &vaf);va_end(args);
}

4.2 debugfs 导出控制节点 control 实现

4.2.1 Dynamic debug 初始化

/* lib/dynamic_debug.c */static int __init dynamic_debug_init(void)
{struct _ddebug *iter, *iter_start;const char *modname = NULL;char *cmdline;int ret = 0;int n = 0, entries = 0, modct = 0;int verbose_bytes = 0;/** [__start___verbose, __stop___verbose):* 为系列接口* pr_debug() -> dynamic_pr_debug() * dev_dbg() -> dynamic_dev_dbg()* netdev_dbg() -> dynamic_netdev_dbg()* print_hex_dump_bytes(), print_hex_dump_debug() -> dynamic_hex_dump()* 每个调用定义的 struct _ddebug 对象列表。*/if (__start___verbose == __stop___verbose) {pr_warn("_ddebug table is empty in a CONFIG_DYNAMIC_DEBUG build\n");return 1;}iter = __start___verbose;modname = iter->modname;iter_start = iter;for (; iter < __stop___verbose; iter++) {entries++; /* dyndbg 调试语句总数 +1 *//* 非调试信息部分总长度: 模块名 + 函数名 + 文件名 + 格式字串 */verbose_bytes += strlen(iter->modname) + strlen(iter->function)+ strlen(iter->filename) + strlen(iter->format);if (strcmp(modname, iter->modname)) { /* 不同于当前模块 @modname 的 新模块 @iter->modname */modct++; /* 模块总数 +1 *//* 添加当前模块 @modname 到 模块表 @ddebug_tables */ret = ddebug_add_module(iter_start, n, modname);if (ret)goto out_err;/* * 更新当前 dyndb 模块:*/n = 0; /* 复位当前模块包含的 dyndb 调试语句总数 */modname = iter->modname; /* 更新当前 dyndb 模块名 */iter_start = iter; /* 当前模块的首个 dyndb 调试语句信息对象 */}n++; /* 当前模块包含的 dyndb 调试语句总数 */}ret = ddebug_add_module(iter_start, n, modname); /* 添加最后一个 dyndb 模块 */if (ret)goto out_err;ddebug_init_success = 1; /* 标记 dyndb 初始化完成 */vpr_info("%d modules, %d entries and %d bytes in ddebug tables, %d bytes in (readonly) verbose section\n",modct, entries, (int)(modct * sizeof(struct ddebug_table)),verbose_bytes + (int)(__stop___verbose - __start___verbose));/* apply ddebug_query boot param, dont unload tables on err *//* * 可通过内核命令行参数 ddebug_query="QUERY" 指定 BOOT 期间启用的调试语句。* 如 ddebug_query="file xxx.c +p" 开启 xxx.c 中的所有 dyndbg 调试语句。* 已废弃,内核不建议使用 ddebug_query="QUERY" 。* ddebug_setup_string[] 设置见后面的分析。*/if (ddebug_setup_string[0] != '\0') {pr_warn("ddebug_query param name is deprecated, change it to dyndbg\n");ret = ddebug_exec_queries(ddebug_setup_string, NULL);if (ret < 0)pr_warn("Invalid ddebug boot param %s\n",ddebug_setup_string);elsepr_info("%d changes by ddebug_query\n", ret);}/* now that ddebug tables are loaded, process all boot args* again to find and activate queries given in dyndbg params.* While this has already been done for known boot params, it* ignored the unknown ones (dyndbg in particular).  Reusing* parse_args avoids ad-hoc parsing.  This will also attempt* to activate queries for not-yet-loaded modules, which is* slightly noisy if verbose, but harmless.*//** 处理内核命令行参数: dyndbg="QUERY", module.dyndbg="QUERY"* module.dyndbg="QUERY" 用于内核内置模块。*/cmdline = kstrdup(saved_command_line, GFP_KERNEL);parse_args("dyndbg params", cmdline, NULL,0, 0, 0, NULL, &ddebug_dyndbg_boot_param_cb);kfree(cmdline);return 0;out_err:ddebug_remove_all_tables();return 0;
}
/* Allow early initialization for boot messages via boot param */
early_initcall(dynamic_debug_init);

前面演示的都是在内核启动完成后对 dyndbg 调试语句的控制,那如果在内核 BOOT 启动阶段,需要控制 dyndbg 语句的启停呢?方式一通过内核命令行参数 ddebug_query="QUERY"dyndbg="QUERY"module.dyndbg="QUERY" 来完成;方式二是在包含的调试接口头文件前定义 DEBUG 宏,这样 dyndbg 调试语句默认就是开启的(详见 4.1dynamic_pr_debug() 展开的 4 种形式)。其中 ddebug_query="QUERY" 已废弃,不建议使用;module.dyndbg="QUERY" 针对编译到内核镜像的内置模块,如 dynamic_debug_example.dyndbg 指定模块是外置模块 dynamic_debug_example.ko,虽然内核启动阶段仍然会对这种情形处理,但不会有任何效果,只有在 modprobe/insmod 时才会真正处理外置模块dyndbg 参数,这会后面的章节 4.3 外置模块的 dyndbg 参数 进行分析。
先看下 ddebug_setup_string[] 的设置过程(即解析内核命令行参数 ddebug_query="QUERY"):

/* lib/dynamic_debug.c */#define DDEBUG_STRING_SIZE 1024
static __initdata char ddebug_setup_string[DDEBUG_STRING_SIZE];static __init int ddebug_setup_query(char *str)
{if (strlen(str) >= DDEBUG_STRING_SIZE) {pr_warn("ddebug boot param string too large\n");return 0;}strlcpy(ddebug_setup_string, str, DDEBUG_STRING_SIZE);return 1;
}__setup("ddebug_query=", ddebug_setup_query);

ddebug_setup_string[] 的处理(即对内核命令行参数 ddebug_query="QUERY" 处理):

dynamic_debug_init()/* 处理 ddebug_query="QUERY" */ret = ddebug_exec_queries(ddebug_setup_string, NULL);

对内核命令行参数 dyndbg="QUERY"module.dyndbg="QUERY" 的处理:

dynamic_debug_init()cmdline = kstrdup(saved_command_line, GFP_KERNEL);parse_args("dyndbg params", cmdline, NULL,0, 0, 0, NULL, &ddebug_dyndbg_boot_param_cb);...ddebug_dyndbg_boot_param_cb()return ddebug_dyndbg_param_cb(param, val, NULL, 0);/* helper for ddebug_dyndbg_(boot|module)_param_cb */
static int ddebug_dyndbg_param_cb(char *param, char *val,const char *modname, int on_err)
{char *sep;sep = strchr(param, '.'); /* module.dyndbg="QUERY" */if (sep) {/* needed only for ddebug_dyndbg_boot_param_cb */*sep = '\0';modname = param;param = sep + 1;} /*else {*//* dyndbg="QUERY" *//*}*/if (strcmp(param, "dyndbg"))return on_err; /* determined by caller */ddebug_exec_queries((val ? val : "+p"), modname);return 0; /* query failure shouldnt stop module load */
}

可见,不管是处理 ddebug_query="QUERY",还是处理 module.dyndbg="QUERY"dyndbg="QUERY",最终都调用 ddebug_exec_queries() 来处理。下面分析 ddebug_exec_queries() 的处理过程:

/* handle multiple queries in query string, continue on error, returnlast error or number of matching callsites.  Module name is eitherin param (for boot arg) or perhaps in query string.
*/
static int ddebug_exec_queries(char *query, const char *modname)
{char *split;int i, errs = 0, exitcode = 0, rc, nfound = 0;for (i = 0; query; query = split) {split = strpbrk(query, ";\n");if (split)*split++ = '\0';query = skip_spaces(query);if (!query || !*query || *query == '#')continue;vpr_info("query %d: \"%s\"\n", i, query);/* 查询、修改(使能 或 关闭) 对应的 dyndbg 调试语句 */rc = ddebug_exec_query(query, modname);if (rc < 0) {errs++;exitcode = rc;} else {nfound += rc;}i++;}...if (exitcode)return exitcode;return nfound;
}/* 查询、修改(使能 或 关闭) 对应的 dyndbg 调试语句 */
static int ddebug_exec_query(char *query_string, const char *modname)
{unsigned int flags = 0, mask = 0;struct ddebug_query query;
#define MAXWORDS 9int nwords, nfound;char *words[MAXWORDS];nwords = ddebug_tokenize(query_string, words, MAXWORDS);if (nwords <= 0) {pr_err("tokenize failed\n");return -EINVAL;}/* check flags 1st (last arg) so query is pairs of spec,val *//* * . 解析 flags 操作符: + (增加), - (移除), = (赋值) * . 解析 flags: p, m, f, l, t, _ */if (ddebug_parse_flags(words[nwords-1], &flags, &mask)) {pr_err("flags parse failed\n");return -EINVAL;}/* 查询 dyndbg 调试语句表项 */if (ddebug_parse_query(words, nwords-1, &query, modname)) {pr_err("query parse failed\n");return -EINVAL;}/* actually go and implement the change */nfound = ddebug_change(&query, flags, mask); /* 按请求修改对应的 dyndbg 调试语句表项 */vpr_info_dq(&query, nfound ? "applied" : "no-match");return nfound;
}static int ddebug_change(const struct ddebug_query *query,unsigned int flags, unsigned int mask)
{int i;struct ddebug_table *dt;unsigned int newflags;unsigned int nfound = 0;char flagbuf[10];/* search for matching ddebugs */mutex_lock(&ddebug_lock);list_for_each_entry(dt, &ddebug_tables, link) {...for (i = 0; i < dt->num_ddebugs; i++) {struct _ddebug *dp = &dt->ddebugs[i];...nfound++;newflags = (dp->flags & mask) | flags;if (newflags == dp->flags)continue;/** dyndbg 系列接口* pr_debug() -> dynamic_pr_debug() * dev_dbg() -> dynamic_dev_dbg()* netdev_dbg() -> dynamic_netdev_dbg()* print_hex_dump_bytes(), print_hex_dump_debug() -> dynamic_hex_dump()* 在* (1) HAVE_JUMP_LABEL 情形,使能 或 关闭 static key ,*     通过 static key 状态决定是否输出调试信息;* (2) !HAVE_JUMP_LABEL 情形,仅修改 flags ,通过*     通过 flags 状态决定是否输出调试信息。*/
#ifdef HAVE_JUMP_LABELif (dp->flags & _DPRINTK_FLAGS_PRINT) {if (!(flags & _DPRINTK_FLAGS_PRINT))static_branch_disable(&dp->key.dd_key_true);} else if (flags & _DPRINTK_FLAGS_PRINT)static_branch_enable(&dp->key.dd_key_true);
#endif/* !HAVE_JUMP_LABEL 情形,仅修改 flags,是通过 flags */dp->flags = newflags;...}}mutex_unlock(&ddebug_lock);...return nfound;
}

4.2.2 Dynamic debug 的用户空间接口

4.2.2.1 control 文件的建立
static const struct file_operations ddebug_proc_fops = {.owner = THIS_MODULE,.open = ddebug_proc_open,.read = seq_read,.llseek = seq_lseek,.release = seq_release_private,.write = ddebug_proc_write
};static int __init dynamic_debug_init_debugfs(void)
{struct dentry *dir, *file;if (!ddebug_init_success)return -ENODEV;/* 创建 /sys/kernel/debug/dynamic_debug 目录 */dir = debugfs_create_dir("dynamic_debug", NULL);if (!dir)return -ENOMEM;/* 创建 /sys/kernel/debug/dynamic_debug/control 文件 */file = debugfs_create_file("control", 0644, dir, NULL,&ddebug_proc_fops);if (!file) {debugfs_remove(dir);return -ENOMEM;}return 0;
}/* Debugfs setup must be done later */
fs_initcall(dynamic_debug_init_debugfs);
4.2.2.2 control 文件的查询(读)

用户空间通过查询(读) control 文件,查看系统中所有 dyndbg 调试语句的状态信息:

# cat /sys/kernel/debug/dynamic_debug/control
[......]

这在前面已经演示过,这里主要看它的内核实现。cat 首先触发 ddebug_proc_open() 调用:

static const struct seq_operations ddebug_proc_seqops = {.start = ddebug_proc_start,.next = ddebug_proc_next,.show = ddebug_proc_show,.stop = ddebug_proc_stop
};static int ddebug_proc_open(struct inode *inode, struct file *file)
{vpr_info("called\n");return seq_open_private(file, &ddebug_proc_seqops,sizeof(struct ddebug_iter));
}

接着调用 ddebug_proc_start() :

ddebug_proc_start()dp = ddebug_iter_first(iter); /* 取 @ddebug_tables 表中第1条 dyndbg 调试语句信息 */

然后通过 ddebug_proc_show() 打印第 1 条信息:

static int ddebug_proc_show(struct seq_file *m, void *p)
{struct ddebug_iter *iter = m->private;struct _ddebug *dp = p;char flagsbuf[10];vpr_info("called m=%p p=%p\n", m, p);if (p == SEQ_START_TOKEN) { /* 第 1 条信息前,加个 标题栏 打印 */seq_puts(m,"# filename:lineno [module]function flags format\n");return 0;}/* 打印一条 dyndbg 调试语句信息 struct _ddebug */seq_printf(m, "%s:%u [%s]%s =%s \"",trim_prefix(dp->filename), dp->lineno,iter->table->mod_name, dp->function,ddebug_describe_flags(dp, flagsbuf, sizeof(flagsbuf)));seq_escape(m, dp->format, "\t\r\n\"");seq_puts(m, "\"\n");return 0;
}

然后通过 ddebug_proc_next() 取下一条数据,再通过 ddebug_proc_show() 打印:

4.2.2.3 control 文件的修改(写)

用户空间通过修改(写) control 文件,控制 dyndbg 调试的启停:

# echo -n "command ::= match-spec* flags-spec" > /sys/kernel/debug/dynamic_debug/control

cat 一样,echo 首先触发 ddebug_proc_open() 调用,这个前面已经进行分析;接下来 echo 会触发 ddebug_proc_write() 调用:

#define USER_BUF_PAGE 4096
static ssize_t ddebug_proc_write(struct file *file, const char __user *ubuf,size_t len, loff_t *offp)
{char *tmpbuf;int ret;if (len == 0)return 0;if (len > USER_BUF_PAGE - 1) {pr_warn("expected <%d bytes into control\n", USER_BUF_PAGE);return -E2BIG;}tmpbuf = memdup_user_nul(ubuf, len);if (IS_ERR(tmpbuf))return PTR_ERR(tmpbuf);vpr_info("read %d bytes from userspace\n", (int)len);ret = ddebug_exec_queries(tmpbuf, NULL);kfree(tmpbuf);if (ret < 0)return ret;*offp += len;return len;
}

可以看到,ddebug_proc_write() 调用 了 ddebug_exec_queries(),这和前面解析内核命令行参数 ddebug_query="QUERY"dyndbg="QUERY"module.dyndbg="QUERY" 的逻辑一样,都是查询、修改(启停)匹配的 dyndbg 调试的状态,在次不再赘述。

4.3 外置模块的 dyndbg 参数

4.3.1 外置模块 dyndbg 参数的解析

到此,基本分析了所有 Dynamic debug 功能的相关实现分析,但还缺少对外置模块dyndbg 参数的分析。用户空间通过内核接口 sys_finit_module() 加载模块,在加载模块时,解析 dyndbg 参数以控制 dyndbg 调试语句的启停:

/* kernel/module.c */SYSCALL_DEFINE3(finit_module, int, fd, const char __user *, uargs, int, flags)
{...return load_module(&info, uargs, flags);
}static int load_module(struct load_info *info, const char __user *uargs,int flags)
{.../* Module is ready to execute: parsing args may do that. *//* 解析内核模块参数(包括 dyndbg 参数) */after_dashes = parse_args(mod->name, mod->args, mod->kp, mod->num_kp,-32768, 32767, mod,unknown_module_param_cb);...
}static int unknown_module_param_cb(char *param, char *val, const char *modname,void *arg)
{.../* Check for magic 'dyndbg' arg */ret = ddebug_dyndbg_module_param_cb(param, val, modname);if (ret != 0)pr_warn("%s: unknown parameter '%s' ignored\n", modname, param);return 0;
}/* lib/dynamic_debug.c */
int ddebug_dyndbg_module_param_cb(char *param, char *val, const char *module)
{vpr_info("module: %s %s=\"%s\"\n", module, param, val);return ddebug_dyndbg_param_cb(param, val, module, -ENOENT);
}

看到了吧,这里和内核启动阶段解析 dyndbgmodule.dyndbg 参数一样,都调用了 ddebug_dyndbg_param_cb(),流程与前面一样,这里就不再赘述。

4.3.2 外置模块 dyndbg 参数的提供方式

外置模块 dyndbg 参数的提供方式包含如下 3 种形式,这 3 种形式如果都配置了,后面的配置会覆盖前面的,也即 4.3.2.1 通过模块配置文件优先级最低4.3.2.3 通过模块参数优先级最高

4.3.2.1 通过模块配置文件

通过模块配置文件 /etc/modprobe.d/*.conf 进行配置,modprobe 会解析它:

options foo dyndbg=+pt
options foo dyndbg # defaults to +p
4.3.2.2 通过内核命令行参数
foo.dyndbg=" func bar +p; func buz +mp"

前面提到,外置模块 dyndbg 参数虽然在内核启动阶段不生效,但在 modprobe 加载模块时,modprobe 会从内核命令行参数提取,并传给内核接口解析并生效。

4.3.2.3 通过模块参数
modprobe foo dyndbg==pmf # override previous settings

对这最后一种形式,以前面的 dynamic_debug_example.ko 外置模块为例,来示范一下:

root@qemu-ubuntu:~# modprobe dynamic_debug_example dyndbg==pmflt
root@qemu-ubuntu:~# lsmod
Module                  Size  Used by
dynamic_debug_example    16384  0
root@qemu-ubuntu:~# dmesg | tail -n 8
[  126.608822] dyndbg test module init.
[  126.626985] [1005] dynamic_debug_example:dyndbg_task_fn:16: dyndbg test message 0
[  128.688377] [1005] dynamic_debug_example:dyndbg_task_fn:16: dyndbg test message 1
[  130.761998] [1005] dynamic_debug_example:dyndbg_task_fn:16: dyndbg test message 2
[  132.861402] [1005] dynamic_debug_example:dyndbg_task_fn:16: dyndbg test message 3
[  134.937928] [1005] dynamic_debug_example:dyndbg_task_fn:16: dyndbg test message 4
[  137.016631] [1005] dynamic_debug_example:dyndbg_task_fn:16: dyndbg test message 5
root@qemu-ubuntu:~# modprobe -r dynamic_debug_example
root@qemu-ubuntu:~# lsmod
Module                  Size  Used by
root@qemu-ubuntu:~# dmesg | tail -n 8
[  189.167825] [1005] dynamic_debug_example:dyndbg_task_fn:16: dyndbg test message 30
[  191.245793] [1005] dynamic_debug_example:dyndbg_task_fn:16: dyndbg test message 31
[  193.402753] [1005] dynamic_debug_example:dyndbg_task_fn:16: dyndbg test message 32
[  195.477486] [1005] dynamic_debug_example:dyndbg_task_fn:16: dyndbg test message 33
[  197.554432] [1005] dynamic_debug_example:dyndbg_task_fn:16: dyndbg test message 34
[  199.631214] [1005] dynamic_debug_example:dyndbg_task_fn:16: dyndbg test message 35
[  201.708996] [1005] dynamic_debug_example:dyndbg_task_fn:16: dyndbg test message 36
[  203.775266] dyndbg test module exit.

5. Dynamic debug (dyndbg) 的优缺点

Dynamic debug 的优点显而易见,它可以在不重新编译内核的前提下,动态的在运行时启用、关闭调试语句输出,这带来了灵活性;Dynamic debug 的缺点也很明显,由于它使用额外的数据来记录每个 KERN_DEBUG 类型调试语句的状态信息,所以它增大了内核的体积。从前面的 Kconfig 配置,Dynamic debug 功能的作者注释大约增长 2%,经笔者实测,不是很准确,有时候不到 2%。同时插入判定语句多少也会带来一些开销。Linux 内核在默认情形下不会开启 Dynamic debug

6. 参考资料

[1] Dynamic debug
[2] 浅析 linux kernel dynamic debug
[3] 动态调试
[4] linux dynamic debug

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/850766.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

力扣hot100学习记录(十二)

94. 二叉树的中序遍历 给定一个二叉树的根节点 root&#xff0c;返回它的中序遍历。 题意 给定一个二叉树&#xff0c;返回它的中序遍历 思路 采用递归的思想&#xff0c;只要根节点不为空&#xff0c;则一直递归遍历左子树&#xff0c;然后将根节点的值存入结果&#xff0c;…

AutoCAD Mechanical机械版专业的计算机辅助设计软件安装包下载安装!

AutoCAD机械版作为一款专业的计算机辅助设计软件&#xff0c;不仅具备卓越的二维绘图功能&#xff0c;更是拥有令人瞩目的3D建模工具&#xff0c;为机械设计师们提供了前所未有的创作空间。 在AutoCAD机械版的3D建模环境中&#xff0c;用户可以借助一系列简洁明了的命令&#…

数智融通 创新发展|亚信科技携AntDB、Data OS与隐私计算产品,赋能企业高质量发展

5月21日&#xff0c;亚信科技在云端举办了一场别开生面的研讨会——“数智融通 创新发展”&#xff0c;聚焦企业数智化升级的前沿话题。资深产品经理和技术架构师们面对面深入交流&#xff0c;分享创新成果与实战案例&#xff0c;共同探索企业数智化转型的新路径。 图1&#xf…

网络安全形势与WAF技术分享

我一个朋友的网站&#xff0c;5月份时候被攻击了&#xff0c;然后他找我帮忙看看&#xff0c;我看他的网站、网上查资料&#xff0c;不看不知道&#xff0c;一看吓一跳&#xff0c;最近几年这网络安全形势真是不容乐观&#xff0c;在网上查了一下资料&#xff0c;1、中国信息通…

基础数据结构 -- 栈

1. 简介 堆栈又名栈&#xff08;stack&#xff09;&#xff0c;他是计算机科学中最基础的数据结构之一。可以算是一种受限制的线性结构&#xff0c;&#xff0c;具有后进先出&#xff08;LIFO&#xff0c; Last In First Out&#xff09;的特性。由于此特性&#xff0c;堆栈常用…

OPenCV的重要结构体Mat

一 Mat Mat是什么&#xff1f; Mat有什么好处&#xff1f; class CV_EXPORTS Mat{ public: ... int dims;//维数 int rows,cols;//行列数 uchar *data;//存储数据的指针 int *refcount;//引用计数 ...};二 Mat属性 三 Mat拷贝 1 Mat浅拷贝 Mat A Aimread(file,IMREAD_COLOR) …

http接口上传文件响应413:413 Request Entity Too Large

目录 一、场景简介二、异常展示三、原因四、解决 一、场景简介 1、服务端有经过nginx代理 2、上传文件超过5M时&#xff0c;响应码为413 3、上传文件小于5M时&#xff0c;上传正常 二、异常展示 三、原因 nginx限制了上传数据的大小 四、解决 扩大nginx上传数据的大小 步…

【详细的Kylin使用心得,什么是Kylin?】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

秋招突击——算法打卡——6/5——提高{(状态机模型)股票买卖、(单调队列优化DP)最大子序列和}——新做:{考试的最大困扰度}

文章目录 提高(状态机模型)股票买卖IV思路分析实现代码参考代码 新作考试的最大困扰度个人实现参考思路 总结 提高 (状态机模型)股票买卖IV 上一次的思路总结&#xff0c;上次写的时候忘记总结了&#xff0c;现在重新画一下图 思路分析 这道题是一个经典的状态机模型&#…

用动态IP采集数据总是掉线是为什么?该怎么解决?

动态IP可以说是做爬虫、采集数据、搜集热门商品信息中必备的代理工具&#xff0c;但在爬虫的使用中&#xff0c;总是会遇到动态IP掉线的情况&#xff0c;从而影响使用效率&#xff0c;本文将探讨动态IP代理掉线的几种常见原因&#xff0c;并提供解决方法&#xff0c;以帮助大家…

牛客网刷题 | BC119 最高分与最低分之差

目前主要分为三个专栏&#xff0c;后续还会添加&#xff1a; 专栏如下&#xff1a; C语言刷题解析 C语言系列文章 我的成长经历 感谢阅读&#xff01; 初来乍到&#xff0c;如有错误请指出&#xff0c;感谢&#xff01; 描述 输入n个成绩&#…

CleanMyMac2025破解版crack+keygen

【CleanMyMac】这款神奇的软件&#xff0c;让我彻底告别了电脑卡顿的困扰&#xff01;&#x1f62e;‍&#x1f4a8; CleanMyMac绿色免费版下载如下&#xff1a;记得保存哈&#xff0c;以防失效&#xff1a; https://pan.quark.cn/s/9b08114cf404 CleanMyMac X2024全新版下载…

指针(初阶2)“野指针以及指针运算”

目录 一.野指针 二.如何避免野指针 三.指针运算 1、指针&#xff08;-&#xff09;整数 2、指针 - 指针 3、指针关系运算 小编在这里声明一下&#xff0c;将某一块的知识点分为上中下或者1&#xff0c;2&#xff0c;3来编写不是为了增加小编的文章总量&#xff0c;也不是故意这…

279 基于matlab的粒子群集法对铁路电能质量控制系统的容量避行优化设计

基于matlab的粒子群集法对铁路电能质量控制系统的容量避行优化设计。计算出满足功率因素、电压不平衡度等电能指标的条件下。RPC所需要的补偿功率。求得所需最小的系统客量。该设计能快速计算出符合系统设定指标的各项最优补偿功率。并通过sumulink份真。检验设计参数的准确性。…

泛微开发修炼之旅--13通过Ecology拦截器(注解的方式),拦截后端接口,实现接口执行成功后或执行前操作源码示例

文章链接&#xff1a;泛微开发修炼之旅--13通过Ecology拦截器(注解的方式)&#xff0c;拦截后端接口&#xff0c;实现接口执行成功后或执行前操作源码示例

R语言探索与分析20-北京市气温预测分析

一、序言 近年来&#xff0c;人类大量燃烧煤炭、天然气等含碳燃料导致温室气 体过度排放&#xff0c;大量温室气体强烈吸收地面辐射中的红外线&#xff0c;造 成温室效应不断累积&#xff0c;使得地球温度上升&#xff0c;造成全球气候变暖。气象温度的预测一直以来都是天气预…

C语言详解(结构体)

Hi~&#xff01;这里是奋斗的小羊&#xff0c;很荣幸各位能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎~~ &#x1f4a5;个人主页&#xff1a;小羊在奋斗 &#x1f4a5;所属专栏&#xff1a;C语言 本系列文章为个人学习笔记&#xff0c;在这里撰写成文一…

网关API(SpringCloudGateway)如何自定义Filter

1.前言 SpringCloud 虽然给我们提供了很多过滤器&#xff0c;但是这些过滤器功能都是固定的&#xff0c;无法满足用户的各式各样的需求。因此SpringCloud提供了过滤器的扩展功能自定过滤器。 开发者可以根据自己的业务需求自定义过滤器。 2. 自定义 GatewayFilter(局部过滤器)…

微信小程序 画布canvas

属性说明 属性类型默认值必填说明最低版本typestring否指定 canvas 类型&#xff0c;支持 2d (2.9.0) 和 webgl (2.7.0)2.7.0canvas-idstring否canvas 组件的唯一标识符&#xff0c;若指定了 type 则无需再指定该属性1.0.0disable-scrollbooleanfalse否当在 canvas 中移动时且…

【Python】解决Python报错:KeyError: ‘username‘

​​​​ 文章目录 引言1. 错误详解2. 常见的出错场景2.1 用户输入处理错误2.2 动态数据源 3. 解决方案3.1 使用 get() 方法3.2 检查键是否存在 4. 预防措施4.1 数据验证4.2 使用默认字典 (defaultdict) 结语 引言 在Python开发中&#xff0c;处理字典时遇到 KeyError 是一种…