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

Linux内核 -- 字符设备之read write poll基本实现

Linux字符设备:read、write和poll函数实现及完整代码

1. read函数

原型

ssize_t read(struct file *file, char __user *buf, size_t count, loff_t *pos);

实现步骤

  1. 检查用户缓冲区:使用copy_to_user将数据从内核空间复制到用户空间。
  2. 返回已读取的字节数:
    • 如果数据长度少于请求长度,返回实际读取的字节数。
    • 如果到达文件末尾,返回0

注意事项

  • 确保对用户缓冲区的访问是安全的。
  • 返回值必须是已成功读取的字节数或错误码(如-EFAULT)。
  • 考虑多线程情况下的并发访问,适当加锁。

示例代码

static ssize_t my_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{char data[] = "Hello, World!\n";size_t datalen = strlen(data);if (*pos >= datalen)return 0;if (count > datalen - *pos)count = datalen - *pos;if (copy_to_user(buf, data + *pos, count))return -EFAULT;*pos += count;return count;
}

2. write函数

原型

ssize_t write(struct file *file, const char __user *buf, size_t count, loff_t *pos);

实现步骤

  1. 验证用户数据:使用copy_from_user将数据从用户空间复制到内核空间。
  2. 处理写入数据:通常会将数据存储到设备的内存区域或触发硬件操作。

注意事项

  • 确保处理的是合法数据。
  • 如果设备有缓冲区,需检查空间是否足够。
  • 同样需要考虑多线程并发访问的问题。

示例代码

static ssize_t my_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{char kernel_buf[128];if (count > sizeof(kernel_buf) - 1)count = sizeof(kernel_buf) - 1;if (copy_from_user(kernel_buf, buf, count))return -EFAULT;kernel_buf[count] = '\0';printk(KERN_INFO "Received from user: %s\n", kernel_buf);*pos += count;return count;
}

3. poll函数

原型

unsigned int poll(struct file *file, struct poll_table_struct *wait);

实现步骤

  1. 注册等待队列:使用poll_wait将设备的等待队列添加到wait
  2. 返回设备状态:返回一个位掩码,指示设备的可读性、可写性等状态。

注意事项

  • 确保等待队列正确唤醒。
  • 返回值可以是POLLIN(可读)、POLLOUT(可写)等。
  • 处理阻塞和非阻塞模式。

示例代码

static unsigned int my_poll(struct file *file, struct poll_table_struct *wait)
{unsigned int mask = 0;poll_wait(file, &my_wait_queue, wait);if (data_available)mask |= POLLIN | POLLRDNORM; // 可读if (space_available)mask |= POLLOUT | POLLWRNORM; // 可写return mask;
}

