eBPF 实时捕获键盘输入

eBPF 实时捕获键盘输入

本文将带你一步步实现一个基于eBPF kprobe的键盘记录功能,通过Go语言配合libbpfgo,你将学会如何无损地监控系统键盘输入,并从中获取实时数据,进一步提高系统安全和监控能力。


1. 说明

本文属于专栏 Go语言+libbpfgo实战eBPF开发,示例代码目录为 040

如何下载并运行代码,请参考 专栏介绍。

注: 老学员可以直接 git pull 拉取最新代码。


2. 引言

在本篇文章中,我们将利用 eBPF 的 kprobe 技术捕捉键盘输入事件,实现一个简单的键盘记录功能。你将学习如何在内核层面监控 input_handle_event 函数,并将捕获到的按键事件传递到用户空间进行处理。整个过程高效且非侵入式,适用于实时监控场景。💡

eBPF 允许在内核中动态加载和执行代码。借助 kprobe,我们可以在 input_handle_event 函数入口处挂载探针,从而捕获关键事件。

  • 键盘事件类型:我们关注 EV_KEY 类型的按键事件,并仅记录按下(value=1)时的事件。
  • 事件传递方式:通过 bpf_perf_event_output 将事件数据发送到用户空间,用户空间程序使用 perf buffer 进行实时读取。

这样既能精准监控输入事件,又无需对系统进行侵入式修改。


3. 原理详解

3.1 当你按下一个键盘上的按键时,发生了什么?

流程图

硬件中断 → 键盘驱动 → 输入子系统 → TTY 行规程 → Shell 进程 → TTY 回显 → 终端渲染

在 Linux 系统中,从按下键盘到字符显示在终端上,涉及 硬件中断处理、内核子系统协作和用户空间进程交互。以下是详细流程和关键函数。

1. 硬件中断触发

  • 硬件层面:键盘控制器(如 PS/2 或 USB)检测到按键动作,生成 硬件中断(PS/2 键盘通常使用 IRQ 1)。
  • 中断控制器:将中断路由至 CPU,CPU 通过中断向量表调用对应的 中断处理程序

2. 内核中断处理

  • 中断处理函数:内核注册的键盘中断处理程序(irq_handler)被触发。
    • 关键函数request_irq() 注册中断处理函数,如 PS/2 键盘的 kbd_event 或 USB 键盘的 usb_kbd_irq
  • 读取扫描码:驱动从键盘控制器读取 扫描码(Scancode)(如 inb(0x60) 读取 PS/2 键盘数据端口)。
  • 转换为键码:扫描码转换为 键码(Keycode)(如 kbd_keycode 处理映射关系)。

3. 输入子系统(Input Subsystem)

  • 生成输入事件:键码通过输入子系统封装为 input_event 结构(包含时间戳、键值、动作等)。
    • 关键函数input_event()input_handle_event()
  • 传递事件:事件通过 /dev/input/eventX 设备节点传递,供用户空间程序(如终端)读取。
    • 关键结构struct input_handler 负责事件路由(如 evdev_handler)。

4. TTY 子系统处理

  • 绑定到 TTY:输入事件传递到当前活动的 TTY(如 /dev/tty1)。
    • 关键函数tty_insert_flip_char() 将字符写入 TTY 的 flip buffer。
  • 行规程(Line Discipline):处理特殊字符(如回车、退格)。默认行规程为 n_tty
    • 关键函数n_tty_receive_char() 解析字符并执行回显逻辑。
  • 刷新缓冲区:调用 tty_flip_buffer_push() 推送数据至 TTY 读队列。

5. 用户空间进程读取输入

  • 前台进程:Shell(如 Bash)通过 read() 系统调用读取 TTY 设备的输入。
    • 关键路径read()tty_read()copy_from_read_buf()
  • 行编辑模式(Canonical Mode):启用时,输入缓存在内核直到用户按下回车。

6. 字符显示到终端

  • 回显(Echo):默认 n_tty 规程会自动回显字符到终端。
    • 关键函数n_tty_receive_char() 调用 echo_char() 进行回显。
  • 终端写入:字符通过 write() 系统调用发送至终端显示。
    • 关键路径write()tty_write()do_tty_write() → 终端驱动的写函数。
  • 终端渲染方式
    • 物理终端:通过显卡驱动(如 vt_console_print())直接输出。
    • 伪终端(PTY):如 SSH 或终端模拟器(如 GNOME Terminal),字符通过 PTY 主从设备传输,最终由终端模拟器渲染。

