当前位置: 首页 > article >正文

Linux内核驱动开发:从传统proc接口到现代seq_file与proc_ops的迁移指南

1. 项目概述为什么我们需要关注/proc的新接口如果你在Linux内核驱动开发领域摸爬滚打过几年一定对/proc文件系统这个“老伙计”又爱又恨。爱它是因为在调试和状态监控时它提供了一个极其简单、直观的窗口让你能通过cat、echo这样的基础命令就能和内核空间“对话”。恨它是因为传统的/proc接口也就是我们常用的create_proc_entry、proc_mkdir那一套在代码结构、安全性、性能上确实有点跟不上现代内核开发的节奏了。最近几年内核社区一直在推动/proc接口的现代化改造引入了基于seq_file和proc_ops的新范式。这不仅仅是API名字变了背后是整个设计哲学和最佳实践的升级。很多刚接触驱动开发的朋友可能还在沿用老教程里的方法或者面对新旧API并存的内核源码感到困惑。今天我就结合自己从老接口迁移到新接口的实际经历把这套“新接口”掰开揉碎了讲清楚。你会发现它不仅仅是“能用”更是“好用”和“应该用”。无论是为了写出更健壮、更安全的驱动代码还是为了在面试、Code Review时展现出你对内核最新进展的了解掌握这套新接口都至关重要。2. 新旧接口对比与核心设计思想变迁2.1 传统/proc接口的“痛点”回顾在深入新接口之前我们得先明白老接口为什么会被逐渐弃用。这有助于我们理解新接口设计的出发点。传统的做法主要依赖于linux/proc_fs.h中定义的几个函数和结构体核心是struct proc_dir_entry和struct file_operations的一个简化版。一个典型的创建过程是这样的static int my_proc_read(char *page, char **start, off_t off, int count, int *eof, void *data) { return sprintf(page, “Current value: %d\n”, some_internal_value); } static int my_proc_write(struct file *file, const char __user *buffer, unsigned long count, void *data) { char buf[32]; if (copy_from_user(buf, buffer, min(count, sizeof(buf)-1))) return -EFAULT; buf[min(count, sizeof(buf)-1)] ‘\0’; sscanf(buf, “%d”, some_internal_value); return count; } static int __init my_module_init(void) { struct proc_dir_entry *entry; entry create_proc_entry(“my_driver_status”, 0666, NULL); if (entry) { entry-read_proc my_proc_read; entry-write_proc my_proc_write; entry-data some_private_data; } return 0; }这段代码看起来挺简洁对吧但它隐藏了几个大问题缓冲区管理噩梦read_proc回调需要开发者自己管理一个静态页面缓冲区page。你必须非常小心地计算偏移量off和剩余计数count确保不会写出界。对于输出内容长度不确定的情况比如遍历一个链表代码会变得异常复杂和脆弱。接口不一致/proc的写操作回调write_proc参数和VFS层标准的file_operations.write完全不同增加了学习成本和出错几率。扩展性差很难在这种接口上实现复杂的迭代操作比如输出一个大的列表。虽然可以通过设置*start魔法值来 hack但这并非官方推荐且容易出错。逐渐被弃用在内核源码树中create_proc_entry、read_proc_t等类型和函数早已被标记为“过时”deprecated。继续使用会导致编译警告并且在未来的内核版本中可能会被彻底移除。注意从内核版本5.6左右开始这些老接口的声明已被移至linux/proc_fs.h的#ifdef CONFIG_PROC_FS块之外并明确标记为__deprecated。如果你在较新的内核如5.10上编译使用老接口的模块会看到大量的警告。这不仅是美观问题更是一个明确的信号是时候升级你的代码了。2.2 新接口的核心seq_file与proc_ops新接口的设计哲学是“统一”和“简化”。它主要建立在两大支柱上seq_file接口用于处理所有需要“读取”的/proc文件。它抽象了迭代输出大数据集的过程自动处理缓冲区、偏移和分页让开发者只需关注“如何生成下一个数据项”。proc_ops结构体用于定义文件操作。它替代了老接口中直接赋值read_proc、write_proc的方式提供了一个更接近标准VFSfile_operations的结构但专为/proc优化例如通常不需要实现llseek因为seq_file会处理。这种设计的优势是显而易见的安全性缓冲区由seq_file核心管理基本消除了缓冲区溢出的风险。一致性写操作回调与普通文件操作完全一致使用copy_from_user等标准方法。功能强大轻松支持大文件、动态内容、格式化输出seq_printf。面向未来这是内核社区认可和维护的现代方式。3. 新接口实战一步步创建现代/proc文件理论说再多不如一行代码。我们来看一个完整的例子创建一个名为my_driver_stats的/proc文件它可以读写一个驱动内部的统计计数器。3.1 定义驱动内部数据与proc_ops首先定义我们需要的私有数据和一个proc_ops结构。#include linux/seq_file.h #include linux/proc_fs.h static int driver_counter 0; static struct proc_dir_entry *my_proc_entry; // 这是 seq_file 操作的核心开始迭代 static void *my_seq_start(struct seq_file *s, loff_t *pos) { // 我们只有一个“数据项”即计数器值所以 // 如果 pos 为0返回一个非NULL指针通常返回数据本身或SEQ_START_TOKEN // 如果 pos 1表示迭代结束返回NULL return *pos ? NULL : driver_counter; } // 移动到下一个“数据项” static void *my_seq_next(struct seq_file *s, void *v, loff_t *pos) { (*pos); return NULL; // 因为我们只有一项所以下一次调用就结束 } // 结束迭代通常用于清理这里不需要 static void my_seq_stop(struct seq_file *s, void *v) { // 无操作 } // 显示一个数据项 static int my_seq_show(struct seq_file *s, void *v) { int *counter (int *)v; seq_printf(s, “Driver internal counter: %d\n”, *counter); seq_printf(s, “Last accessed at: %llu ns\n”, ktime_get_ns()); // 示例添加时间戳 return 0; // 成功返回0 } // 将上述操作组装成一个 seq_operations 结构体 static struct seq_operations my_seq_ops { .start my_seq_start, .next my_seq_next, .stop my_seq_stop, .show my_seq_show, }; // 这是打开 /proc 文件时的回调它将 seq_operations 与 seq_file 关联 static int my_proc_open(struct inode *inode, struct file *file) { return seq_open(file, my_seq_ops); } // 定义文件的写操作与标准VFS的write完全一样 static ssize_t my_proc_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { char kbuf[32]; int val; if (count sizeof(kbuf)) return -EINVAL; if (copy_from_user(kbuf, buf, count)) return -EFAULT; kbuf[count] ‘\0’; if (kstrtoint(kbuf, 10, val)) // 安全地将字符串转换为整数 return -EINVAL; driver_counter val; return count; // 返回成功写入的字节数 } // 关键的 proc_ops 结构体 static const struct proc_ops my_proc_fops { .proc_open my_proc_open, .proc_read seq_read, // 使用 seq_file 提供的标准读方法 .proc_write my_proc_write, .proc_lseek seq_lseek, // 使用 seq_file 提供的标准定位方法 .proc_release seq_release, // 使用 seq_file 提供的标准释放方法 };代码解读与心得seq_operations定义了如何遍历你的数据。对于简单的单值输出start和next的逻辑看起来有点“大材小用”但这就是seq_file的通用范式。一旦你需要输出一个链表或数组这个框架的优势就体现出来了——你只需要在next里移动到下一个节点即可。my_proc_open是连接proc_ops和seq_operations的桥梁。seq_open这个调用是关键。proc_ops中的成员都以proc_为前缀与通用的file_operations区分开。注意我们直接使用了seq_read、seq_lseek、seq_release这些seq_file框架提供的通用函数这避免了重复造轮子。在写操作中我使用了kstrtoint而不是老旧的sscanf。这是内核推荐的更安全的字符串转换函数能更好地处理错误和边界情况。3.2 创建与注册/proc文件有了proc_ops创建文件就变得非常直观和现代。static int __init my_driver_init(void) { // 使用 proc_create 创建文件并直接关联 proc_ops my_proc_entry proc_create(“my_driver_stats”, 0666, NULL, my_proc_fops); if (!my_proc_entry) { pr_err(“Failed to create /proc/my_driver_stats\n”); return -ENOMEM; } pr_info(“/proc/my_driver_stats created successfully.\n”); return 0; } static void __exit my_driver_exit(void) { if (my_proc_entry) proc_remove(my_proc_entry); // 统一使用 proc_remove 进行清理 pr_info(“Driver module exited.\n”); } module_init(my_driver_init); module_exit(my_driver_exit);关键变化与技巧proc_create这是新接口的核心创建函数。参数依次是文件名、权限位、父目录NULL表示在/proc根目录、proc_ops结构体指针。它一次性完成了老接口中create_proc_entry和后续回调函数赋值的所有工作代码更紧凑。权限位0666表示所有用户可读可写。在生产环境中你需要仔细考虑权限比如0644全局可读仅root可写可能更安全。proc_remove在模块退出时使用这个函数来删除/proc条目。它比老接口的remove_proc_entry更常用是通用的清理函数。3.3 编译、测试与效果验证编译并插入模块后你可以立即进行测试# 1. 查看文件是否存在 $ ls -l /proc/my_driver_stats -rw-rw-rw- 1 root root 0 Apr 26 10:00 /proc/my_driver_stats # 2. 读取文件内容初始值为0 $ cat /proc/my_driver_stats Driver internal counter: 0 Last accessed at: 1714118400123456789 ns # 3. 写入一个新值 $ echo “42” /proc/my_driver_stats # 4. 再次读取确认值已改变 $ cat /proc/my_driver_stats Driver internal counter: 42 Last accessed at: 1714118410987654321 ns看到效果了吗输出是格式化的、清晰的并且我们轻松地添加了时间戳这种额外信息。写入操作也符合Linux命令的直觉。整个过程没有手动计算任何缓冲区偏移代码逻辑清晰安全性也大大提升。4. 进阶技巧与复杂场景处理掌握了基础用法后我们来看看如何用新接口处理更复杂的场景这些才是体现其威力的地方。4.1 输出复杂数据结构链表遍历假设你的驱动维护了一个客户端连接链表struct client_conn现在需要把它输出到/proc。static int my_seq_show(struct seq_file *s, void *v) { struct client_conn *conn (struct client_conn *)v; seq_printf(s, “Client ID: %d, Address: %pI4, Active: %s\n”, conn-id, conn-ip_addr, conn-is_active ? “yes” : “no”); return 0; } static void *my_seq_start(struct seq_file *s, loff_t *pos) { struct client_conn *conn; loff_t i 0; // 加锁保护链表非常重要 mutex_lock(client_list_lock); // 遍历链表直到找到 *pos 指向的位置 list_for_each_entry(conn, client_list, list) { if (i *pos) return conn; // 返回找到的节点 } return NULL; // 链表遍历完毕 } static void *my_seq_next(struct seq_file *s, void *v, loff_t *pos) { struct client_conn *conn (struct client_conn *)v; (*pos); // 获取链表中的下一个节点 conn list_next_entry(conn, list); // 如果下一个节点就是链表头说明结束了 if (conn-list client_list) return NULL; return conn; } static void my_seq_stop(struct seq_file *s, void *v) { // 在迭代结束时释放锁 mutex_unlock(client_list_lock); }实操心得锁是关键start函数里加锁stop函数里释放锁。这确保了在遍历链表的过程中链表结构不会被其他执行线程如中断处理程序、其他系统调用修改从而避免内核崩溃或数据混乱。这是生产级驱动必须考虑的。seq_file会自动处理分页。当用户用cat命令时内核会多次调用start、show、next、stop这个序列每次输出一“页”数据。开发者完全不用操心off和count。list_next_entry是Linux内核链表辅助函数用起来比直接操作list_head更安全方便。4.2 创建目录与多文件组织一个功能完善的驱动往往需要在/proc下创建一个专属目录里面放置多个状态文件。static struct proc_dir_entry *my_proc_dir NULL; static struct proc_dir_entry *entry_stats, *entry_config, *entry_debug; static int __init my_driver_init(void) { // 1. 首先创建目录 my_proc_dir proc_mkdir(“my_awesome_driver”, NULL); if (!my_proc_dir) return -ENOMEM; // 2. 在目录下创建多个文件 entry_stats proc_create(“stats”, 0444, my_proc_dir, stats_proc_fops); entry_config proc_create(“config”, 0644, my_proc_dir, config_proc_fops); entry_debug proc_create(“debug”, 0200, my_proc_dir, debug_proc_fops); // 只写文件 if (!entry_stats || !entry_config || !entry_debug) { // 创建失败需要清理已创建的资源 if (entry_stats) proc_remove(entry_stats); if (entry_config) proc_remove(entry_config); if (entry_debug) proc_remove(entry_debug); proc_remove(my_proc_dir); return -ENOMEM; } return 0; } static void __exit my_driver_exit(void) { // 清理时直接移除目录即可目录下的文件会被自动递归移除 if (my_proc_dir) proc_remove(my_proc_dir); // 不需要再单独移除 entry_stats 等 }重要提示proc_mkdir用于创建目录它返回一个struct proc_dir_entry *可以作为proc_create的父目录参数。错误处理创建多个文件时必须对每个proc_create的返回值做检查。一旦某个失败需要清理之前已经成功创建的所有条目和目录否则会造成/proc文件系统“泄漏”。简化清理在模块退出时只需要移除目录proc_remove(my_proc_dir)内核会自动递归删除该目录下的所有文件。这是一种更简洁且不易出错的清理方式。4.3 使用single_open简化单次输出对于前面那个简单的计数器例子我们用了完整的seq_operations四件套。如果确定你的proc文件只输出一段固定的、不需要迭代的内容比如一次性打印所有统计信息有一个更简单的方案single_open。static int my_single_show(struct seq_file *s, void *unused) { seq_printf(s, “Total interrupts: %lu\n”, interrupt_count); seq_printf(s, “IO errors: %lu\n”, io_error_count); seq_printf(s, “Current mode: %s\n”, operational_mode ? “ACTIVE” : “STANDBY”); // ... 一次性输出所有信息 return 0; } static int my_single_open(struct inode *inode, struct file *file) { return single_open(file, my_single_show, NULL); // 第三个参数是私有数据这里不需要 } static const struct proc_ops my_single_fops { .proc_open my_single_open, .proc_read seq_read, .proc_lseek seq_lseek, .proc_release single_release, // 注意这里用 single_release };使用场景判断用single_open当你的show函数逻辑是固定的一次性生成所有输出数据量不大且不需要根据文件读取位置*pos来动态输出不同内容时。代码量最少。用完整的seq_operations当需要遍历一个很大的数据结构链表、数组、哈希表或者输出内容可能很大需要内核自动分页时。这是更通用、更强大的模式。5. 常见陷阱、调试技巧与性能考量即使理解了原理在实际编码和调试中还是会遇到一些坑。这里分享几个我踩过的雷和总结的经验。5.1 并发与锁的陷阱这是/proc接口开发中最容易出错的地方。/proc文件可以被多个进程同时读取甚至同时读写。问题场景在my_seq_show函数里你正在遍历一个链表并打印每个节点的值。与此同时一个写操作my_proc_write删除了链表中的一个节点。如果没有锁保护seq_show可能访问到一个已经被释放的节点内存导致内核Oops崩溃。解决方案使用内核锁如 mutex 或 spinlock。在seq_start中加锁在seq_stop中解锁确保整个迭代序列的原子性。这是最常用的方法。RCU读-复制-更新如果读操作极其频繁而写操作很少可以考虑使用RCU机制来保护链表。这能极大提升读性能但实现复杂度较高。在seq_start中使用rcu_read_lock在seq_stop中使用rcu_read_unlock写操作则使用list_replace_rcu等。调试技巧可以使用pr_debug在start、stop、show函数中加入日志观察并发访问时函数的调用顺序。也可以使用内核的lockdep锁依赖检测工具来发现潜在的锁顺序死锁问题。5.2 内存分配与格式化输出避免在show函数中分配大内存seq_printf会向seq_file的内部缓冲区写入。这个缓冲区大小是有限的通常一页4KB。如果你试图一次性格式化一个超长的字符串可能会失败。对于很长的行考虑分多次调用seq_printf或者使用seq_puts输出纯字符串用seq_putc输出单个字符。处理seq_printf失败seq_printf实际上会返回一个错误码负数但在很多示例代码中被忽略了。在严谨的驱动中应该检查其返回值。if (seq_printf(s, “Some format: %d\n”, var) 0) return -ENOMEM; // 或其它错误码5.3 文件权限与安全最小权限原则不要随意给0666所有用户可读写。思考这个文件的作用。纯状态信息0444只读。可配置参数0644root可写其他用户只读。调试接口可能触发敏感操作0200仅root可写甚至可以考虑通过内核启动参数来控制是否创建此类调试文件。用户输入验证在write回调中必须严格验证从用户空间copy_from_user过来的数据。检查长度、范围、格式。使用kstrtoint、kstrtoul等安全转换函数而不是不安全的sscanf或简单的simple_strtol。5.4 性能考量/proc不是高性能接口每次cat一个文件都会执行一整套内核函数调用open, read, …。它设计用于偶尔的状态查询和调试绝不能用于高频、实时的数据通道。如果你需要从内核向用户空间高速传输数据应该考虑netlink、relayfs或debugfs特别是debugfs_create_blob用于大块数据。输出内容的计算代价如果seq_show中需要计算复杂的统计信息例如遍历所有数据结构进行求和要考虑这个计算成本。如果该proc文件可能被频繁读取比如被监控脚本每秒调用这种计算可能成为性能瓶颈。可以考虑在驱动内部周期性地更新一个缓存值proc接口只输出这个缓存值。从传统的create_proc_entry到现代的proc_create配合seq_file这不仅仅是API的更新更是内核开发向着更安全、更一致、更强大方向发展的一个缩影。迁移到新接口需要一点学习成本但带来的代码健壮性和可维护性的提升是巨大的。下次当你为驱动添加调试接口时别再犹豫直接使用proc_create和seq_file吧。它会让你的代码看起来更专业也更经得起时间的考验。在实际项目中我通常会为复杂的驱动建立一个proc目录里面用不同的文件来区分统计信息、运行时配置和调试开关这套新接口让这种组织变得非常清晰和易于管理。