4. 完整代码示例

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/poll.h>
#include <linux/wait.h>
#include <linux/sched.h>#define DEVICE_NAME "my_device"
#define BUFFER_SIZE 128static int major;
static char device_buffer[BUFFER_SIZE];
static size_t buffer_len = 0;static wait_queue_head_t my_wait_queue; // 声明等待队列
static int data_available = 0;         // 标志:是否有数据可供读取// read 实现
static ssize_t my_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{if (buffer_len == 0) {if (file->f_flags & O_NONBLOCK)return -EAGAIN;// 等待数据可用wait_event_interruptible(my_wait_queue, data_available);if (buffer_len == 0) // 防止被中断唤醒但没有数据return -EINTR;}if (count > buffer_len)count = buffer_len;if (copy_to_user(buf, device_buffer, count))return -EFAULT;buffer_len = 0; // 数据被读取后清空缓冲区data_available = 0;return count;
}// write 实现
static ssize_t my_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{if (count > BUFFER_SIZE - 1)count = BUFFER_SIZE - 1;if (copy_from_user(device_buffer, buf, count))return -EFAULT;device_buffer[count] = '\0';buffer_len = count;// 数据写入后唤醒等待的进程data_available = 1;wake_up_interruptible(&my_wait_queue);return count;
}// poll 实现
static unsigned int my_poll(struct file *file, struct poll_table_struct *wait)
{unsigned int mask = 0;poll_wait(file, &my_wait_queue, wait);if (buffer_len > 0)mask |= POLLIN | POLLRDNORM; // 数据可读if (buffer_len < BUFFER_SIZE)mask |= POLLOUT | POLLWRNORM; // 可写入更多数据return mask;
}// 文件操作结构
static struct file_operations my_fops = {.owner = THIS_MODULE,.read = my_read,.write = my_write,.poll = my_poll,
};// 模块初始化
static int __init my_init(void)
{major = register_chrdev(0, DEVICE_NAME, &my_fops);if (major < 0) {printk(KERN_ALERT "Failed to register character device\n");return major;}init_waitqueue_head(&my_wait_queue); // 初始化等待队列printk(KERN_INFO "Device registered with major number %d\n", major);return 0;
}// 模块退出
static void __exit my_exit(void)
{unregister_chrdev(major, DEVICE_NAME);printk(KERN_INFO "Device unregistered\n");
}module_init(my_init);
module_exit(my_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple character device with wait queue.");

相关文章:

Linux内核 -- 字符设备之read write poll基本实现

Linux字符设备&#xff1a;read、write和poll函数实现及完整代码 1. read函数 原型 ssize_t read(struct file *file, char __user *buf, size_t count, loff_t *pos);实现步骤 检查用户缓冲区&#xff1a;使用copy_to_user将数据从内核空间复制到用户空间。返回已读取的字…...

linux部署ansible自动化运维

ansible自动化运维 1&#xff0c;编写ansible的仓库&#xff08;比赛已经安装&#xff0c;无需关注&#xff09; 1、虚拟机右击---设置---添加---CD/DVD驱动器---完成---确定 2、将ansible.iso的光盘连接上&#xff08;右下角呈绿色状态&#xff09; 3、查看光盘挂载信息 df -h…...

springboot421社区医疗服务可视化系统(论文+源码)_kaic

摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装社区医疗服务可视化系统软件来发挥其高效地信息处理的作用…...

vue渲染时闪光白屏问题怎么解决(笔记)

Vue渲染时出现闪光白屏问题&#xff0c;可能是因为页面内容需要较长时间才能加载完成&#xff0c;而在加载期间&#xff0c;页面会显示白屏或者空白内容&#xff0c;给用户体验带来负面影响。 解决方法&#xff1a; 为标签绑定 v-cloak 在样式中写 v-clock {displa&#xff…...

duxapp 2024-12-09 更新 PullView可以弹出到中间,优化CLI使用体验

UI库 修复 Button 禁用状态失效的问题Modal 组件即将停用&#xff0c;请使用 PullView 基础库 PullView side 新增 center 指定弹出到屏幕中间PullView 新增 duration 属性&#xff0c;指定动画时长新增 useBackHandler hook 用来阻止安卓端点击返回键 RN端 修复 windows …...

vue调试插件vue Devtools下载安装

Vue.js Devtools_6.6.3_Chrome插件下载_极简插件 进图官网点击推荐下载下载该调试工具 解压缩找到扩展程序开启开发者模式 将解压缩的文件拖拽到该页面空白处&#xff0c;点击添加扩展程序 点击详情允许访问文件网址 页面右键检查可以发现已经成功添加该页面调试插件...

.net core使用AutoMapper

AutoMapper 是一个用于 .NET 平台的对象映射工具&#xff0c;它简化了不同对象类型之间的转换过程。在软件开发中&#xff0c;尤其是在分层架构的应用程序里&#xff0c;常常需要在不同的对象模型之间进行数据传递&#xff0c;例如从数据库实体到视图模型、DTO&#xff08;数据…...

HTTP头注入

注入类漏洞经久不衰&#xff0c;多年保持在owasp Top 10的首位。今天就聊聊那些被人遗忘的http头注入。用简单的实际代码进行演示&#xff0c;让每个人更深刻的去认识该漏洞。 3|0HOST注入 在以往http1.0中并没有host字段&#xff0c;但是在http1.1中增加了host字段&#xff…...

110.【C语言】编写命令行程序(1)

目录 1.前置知识 "命令"的含义 运行C语言程序 2.介绍 main函数的参数 实验1 执行结果 实验2 执行结果 修改代码 实验3 分析 方法:遍历数组argv[]中的所有参数 执行结果 修改代码 执行结果 1.前置知识 "命令"的含义 WINR输入cmd,在cmd窗口下…...

k8s 之 StatefulSet

深入理解StatefulSet&#xff08;一&#xff09;&#xff1a;拓扑状态 k8s有状态与无状态的区别 无状态服务&#xff1a;deployment Deployment被设计用来管理无状态服务的pod,每个pod完全一致.什么意思呢? 无状态服务内的多个Pod创建的顺序是没有顺序的. 无状态服务内的多…...

PTA 7-2 从身份证号码中提取出出生年月

分数 12 作者 崔孝凤 单位 山东理工大学 输入一个18位的身份证号码&#xff0c;提取出年份和月份并输出&#xff0c;请判断输入的号码是否是18位&#xff0c;如果不是请提示"Invalid data,input again!"&#xff0c;并重新输入新的号码。 输入格式: 输入一个18位…...

Plugin - 插件开发04_Spring Boot中的SPI机制与Spring Factories实现

文章目录 Pre方案概览使用插件的好处Spring Boot中的SPI机制与Spring Factories实现1. Spring Boot中的SPI机制Spring Factories文件 2. Spring Factories实现原理3. Code3.1 定义一个服务接口3.2 定义 实现类3.3 配置 spring.factories 文件3.4 创建一个Controller来加载插件3…...

GUNS搭建

一、准备工作 源码下载&#xff1a; 链接: https://pan.baidu.com/s/1bJZzAzGJRt-NxtIQ82KlBw 提取码: criq 官方文档 二、导入代码 1、导入后端IDE 导入完成需要&#xff0c;需要修改yml文件中的数据库配置&#xff0c;改成自己的。 2、导入前端IDE 我是用npm安装的yarn npm…...

路径规划之启发式算法之十四:蜘蛛蜂优化算法(Spider Wasp Optimizer, SWO)

蜘蛛蜂优化算法(Spider Wasp Optimizer, SWO)是一种受自然界中蜘蛛蜂行为启发的元启发式智能优化算法。由Mohamed Abdel-Basset等人于2023年提出,算法模拟了雌性蜘蛛蜂的狩猎、筑巢和交配行为,具有独特的更新策略,适用于具有不同探索和开发需求的广泛优化问题。 一、算法背…...

OpenGL ES详解——多个纹理实现混叠显示

目录 一、获取图片纹理数据 二、着色器编写 1. 顶点着色器 2. 片元着色器 三、绑定和绘制纹理 1. 绑定纹理 2. 绘制纹理 四、源码下载 一、获取图片纹理数据 获取图片纹理数据代码如下&#xff1a; //获取图片1纹理数据 mTextureId loadTexture(mContext, R.mipmap.…...

Java多线程与线程池技术详解(八)

《星游记》 “如果只有傻瓜才相信梦想&#xff0c;那么就叫我大傻瓜吧&#xff01;” 《一人之下》 “想走的路不好走&#xff0c;想做人不好做&#xff0c;都说是身不由己&#xff0c;不是废话么。己不由心&#xff0c;身又岂能由己&#xff01;” 目录 上一篇博客习题讲解 编…...

2024年12月11日Github流行趋势

项目名称&#xff1a;maigret 项目维护者&#xff1a;soxoj, kustermariocoding, dependabot, fen0s, cyb3rk0tik项目介绍&#xff1a;通过用户名从数千个站点收集个人档案信息的工具。项目star数&#xff1a;12,055项目fork数&#xff1a;870 项目名称&#xff1a;uv 项目维护…...

ThinkPHP 6.0 PHP新手教程

1、系统配置文件 下面系统自带的配置文件列表及其作用&#xff1a; 配置文件名描述app.php应用配置cache.php缓存配置console.php控制台配置cookie.phpCookie配置database.php数据库配置filesystem.php磁盘配置lang.php多语言配置log.php日志配置middleware.php中间件配置rou…...

【Excel学习记录】02-单元格格式设置

1.单元格格式工具美化表格 单元格格式位置 选中单元格&#xff0c;右键→设置单元格格式 合并居中 跨越合并 字体类型、大小、颜色、填充底纹、边框 斜线 软回车&#xff1a;alt enter 格式刷 2.单元格数字格式 格式不影响数值&#xff0c;只是展示形式 日期本质也是数…...

Paimon Tag和Branch创建文件存储过程

结论&#xff1a; 如果data-file被引用则不会被压缩&#xff0c;压缩仅针对未被引用的文件&#xff0c;创建tag时候根据当前快照进行创建 1、实际表和Manifest的内容 查看tag的内容 select * from table$tags;或者直接查看tag ossutil cat oss://test-dataware/warehouse/te…...

HCIA笔记8--DHCP、Telnet协议

1. DHCP介绍 对于主机的网络进行手动配置&#xff0c;在小规模的网络中还是可以运作的&#xff0c;但大规模网络是无力应对的。因此就有了DHCP协议来自动管理主机网络的配置。 DHCP(Dynamic Host Configuration Protocol): 动态主机配置协议&#xff0c;主要需要配置的参数有…...

Tableau数据可视化与仪表盘搭建

1.Tableau介绍 可视化功能 数据赋能 数据赋能就是将我们的数据看板发布到我们的线上去 这里的IP地址是业务部门可以通过账号密码登入的 我们也可以根据需要下载&#xff0c;选中并点击下载即可 下载下来之后&#xff0c;自己就能根据数据进行自定义的分析 也可以下载图片 还有…...

Django结合websocket实现分组的多人聊天

其他地方和上一篇大致相同&#xff0c;上一篇地址点击进入, 改动点1&#xff1a;在setting.py中最后再添加如下配置&#xff1a; # 多人聊天 CHANNEL_LAYERS {"default":{"BACKEND": "channels.layers.InMemoryChannelLayer"} }因此完整的se…...

自动驾驶控制与规划——Project 1: 车辆纵向控制

目录 零、任务介绍一、环境配置1.1 CARLA的配置1.2 Docker Ubuntu 20.04 ROS2 Foxy的配置 二、算法2.1 定速巡航2.2 自适应巡航2.3 离散PID控制 三、代码实现3.1 代码补全3.2仿真验证 零、任务介绍 课程主页 配置Carla仿真器配置carla-ros-bridge补全src\ros-bridge\carla_s…...

基于python实现自动化的验证码识别:探索与实践

基于python实现自动化的验证码识别&#xff1a;探索与实践 一、验证码的类型及特点&#xff08;一&#xff09;图像验证码&#xff08;二&#xff09;短信验证码&#xff08;三&#xff09;语音验证码 二、验证码识别的方法*&#xff08;一&#xff09;传统图像处理方法&#x…...

如何保证消息队列的高可用?(RabbitMQ)

RabbitMQ 是基于主从&#xff08;非分布式&#xff09;做高可用性的&#xff0c;RabbitMQ 有三种模式&#xff1a;单机模式、普通集群模式、镜像集群模式 1、单机模式&#xff1a;一般没人生产用单机模式 2、普通集群模式&#xff1a; 普通集群模式用于提高系统的吞吐量&…...

es的join是什么数据类型

在 Elasticsearch 中,parent 并不是一个独立的数据类型,而是与 join 数据类型一起使用的一个概念。join 数据类型用于在同一个索引中建立父子文档之间的关系,允许你在一个索引内表示层级结构或关联关系。通过 join 字段,你可以定义不同类型的文档(如父文档和子文档),并指…...

爬虫基础之代理的基本原理

在做爬虫的过程中经常会遇到一种情况&#xff0c;就是爬虫最初是正常运行、正常抓取数据的&#xff0c;一切看起来都是那么美好&#xff0c;然而一杯茶的工夫就出现了错误&#xff0c;例如 403 Forbidden&#xff0c;这时打开网页一看&#xff0c;可能会看到“您的IP访问频率太…...

golang实现简单的reids服务2

golang实现redis兼容的redis服务实现redis兼容的redis服务思路 golang实现redis兼容的redis服务 之前做的redis服务是通过tcp封装的自定义协议 原版项目地址:https://github.com/dengjiayue/my-redis.git 那么能不能实现一个redis兼容的redis服务,这样一般的redis包也可以调…...

CSS常用的尺寸单位

像素px 以屏幕上的一个点为单位&#xff0c;比较稳定和精确用的最多 em 以字体大小为参考&#xff0c;&#xff08;是自身字体大小的倍数&#xff09;当自身的字体大小改变时&#xff0c;em也会随着改变em * font-size px rem 以根元素 < html > 作为参考&#xff…...

计算机网络 备查

OSI 七层模型 七层模型协议各层实现的功能 简要 详细 TCP/IP协议 组成 1.传输层协议 TCP 2.网络层协议 IP 协议数据单元&#xff08;PDU&#xff09;和 封装 数据收发过程 数据发送过程 1. 2.终端用户生成数据 3.数据被分段&#xff0c;并加上TCP头 4.网络层添加IP地址信息…...

JavaEE 【知识改变命运】05 多线程(4)

文章目录 单例模式什么是单例模式饿汉模式懒汉模式多线程- 懒汉模式分析多线程问题第一种添加sychronized的方式第二种添加sychronized的方式改进第二种添加sychronized的方式&#xff08;DCL检查锁&#xff09; 阻塞队列什么是阻塞队列什么是消费生产者模型标准库中的阻塞队列…...

迭代器和生成器

一、迭代器&#xff08;Iterator&#xff09; 1. 什么是迭代器&#xff1f; 迭代器是一个可以在某一集合&#xff08;如列表、元组等&#xff09;中逐个访问元素的对象。它提供了一个方法&#xff0c;可以记住遍历的位置&#xff0c;每次取出一个元素&#xff0c;直到所有元素…...

小发现,如何高级的顺序输出,逆序输出整数的每一位(栈,队列)

当我还是初学者的时候&#xff0c;我经常思考有没有比慢慢求每一位数字然后考虑正序&#xff0c;逆序输出要快的办法...长期琢磨&#xff0c;必有所获&#xff01; 我刚学数据结构的时候还没意识到栈&#xff0c;队列还能这样用&#xff0c;虽然说有点杀鸡用牛刀的感觉&#x…...

前端换行、空格的多种表现形式

换行 1、<br> 标签 这是最直接的方式&#xff0c;用于在文本中插入一个简单的换行。<br> 标签是一个空元素&#xff0c;意味着它不需要结束标签。 示例&#xff1a; <p>这是第一行。<br>这是第二行。</p>2、CSS white-space 属性 通过CSS的w…...

自己总结:selenium高阶知识

全篇大概10000字&#xff08;含代码&#xff09;&#xff0c;建议阅读时间30min 一、等待机制 如果有一些内容是通过Ajax加载的内容&#xff0c;那就需要等待内容加载完毕才能进行下一步操作。 为了避免人为操作等待&#xff0c;会遇到的问题&#xff0c; selenium将等待转换…...

无线遥控红外通信

无线遥控红外通信 红外发射装置一般是指红外遥控器由 键盘电路 &#xff0c;红外编码电路 电源电路 和红外发射 电路组成 一般的红外线波长为940nm左右&#xff0c;外形与普通发光二极管相同 红外遥控为了提高抗干扰性能和降低电源消耗&#xff0c;红外遥控器常用载波的方式传送…...

第一个C++程序--(蓝桥杯备考版)

第一个C程序 基础程序 #include <iostream>//头⽂件 using namespace std;//使⽤std的名字空间 int main()//main函数 {cout << "hello world!" << endl; //输出&#xff1a;在屏幕打印"hello world!" return 0;}main函数 main 函数是…...

Rust包管理和构建工具

Cargo 是 Rust 语言的包管理和构建工具。它提供了一套完整的工具链&#xff0c;用于管理 Rust 项目的依赖关系、编译代码、运行测试和生成文档。Cargo 极大地简化了 Rust 项目的开发和部署过程&#xff0c;使得开发者可以专注于编写代码&#xff0c;而不是处理构建系统的复杂性…...

STM32输入捕获详解

目录 一、引言 二、输入捕获原理 三、寄存器介绍 四、配置步骤 1.开启时钟 2.GPIO 初始化 3.初始化定时器 4.配置输入捕获模式 5.使能捕获和更新中断 6.设置中断分组并编写中断服务函数 7.使能定时器 五、程序示例 六、总结 一、引言 在嵌入式系统开发中&#xff0…...

利用高德地图API,如何在PHP与vue3中实现地图缩放功能

文章精选推荐 1 JetBrains Ai assistant 编程工具让你的工作效率翻倍 2 Extra Icons&#xff1a;JetBrains IDE的图标增强神器 3 IDEA插件推荐-SequenceDiagram&#xff0c;自动生成时序图 4 BashSupport Pro 这个ides插件主要是用来干嘛的 &#xff1f; 5 IDEA必装的插件&…...

Selenium WebDriver:自动化网页交互的利器

Selenium WebDriver&#xff1a;自动化网页交互的利器 在当今快速发展的Web开发领域&#xff0c;自动化测试已经成为确保应用程序质量和用户体验的重要手段。Selenium WebDriver&#xff0c;作为Selenium工具包中的核心组件&#xff0c;正是这一领域的佼佼者。本文将详细介绍S…...

uniapp -- 实现页面滚动触底加载数据

效果 首选,是在pages.json配置开启下拉刷新 {"path": "pages/my/document/officialDocument","style": {"navigationStyle":</...

用ChatGPT-o1进行论文内容润色效果怎么样?

目录 1.引导问题发现 2.角色设定 3.整理常问修改 4.提供样例 5.小细节 小编在这篇文章中分享如何充分利用ChatGPT-o1-preview来提升论文润色的技巧。小编将持续跟进最新资源和最新的调研尝试结果&#xff0c;为宝子们补充更多实用的写作技巧。这些技巧将有助于您更有效地利…...

6 C/C++输⼊输出(下)(未完续)

1. OJ&#xff08;online judge&#xff09;题⽬输⼊情况汇总 在竞赛的 OJ 题⽬中&#xff0c;⼀般关于输⼊场景总结为下⾯四类&#xff1a; 接下来&#xff0c;我们就结合题⽬&#xff0c;给⼤家分别介绍。 1.1 单组测试⽤例 练习1 B2009 计算 (ab)/c 的值 - 洛谷 | 计算机科…...

题海拾贝:力扣 20、有效的括号

Hello大家好&#xff01;很高兴我们又见面啦&#xff01;给生活添点passion,开始今天的编程之路&#xff01; 我的博客&#xff1a;<但凡.-CSDN博客 我的专栏&#xff1a;《编程之路》、《题海拾贝》、《数据结构与算法之美》 欢迎点赞、关注&#xff01; 1、题目 2、题解 这…...

视频推拉流EasyDSS无人机直播技术巡查焚烧、烟火情况

焚烧作为一种常见的废弃物处理方式&#xff0c;往往会对环境造成严重污染。因此&#xff0c;减少焚烧、推广绿色能源和循环经济成为重要措施。通过加强森林防灭火队伍能力建设与长效机制建立&#xff0c;各地努力减少因焚烧引发的森林火灾&#xff0c;保护生态环境。 巡察烟火…...

基于Hermite多项式的三维反时间波的生成

原创&#xff1a;daode3056(daode1212) 反时间波&#xff0c;也称为时间反演波&#xff0c;是一种在特定条件下能够实现波的聚焦和传播的技术。反时间波的产生基于时间反演技术&#xff0c;其原理和方法通常有&#xff1a; 1. [时间反演信号处理原理]: 时间反演技术并不是指时间…...

数据结构与算法复习AVL树插入过程

环境 $ cat /proc/version Linux version 6.8.0-45-generic (builddlcy02-amd64-115) (x86_64-linux-gnu-gcc-13 (Ubuntu 13.2.0-23ubuntu4) 13.2.0, GNU ld (GNU Binutils for Ubuntu) 2.42) #45-Ubuntu SMP PREEMPT_DYNAMIC Fri Aug 30 12:02:04 UTC 2024 #include <std…...

MetaGPT源码 (Memory 类)

目录 MetaGPT源码&#xff1a;Memory 类例子 MetaGPT源码&#xff1a;Memory 类 这段代码定义了一个名为 Memory 的类&#xff0c;用于存储和管理消息(Message)对象。Memory 提供了多种操作消息的功能&#xff0c;包括添加单条或批量消息、按角色或内容筛选消息、删除最新消息…...