3.2 在哪里捕获键盘输入事件?

在 Linux 系统中,键盘输入事件可以在不同层次进行捕获,主要包括 内核态用户态,以下是常见的捕获位置及方法。

1. 内核态捕获

1.1 中断处理程序(IRQ 级别)

  • 最底层的捕获方式,在键盘触发 硬件中断 时,内核的 中断处理函数 被调用。
  • 相关代码位置:drivers/input/keyboard/atkbd.c(PS/2 键盘) 或 drivers/hid/usbhid/usbkbd.c(USB 键盘)。
  • 关键函数:
    • irq_handler_t kbd_event()(PS/2)
    • usb_kbd_irq()(USB)
    • 读取 扫描码 并传递至 输入子系统

1.2 输入子系统(Input Subsystem)

  • 内核的 input_event 机制封装了键盘输入事件。
  • 相关设备节点:/dev/input/eventX
  • 关键函数:
    • input_event() 生成键盘事件。
    • input_handle_event() 处理事件并分发至用户空间。
2. 用户态捕获

2.1 通过 /dev/input/eventX 捕获原始输入事件

  • 适用于读取底层输入设备(适用于键盘监听、按键统计等)。

  • 代码示例(使用 evdev 接口):

    #include <stdio.h>
    #include <fcntl.h>
    #include <linux/input.h>int main() {int fd = open("/dev/input/event2", O_RDONLY);if (fd < 0) {perror("open");return 1;}struct input_event ev;while (read(fd, &ev, sizeof(ev)) > 0) {if (ev.type == EV_KEY && ev.value == 1) {printf("Key %d pressed\n", ev.code);}}close(fd);return 0;
    }
    

2.2 通过 /dev/tty 读取终端输入

  • 适用于读取当前终端的键盘输入(受 TTY 规程控制)。

  • 代码示例(读取标准输入):

    #include <stdio.h>int main() {char c;while (1) {c = getchar();printf("Pressed: %c\n", c);}return 0;
    }
    

2.3 使用 libinput 监听键盘输入(Wayland 环境)

  • 适用于现代桌面环境(X11/Wayland)。

  • 监听系统级输入事件,适用于图形界面应用。

  • 代码示例(Python):

    from evdev import InputDevice, categorize, ecodesdev = InputDevice('/dev/input/event2')
    for event in dev.read_loop():if event.type == ecodes.EV_KEY:print(categorize(event))
    

2.4 监听 X11 按键事件(X Window System)

  • 适用于 GUI 应用,使用 Xlib 监听按键。

  • 代码示例(Python + python-xlib):

    from Xlib import display, Xd = display.Display()
    r = d.screen().root
    r.grab_keyboard(True, X.GrabModeAsync, X.GrabModeAsync, X.CurrentTime)
    while True:event = r.display.next_event()if event.type == X.KeyPress:print("Key pressed")
    

3.3 选择合适的捕获方式

需求推荐方法
低级输入监控(扫描码)内核 input_event 或者 input_handle_event API
监听所有键盘事件读取 /dev/input/eventX
监听终端输入读取 /dev/tty
GUI 应用按键捕获Xliblibinput

不同场景下,可以选择合适的方式进行键盘输入捕获。

本文将演示通过 hook 内核函数 input_handle_event 实现键盘记录功能。


4. 代码详解

4.1 bpf代码

4.1.1 整体逻辑
  • 加载入口: 代码通过SEC("kprobe/input_handle_event")挂载到内核的input_handle_event函数。
  • 事件过滤: 判断传入的type是否为EV_KEYvalue是否等于1,只在按键按下时进行处理。
  • 数据发送: 如果满足条件且按键码小于MAX_KEYS,则利用bpf_perf_event_output将事件数据发送到用户空间。
4.1.2 代码细节解析
  • 头文件与宏定义:

    • 包含了vmlinux.hbpf_helpers.hbpf_tracing.hbpf_core_read.h等头文件,保证代码能调用内核相关的API。
    • 定义了EV_KEYMAX_KEYS,分别代表按键事件类型和允许的最大按键数量。
  • 事件结构体:

    struct event_t {u32 type;u32 code;u32 value;
    };
    

    该结构体用于保存按键事件数据,包括事件类型、按键码和按键状态。

  • kprobe函数:

    SEC("kprobe/input_handle_event")
    int BPF_KPROBE(hook_input_handle_event, struct input_dev *dev, unsigned int type, unsigned int code, int value)
    {struct event_t event = { 0, };event.type = type;event.code = code;event.value = value;if (type == EV_KEY && value == 1){if(code < MAX_KEYS) {bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event, sizeof(event));} }return 0;
    }
    
    • 整体逻辑: 在每次input_handle_event调用时,将输入数据封装到event中;判断条件满足时,将事件通过perf event发送。
    • 细节说明:
      • SEC("kprobe/input_handle_event"):声明该函数为kprobe处理函数。
      • BPF_KPROBE宏用于自动生成探针入口。
      • bpf_perf_event_output负责将数据传递到用户空间,不涉及错误处理代码。