相关文章:

Linux内核驱动开发:从传统proc接口到现代seq_file与proc_ops的迁移指南

1. 项目概述:为什么我们需要关注/proc的新接口?如果你在Linux内核驱动开发领域摸爬滚打过几年,一定对/proc文件系统这个“老伙计”又爱又恨。爱它,是因为在调试和状态监控时,它提供了一个极其简单、直观的窗口&#xf…...

使用Taotoken后API调用延迟与稳定性体感观察报告

🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 使用Taotoken后API调用延迟与稳定性体感观察报告 1. 引言:从直接对接模型到使用聚合平台 在开发基于大语言模型的应用…...

3步掌握SMUDebugTool:AMD Ryzen处理器调试完全指南

3步掌握SMUDebugTool:AMD Ryzen处理器调试完全指南 【免费下载链接】SMUDebugTool A dedicated tool to help write/read various parameters of Ryzen-based systems, such as manual overclock, SMU, PCI, CPUID, MSR and Power Table. 项目地址: https://gitco…...

从PyAutoGUI到OpenClaw:构建健壮桌面自动化的状态驱动技能库

1. 项目概述:当自动化脚本拥有“鹰爪”最近在GitHub上看到一个挺有意思的项目,叫Ikaros-521/openclaw-pyautogui-skill。光看名字,就透着一股“硬核”和“实用”的气息。Ikaros(伊卡洛斯)是希腊神话里那位用蜡和羽毛造…...