4.2 go代码

4.2.1 main.go
整体逻辑
  • 信号注册: 利用signal.NotifyContext注册系统信号(如SIGINTSIGTERM),确保程序能优雅退出。
  • 加载BPF程序: 调用BpfLoadAndAttach加载并附加BPF程序文件bpf.o
  • 创建perf buffer: 初始化perf buffer,通过eventsChannellostChannel接收事件数据及丢失事件统计。
  • 事件处理: 启动一个事件处理循环,实时解析并打印键盘事件,直到收到退出信号。
代码细节解析
  • 日志设置: 使用logrus设置日志级别和时间格式,使调试信息更直观。
  • 资源管理: 利用defer语句确保BPF模块和perf buffer在程序结束时被正确关闭。
  • 事件循环:
    for {// 循环接收事件select {// 接收到事件时打印事件信息case data := <-eventsChannel:var event Eventerr := event.Parse(data)if err != nil {log.Printf("parse event error: %v", err)} else {log.Println(event.String())}}
    }
    
    该循环实时响应来自内核空间的事件数据。

4.2.2 event.go
整体逻辑
  • 数据结构: 定义了Event结构体,用于与内核传来的event_t数据一一对应。
  • 数据解析: Parse方法利用binary.Read将接收到的字节流转化为结构体数据。
  • 数据展示: String方法调用keyStr函数,将按键码转换为对应的按键名称,便于直观展示。
代码细节解析
package mainimport ("bytes""encoding/binary"
)type Event struct {Type  uint32Code  uint32Value uint32
}// 解析event数据
func (e *Event) Parse(data []byte) error {err := binary.Read(bytes.NewBuffer(data), binary.LittleEndian, e)if err != nil {return err}return nil
}// 转换成字符串
func (e *Event) String() string {return keyStr(int(e.Code))
}const MAX_KEYS = 256var keyNames = [MAX_KEYS]string{0: "RESERVED",1: "ESC",2: "1", 3: "2", 4: "3", 5: "4", 6: "5", 7: "6", 8: "7", 9: "8", 10: "9", 11: "0",12: "MINUS", 13: "EQUAL", 14: "BACKSPACE", 15: "TAB",16: "Q", 17: "W", 18: "E", 19: "R", 20: "T", 21: "Y", 22: "U", 23: "I", 24: "O", 25: "P",26: "LEFTBRACE", 27: "RIGHTBRACE", 28: "ENTER", 29: "LEFTCTRL",30: "A", 31: "S", 32: "D", 33: "F", 34: "G", 35: "H", 36: "J", 37: "K", 38: "L", 39: "SEMICOLON",40: "APOSTROPHE", 41: "GRAVE", 42: "LEFTSHIFT", 43: "BACKSLASH",44: "Z", 45: "X", 46: "C", 47: "V", 48: "B", 49: "N", 50: "M", 51: "COMMA", 52: "DOT", 53: "SLASH",54: "RIGHTSHIFT", 55: "KPASTERISK", 56: "LEFTALT", 57: "SPACE",58: "CAPSLOCK", 59: "F1", 60: "F2", 61: "F3", 62: "F4", 63: "F5", 64: "F6", 65: "F7", 66: "F8", 67: "F9", 68: "F10",69: "NUMLOCK", 70: "SCROLLLOCK", 71: "KP7", 72: "KP8", 73: "KP9", 74: "KPMINUS", 75: "KP4", 76: "KP5", 77: "KP6", 78: "KPPLUS",79: "KP1", 80: "KP2", 81: "KP3", 82: "KP0", 83: "KPDOT",85: "ZENKAKUHANKAKU", 86: "102ND", 87: "F11", 88: "F12", 89: "RO", 90: "KATAKANA", 91: "HIRAGANA", 92: "HENKAN", 93: "KATAKANAHIRAGANA", 94: "MUHENKAN",95: "KPJPCOMMA", 96: "KPENTER", 97: "RIGHTCTRL", 98: "KPSLASH", 99: "SYSRQ", 100: "RIGHTALT", 101: "LINEFEED",102: "HOME", 103: "UP", 104: "PAGEUP", 105: "LEFT", 106: "RIGHT", 107: "END", 108: "DOWN", 109: "PAGEDOWN", 110: "INSERT", 111: "DELETE",112: "MACRO", 113: "MUTE", 114: "VOLUMEDOWN", 115: "VOLUMEUP", 116: "POWER", 117: "KPEQUAL", 118: "KPPLUSMINUS", 119: "PAUSE",120: "SCALE", 121: "KPCOMMA", 122: "HANGEUL", 123: "HANJA", 124: "YEN", 125: "LEFTMETA", 126: "RIGHTMETA", 127: "COMPOSE",128: "STOP", 129: "AGAIN", 130: "PROPS", 131: "UNDO", 132: "FRONT", 133: "COPY", 134: "OPEN", 135: "PASTE", 136: "FIND", 137: "CUT",138: "HELP", 139: "MENU", 140: "CALC", 141: "SETUP", 142: "SLEEP", 143: "WAKEUP", 144: "FILE", 145: "SENDFILE", 146: "DELETEFILE", 147: "XFER",148: "PROG1", 149: "PROG2", 150: "WWW", 151: "MSDOS", 152: "COFFEE", 153: "ROTATE_DISPLAY", 154: "CYCLEWINDOWS", 155: "MAIL", 156: "BOOKMARKS",157: "COMPUTER", 158: "BACK", 159: "FORWARD", 160: "CLOSECD", 161: "EJECTCD", 162: "EJECTCLOSECD", 163: "NEXTSONG", 164: "PLAYPAUSE",165: "PREVIOUSSONG", 166: "STOPCD", 167: "RECORD", 168: "REWIND", 169: "PHONE", 170: "ISO", 171: "CONFIG", 172: "HOMEPAGE", 173: "REFRESH",174: "EXIT", 175: "MOVE", 176: "EDIT", 177: "SCROLLUP", 178: "SCROLLDOWN", 179: "KPLEFTPAREN", 180: "KPRIGHTPAREN", 181: "NEW", 182: "REDO",183: "F13", 184: "F14", 185: "F15", 186: "F16", 187: "F17", 188: "F18", 189: "F19", 190: "F20", 191: "F21", 192: "F22", 193: "F23", 194: "F24",200: "PLAYCD", 201: "PAUSECD", 202: "PROG3", 203: "PROG4", 204: "ALL_APPLICATIONS", 205: "SUSPEND", 206: "CLOSE", 207: "PLAY", 208: "FASTFORWARD",209: "BASSBOOST", 210: "PRINT", 211: "HP", 212: "CAMERA", 213: "SOUND", 214: "QUESTION", 215: "EMAIL", 216: "CHAT", 217: "SEARCH", 218: "CONNECT",219: "FINANCE", 220: "SPORT", 221: "SHOP", 222: "ALTERASE", 223: "CANCEL", 224: "BRIGHTNESSDOWN", 225: "BRIGHTNESSUP", 226: "MEDIA",227: "SWITCHVIDEOMODE", 228: "KBDILLUMTOGGLE", 229: "KBDILLUMDOWN", 230: "KBDILLUMUP", 231: "SEND", 232: "REPLY", 233: "FORWARDMAIL", 234: "SAVE",235: "DOCUMENTS", 236: "BATTERY", 237: "BLUETOOTH", 238: "WLAN", 239: "UWB", 240: "UNKNOWN", 241: "VIDEO_NEXT", 242: "VIDEO_PREV",243: "BRIGHTNESS_CYCLE", 244: "BRIGHTNESS_AUTO", 245: "DISPLAY_OFF", 246: "WWAN", 247: "RFKILL", 248: "MICMUTE",
}func keyStr(code int) string {if code < 0 || code >= MAX_KEYS {return "UNKNOWN"}name := keyNames[code]if name == "" {return "UNKNOWN"}return name
}
  • Parse方法:
    • 通过binary.LittleEndian格式解析数据,确保与内核传输的数据格式一致。
  • String方法与键值映射:
    • 利用预定义的keyNames数组映射按键码到具体按键名称。
    • keyStr函数通过判断码值范围返回对应的字符串,如果不存在则返回"UNKNOWN"

5. 总结