linux内核源码内存管理(7)

一、 引言:冲破冯诺依曼瓶颈的壁障在传统的单处理器(UMA,Uniform Memory Access)架构中,所有CPU核心通过同一条总线平等地访问所有内存。这种对称性带来了编程模型的简洁,但也埋下了致命的可扩展性陷阱&…...

AMD Ryzen处理器底层调试技术解析:SMUDebugTool的架构设计与实践应用

AMD Ryzen处理器底层调试技术解析:SMUDebugTool的架构设计与实践应用 【免费下载链接】SMUDebugTool A dedicated tool to help write/read various parameters of Ryzen-based systems, such as manual overclock, SMU, PCI, CPUID, MSR and Power Table. 项目地…...

Python 开发者三步接入 Taotoken 调用 GPT 与 Claude 模型

🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 Python 开发者三步接入 Taotoken 调用 GPT 与 Claude 模型 对于习惯使用 OpenAI 官方 Python SDK 的开发者来说,接入 T…...

【亲测有效】DeepSeek极简入门与应用_156.[第6章 高级应用技巧] 场景描述型框架:用情境设定让AI进入最佳状态

别再让AI"猜谜"了!一个场景设定,让DeepSeek从"人工智障"秒变"懂王"——这可能是你用过最被低估的Prompt技巧 #mermaid-svg-7MQcGN4wm4OXCgus{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:1…...

apk 包管理器完全指南:Alpine Linux 的轻量级利器

一、apk 体系架构全景 apk(Alpine Package Keeper)是 Alpine Linux 的核心包管理工具,与 Debian 的 APT 相比,它遵循极简主义设计哲学:代码量少、依赖解析简单、资源占用极低。这使得 Alpine 成为 Docker 容器的默认基…...

黑苹果配置神器Hackintool:从新手到高手的完整指南

黑苹果配置神器Hackintool:从新手到高手的完整指南 【免费下载链接】Hackintool The Swiss army knife of vanilla Hackintoshing 项目地址: https://gitcode.com/gh_mirrors/ha/Hackintool Hackintool被誉为"黑苹果瑞士军刀",是配置和…...

ThinkPad风扇控制终极指南:TPFanCtrl2让笔记本更安静高效

ThinkPad风扇控制终极指南:TPFanCtrl2让笔记本更安静高效 【免费下载链接】TPFanCtrl2 ThinkPad Fan Control 2 (Dual Fan) for Windows 10 and 11 项目地址: https://gitcode.com/gh_mirrors/tp/TPFanCtrl2 你是否经常被ThinkPad风扇的噪音打扰?…...

网安工具系列python系列【仅供参考】:Python实战:利用fofa API高效搜索网络资产