本文通过Go语言结合libbpfgo演示了如何利用eBPF实现键盘记录功能,具体如下:

  • 实时性: 直接利用内核kprobe捕捉键盘事件,无需轮询,实时性极佳⏱️。
  • 高效性: 通过perf buffer将数据高效传递到用户空间,确保系统性能不受影响。

该方案不仅适用于键盘事件监控,还可以拓展到其他系统监控和安全检测场景。动手实践后,你也可以根据业务需求调整代码,实现更多高级功能。


6. 练习题

  1. 按键过滤: 修改代码使其只记录特定按键(如ESCENTER)的事件,并在用户空间进行特别处理。
  2. 数据统计: 增加功能,对每种按键的出现次数进行统计,并定时输出统计结果。
  3. 扩展应用: 在BPF程序中增加其他类型事件(如鼠标事件)的监控,探索更多内核事件的捕捉方式。

🚀 动手试试吧!遇到问题欢迎留言交流,一起进步!

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

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

相关文章

APB-清华联合腾讯等机构推出的分布式长上下文推理框架

APB (Accelerating Distributed Long-Context Inference by Passing Compressed Context Blocks acrossGPUs)是清华大学等机构联合提出的分布式长上下文推理框架。通过稀疏注意力机制和序列并行推理方式&#xff0c;有效解决了大模型处理长文本时的效率瓶颈。APB采用更小的Anch…

数据库分库分表介绍

分库分表是解决数据库性能瓶颈的常用技术手段&#xff0c;主要用于应对数据量过大、读写压力过高的问题。通过将数据分散到多个数据库或表中&#xff0c;可以提高系统的扩展性和性能。 1. 分库分表的核心概念 &#xff08;1&#xff09;分库 定义&#xff1a;将数据分散到多个…

#mapreduce打包#maven:could not resolve dependencies for project

打包报错&#xff1a; #报错信息&#xff1a; [ERROR] Failed to execute goal on project mapreduce_teacher1: Could not resolve dependencies for project org.example:mapreduce_teacher1:jar:1.0-SNAPSHOT: Failed to collect dependencies at org.apache.hive:hive-exe…

Rabit

之前发过rabit了&#xff0c;所以这里不再赘述&#xff0c;讲讲原理 在线Rabbit加密 | Rabbit解密- 在线工具 (sojson.com) rabbit加密原理 Rabbit加密算法是一种流密码算法&#xff0c;由Daniel J. Bernstein设计&#xff0c;并被广泛用于多种加密和安全通信应用中。它的设…

【A2DP】深入解读A2DP中通用访问配置文件(GAP)的互操作性要求

目录 一、模式支持要求 1.1 发现模式 1.2 连接模式 1.3 绑定模式 1.4 模式间依赖关系总结 1.5 注意事项 1.6 协议设计深层逻辑 二、安全机制&#xff08;Security Aspects&#xff09; 三、空闲模式操作&#xff08;Idle Mode Procedures&#xff09; 3.1 支持要求 …

模型蒸馏系列——开源项目

推荐项目&#xff1a;MiniMind&#xff08;低成本全流程训练框架&#xff09; GitHub&#xff1a;https://github.com/jingyaogong/minimind 核心特性&#xff1a;完整实现从数据清洗到模型部署的全流程&#xff0c;支持单卡低成本训练&#xff0c;代码全透明&#xff0c;适合…

【软考-架构】13.1、软件架构概述-构件技术

✨资料&文章更新✨ GitHub地址&#xff1a;https://github.com/tyronczt/system_architect 文章目录 ✨【重点】系统架构设计软件架构概述软件架构设计与生命周期构件&#x1f31f;软件架构风格数据流风格调用/返回风格独立构件风格虚拟机风格仓库风格闭环控制风格C2体系结…

《Android启动侦探团:追踪Launcher启动的“最后一公里”》

1. 开机仪式的“黑屏悬案” 当Android设备完成开机动画后&#xff0c;某些产品会陷入诡异的“黑屏时刻”——仿佛系统在玩捉迷藏。此时&#xff0c;**Launcher&#xff08;桌面&#xff09;**就是躲猫猫的主角。我们的任务&#xff1a;揪出Launcher何时完成启动&#xff0c;终…

Redis事务与管道

Redis事务 可以一次执行多个命令&#xff0c;本质是一组命令的集合。一个事务中的所有命令都会序列化&#xff0c;按顺序地串行执行而不会被其他命令插入&#xff0c;不许加塞。 一个队列中&#xff0c;一次性、顺序性、排他性的执行一系列命令。 Redis事务VS数据库事务 常用…

掌握这些 UI 交互设计原则,提升产品易用性

在当今数字化时代&#xff0c;用户对于产品的体验要求越来越高&#xff0c;UI 交互设计成为决定产品成败的关键因素之一。一个易用的产品能够让用户轻松、高效地完成各种操作&#xff0c;而实现这一目标的核心在于遵循一系列科学合理的 UI 交互设计原则。本文将详细阐述简洁性、…

Alembic 实战指南:快速入门到FastAPI 集成

一、快速开始 1.1 简介 Alembic 是一个基于 SQLAlchemy 的数据库迁移工具&#xff0c;主要用于管理数据库模式&#xff08;Schema&#xff09;的变更&#xff0c;例如新增表、修改字段、删除索引等&#xff0c;确保数据库结构与应用程序的 ORM 模型保持一致。 Alembic 通过版…

LRU(最近最少使用)算法实现

核心思想与基本思路 LRU&#xff08;Least Recently Used&#xff09;算法是一种缓存淘汰策略&#xff0c;其核心思想是淘汰最近最少使用的数据。 最近使用原则&#xff1a;最近被访问的数据在未来被访问的概率更高&#xff0c;因此应保留在缓存中。淘汰机制&#xff1a;当缓…

现在有分段、句子数量可能不一致的中英文文本,如何用python实现中英文对照翻译(即每行英文对应相应的中文)

以下是处理分段且中英文句子数量可能不一致的文本的Python实现方案&#xff0c;包含分句、翻译和对齐功能&#xff1a; from googletrans import Translator import redef split_paragraphs(text):"""按空行分割段落并清洗"""return [p.strip()…

C语言每日一练——day_8

引言 针对初学者&#xff0c;每日练习几个题&#xff0c;快速上手C语言。第八天。&#xff08;连续更新中&#xff09; 采用在线OJ的形式 什么是在线OJ&#xff1f; 在线判题系统&#xff08;英语&#xff1a;Online Judge&#xff0c;缩写OJ&#xff09;是一种在编程竞赛中用…

基础知识《Redis解析》

Redis 详细解析与介绍 Redis&#xff08;Remote Dictionary Server&#xff09;是一个开源的高性能键值对&#xff08;Key-Value&#xff09;数据库&#xff0c;支持多种数据结构&#xff08;如字符串、哈希、列表、集合等&#xff09;&#xff0c;广泛应用于缓存、消息队列、…

区跨链知识和概念

1、以太坊 Geth 源码解析 Geth&#xff08;Go Ethereum&#xff09;是以太坊官方提供的 Go 语言实现的客户端&#xff0c;广泛用于以太坊全节点运行、挖矿、DApp 开发等。理解 Geth 的源码有助于掌握以太坊区块链底层逻辑&#xff0c;如区块同步、EVM 执行、P2P 交互等。 2、…

Vue 计算属性与 Data 属性同名问题深度解析

文章目录 1. 问题背景与核心概念1.1 Vue 响应式系统架构1.2 核心概念定义 2. 同名问题的技术分析2.1 同名场景示例2.2 问题发生机制 3. 底层原理剖析3.1 Vue 初始化流程3.2 响应式系统关键代码 4. 问题解决方案4.1 最佳实践建议4.2 错误处理机制 5. 性能影响分析5.1 递归调用性…

Mybatis——基础操作、动态SQL

目录 一.基础操作 1.删除 2.新增 3.更新 4.查询 5.XML映射文件 二、动态SQL 1.<if> 2.<where> 3.<set> 4.<foreach> 5.<sql> 6.<include> 一.基础操作 1.删除 参数占位符&#xff1a; 注意&#xff1a; #{...}相比于${...}…

[设计模式]1_设计模式概览

摘要&#xff1a;设计模式原则、设计模式的划分与简要概括&#xff0c;怎么使用重构获得设计模式并改善代码的坏味道。 本篇作概览与检索用&#xff0c;后续结合源码进行具体模式深入学习。 目录 1、设计模式原理 核心原则&#xff08;语言无关&#xff09; 本质原理图 原…

C语言数据类型取值范围及格式化符号

一、数据类型取值范围与格式化输出符号表格 数据类型大小&#xff08;字节&#xff09;取值范围格式化输出符号char1-128到127<br>或0到255&#xff08;如果声明为unsigned char&#xff09;%c (字符)<br>%hhu (无符号)signed char1-1.2810到1.2710%hhd (有符号)u…