Python实战:利用fofa API高效搜索网络资产 Python实战:利用fofa API高效搜索网络资产 1. 从零开始:为什么你需要一个自动化的资产搜索工具? 2. 动手前的准备:你的fofa账户和Python环境 2.1 获取你的fofa API凭证 2.2 搭建Python脚本环境 3. 核心代码拆解:一行行理解搜索脚…...

移动篇:WMS里的“乾坤大挪移”——移库、补货、冻结全解析

WMS里的“乾坤大挪移”——移库、补货、冻结全解析 摘要:货物入库后,不是“一放了之”。库位要优化、库存要周转、临期品要管理……这就涉及WMS中的“库存移动”操作。移库、补货、冻结分别解决什么问题?什么场景下会用到?本文带你…...

出库篇:仓库里的货往哪去?——WMS出库方式全解析,物流新人必读

仓库里的货往哪去?——WMS出库方式全解析,物流新人必读 摘要:货品有进必有出。上一期我们聊了WMS中货品的四大来源(采购、生产、退货、调拨入库),这一期我们来看看货品是怎么“出”去的——销售出库、采购退…...

入库篇:仓库里的货从哪来?——WMS货品来源全解析,物流新人必读

仓库里的货从哪来?——WMS货品来源全解析,物流新人必读 摘要:每天在WMS系统里看到成百上千的库存记录,但你想过没有——这些货品最初是怎么进入系统的?是采购进来的?生产出来的?客户退回来的&am…...

机器人遥测系统设计:从数据采集到可视化监控的工程实践

1. 项目概述:从开源代码仓库到可观测性实践最近在梳理一些开源机器人项目时,遇到了一个名为jizb880/openclaw_telemetry的仓库。乍一看,这个标题由两部分组成:一个可能是作者的用户名jizb880,以及一个极具指向性的项目…...

开源Claude本地部署指南:从模型选型到性能调优实战

1. 项目概述:当开源精神遇上AI推理最近在折腾本地部署大语言模型的朋友,估计都绕不开一个名字:Claude。作为Anthropic家的明星产品,Claude系列模型以其出色的推理能力、对指令的精准理解和强大的安全性,在开发者圈子里…...

5G NR(新空口)物理层设计解析

5G NR(新空口)物理层设计解析 在无线通信技术的演进过程中,5G NR(新空口)作为第五代移动通信技术的核心组成部分,其物理层设计承载着提升数据传输速率、降低时延、增强连接密度等多重目标。本文将围绕5G NR…...

基于Emissaries框架构建多AI智能体协作系统:从原理到实践

1. 项目概述:一个基于AI的智能体协作框架最近在开源社区里,一个名为muinyc/emissaries的项目引起了我的注意。乍一看这个名字,你可能会联想到“使者”或“特使”,这其实非常贴切地揭示了它的核心定位。简单来说,Emissa…...

车载以太网之要火系列 - 第49篇郭大侠学SOME/IP:人说SOME/IP虽好,对手已在路上跑

写在开篇蓉儿又挖坑上回说到,郭靖学完了SOME/IP的十八般武艺——报文头、Service ID、Instance ID、Method、Event、Field、SD的Offer/Find/Subscribe三驾马车。郭靖合上笔记本,信心满满:“蓉儿,SOME/IP我算是学透了!服…...

机器学习中的视觉与自然语言处理

一两个月前看了李飞飞老师的自传,看第一页就觉得 这是对A国的表白。当然也会遗憾,希望她小时候遇到的老师是更好的老师,她家周围遇到的人是更好的人。这是概率问题,在过去可能不够好今天会更好。 重点是当我看到她在思考智能的起源…...

AMD Ryzen处理器终极调试指南:SMU Debug Tool实战技巧与完整解决方案

AMD Ryzen处理器终极调试指南:SMU Debug Tool实战技巧与完整解决方案 【免费下载链接】SMUDebugTool A dedicated tool to help write/read various parameters of Ryzen-based systems, such as manual overclock, SMU, PCI, CPUID, MSR and Power Table. 项目地…...

低空经济崛起,实干企业的“品牌失语”危机比“黑飞”更可怕!

最近,低空经济成为热词。从浙江移动发布的低空智联网“4S”安全服务矩阵,到无人机在医疗、巡检、物流等领域的广泛应用,我们看到了一个万亿级市场的技术底座正在快速搭建。然而,在另一片我们称之为“AI空域”的新战场,…...

低压电工-电子技术常识

一、导体、绝缘体、半导体(按电阻率划分)1. 划分标准单位是 Ω・cm(欧姆・厘米),不是单纯欧姆 (Ω),是电阻率专用单位:欧姆・厘米 Ω⋅cm,也可以用 Ω⋅m(欧姆・米&#…...

ROS新手也能玩转AUBO i5:用MoveIt和Rviz在Ubuntu 20.04上实现机械臂可视化仿真与控制

ROS新手也能玩转AUBO i5:用MoveIt和Rviz在Ubuntu 20.04上实现机械臂可视化仿真与控制 机械臂控制一直是机器人开发中的核心课题,而ROS(Robot Operating System)为这一领域提供了强大的工具链。本文将带你从零开始,在Ub…...

命令行集成AI代码审查:基于Gemini的Git工作流自动化实践

1. 项目概述:当命令行遇上代码审查在开发者的日常工作中,代码审查是保证代码质量、促进知识共享的关键环节。然而,传统的代码审查流程往往伴随着频繁的上下文切换:你需要离开终端,打开浏览器,登录代码托管平…...

百考通AI实践报告:让实习沉淀有迹可循,成长答卷专业呈现

实习实践是连接理论学习与职场实战的桥梁,而一份逻辑清晰、内容详实的实践报告,既是对实习经历的系统复盘,也是个人成长与能力认证的重要载体。然而,许多学生在撰写报告时,常陷入思路混乱、结构松散、重点模糊的困境&a…...

AI智能体长期记忆系统:从RAG到Memory-Skill的工程实践

1. 项目概述:一个关于“记忆”的AI技能最近在折腾AI智能体(Agent)和RAG(检索增强生成)相关的东西,发现一个挺有意思的GitHub项目,叫memory-skill。光看名字,你可能会觉得这是个简单的…...

基于SpringBoot的广西特色水果电商平台的设计与实现

本课题的选题依据及研究意义 一、选题依据和意义 (一)选题依据 随着互联网经济的深入发展,电子商务在推动全球经济发展中发挥了重要作用。其中生鲜电商已成为农产品销售的重要渠道。广西作为我国热带水果的重要产区,对其传统水果产…...

基于SpringBoot的汽车美容养护管理系统的设计与开发

一、选题依据和意义 (一)选题依据 随着国内汽车保有量持续攀升,汽车后市场规模不断扩大,汽车美容养护行业迎来快速发展期,但行业整体仍存在管理效率低下、服务流程不规范等问题[1]。传统管理模式依赖人工记录客户信息…...