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

【Linux网络】I/O多路转接技术 - epoll

📢博客主页:https://blog.csdn.net/2301_779549673
📢博客仓库:https://gitee.com/JohnKingW/linux_test/tree/master/lesson
📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
📢本文由 JohnKi 原创,首发于 CSDN🙉
📢未来很长,值得我们全力奔赴更美好的生活✨

在这里插入图片描述

在这里插入图片描述

文章目录

  • 🏳️‍🌈一、epoll 概念
  • 🏳️‍🌈二、epoll 相关调用
    • 2.1 epoll_create 创建句柄
    • 2.2 epoll_ctl 执行事件
    • 2.3 epoll_wait 阻塞调用
  • 🏳️‍🌈三、epoll 工作原理
    • 3.1 数据到达主机
    • 3.2 epoll 工作原理
    • 3.3 模拟演示
      • 3.3.1 EpollServer 类
        • 3.3.1.1 基本结构
        • 3.3.1.2 构造函数、析构函数
        • 3.3.1.3 初始化函数 InitServer()
        • 3.3.1.4 循环函数 Loop()
        • 3.3.1.5 处理就绪事件函数 HandlerEvent()
        • 3.3.1.6 监听套接字处理函数 HandlerNewConnection()
        • 3.3.1.7 普通套接字处理函数 HandlerIO()
      • 3.3.2 EpollServer.cpp 主函数
      • 3.3.3 运行结果
  • 🏳️‍🌈四、epoll 的优点(与 select 的缺点对应)
  • 🏳️‍🌈五、epoll 工作方式
  • 🏳️‍🌈六、对比 LT 和 ET
  • 🏳️‍🌈七、理解 ET 模式和 非阻塞文件描述符
  • 🏳️‍🌈八、ET 模式的典型陷阱​
  • 🏳️‍🌈九、epoll 的使用场景
  • 👥总结


🏳️‍🌈一、epoll 概念

  • 按照 man 手册的说法: 是为处理大批量句柄而作了改进的 poll.
  • 它是在 2.5.44 内核中被引进的(epoll(4) is a new API introduced in Linux kernel 2.5.44)
  • 几乎具备了之前所说的一切优点,被公认为 Linux2.6 下性能最好的多路 I/O 就绪通知方法.

作用:等待多个fd,等待fd上面的新事件就绪,通知程序员,事件已经就绪,可以进行IO拷贝了!
定位:只负责进行等,等就绪事件派发!

🏳️‍🌈二、epoll 相关调用

2.1 epoll_create 创建句柄

int epoll_create(int size);     // 创建一个 epoll 句柄

参数

  • size:在早期版本的 Linux 中指定了监听的文件描述符数量上限,自从 linux2.6.8 之后,这个参数被忽略因为 epoll 自动调整以处理最大数量的文件描述符因此,传递任何大于 0 的值都是可以的,通常使用 1 作为默认值。

返回值

  • 成功时,epoll_create() 返回一个非负的文件描述符,该描述分用于后续的 epoll 操作
  • 失败时,返回 -1 并设置 errno 以指示错误类型

注意

  • 用完之后,必须调用 close 关闭

2.2 epoll_ctl 执行事件

 // 注册、注销、修改 epoll 事件
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);  

参数:

  • epfd: 由 epoll_create() 或 epoll_createl() 返回的 epoll 文件描述符
  • op:要执行的操作,可以是以下三个值之一
    • EPOLL_CTL_ADD:向 epoll 实例中添加一个新的文件描述符
    • EPOLL_CTL_DEL:从 epoll 实例中删除一个文件描述符
    • EPOLL_CTL_MOD修改一个已经存在于 epoll 实例中的文件描述符的监听事件
  • fd要添加、删除或修改的文件描述符
  • event执行一个 epoll_event 结构体的指针该结构体指定了要监听的事件类型和数据。对于 EPOLL_CTL_DEL 操作,这个参数可以是 nullptr,因为删除操作不需要直到事件类型

返回值:

  • 成功时,epoll_ctl() 返回 0
  • 失败时,返回 -1 并设置 errno 以指示错误类型

epoll_event 结构体

typedef union epoll_data
{void *ptr;int fd;uint32_t u32;uint64_t u64;
} epoll_data_t;struct epoll_event
{uint32_t events;	/* Epoll events */epoll_data_t data;	/* User data variable */
} __EPOLL_PACKED;

events 可以是以下几个宏的集合:

  • EPOLLIN : 表示对应的文件描述符可以读 (包括对端 SOCKET 正常关闭);
  • EPOLLOUT : 表示对应的文件描述符可以写;
  • EPOLLPRI : 表示对应的文件描述符有紧急的数据可读 (这里应该表示有带外数据到来);
  • EPOLLERR : 表示对应的文件描述符发生错误;
  • EPOLLHUP : 表示对应的文件描述符被挂断;
  • EPOLLET : 将 EPOLL 设为边缘触发(Edge Triggered)模式, 这是相对于水平触发(Level Triggered)来说的.
  • EPOLLONESHOT:只监听一次事件, 当监听完这次事件之后, 如果还需要继续监听这个 socket 的话, 需要再次把这个 socket 加入到 EPOLL 队列里.

在这里插入图片描述
在这里插入图片描述

2.3 epoll_wait 阻塞调用

// 阻塞调用线程,直到有至少一个文件描述符上的事件变得就绪,或者超时发生
int epopll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);

参数

  • epfd: 由 epoll_create() 或 epoll_createl() 返回的 epoll 文件描述符
  • events:指向一个 epoll_event 结构体数组的指针,该数组用于存储返回的事件信息
  • maxevents:events 数组的大小,即最多可以返回的事件数量
  • timeout等待事件的超时事件(毫秒)
    • 如果为 -1,则 无限期阻塞,直到有事件发生
    • 如果为 0 ,则 立即返回

返回值

  • 成功时,epoll_wait() 返回就绪事件的数量,这些事件被村粗在 events 数组中
  • 失败时,返回 -1 并设置 errno 以指示错误类型

🏳️‍🌈三、epoll 工作原理

3.1 数据到达主机

  • 数据到达主机的原理 涉及多个层级和协议的协同工作。通过逐层封装、转发和接收处理,数据能够准确地从源主机传输到目的主机
  • 硬件中断时由硬件设备发出的信号,用于通知计算机系统发生了某个事件,需要系统进行处理。这些硬件设备可以是磁盘、网卡、键盘、时钟等

在这里插入图片描述

3.2 epoll 工作原理

在这里插入图片描述

  • 当某一进程调用 epoll create 方法时,Linux 内核会创建一个 eventpoll 结构体,这个结构体中有两个成员epoll 的使用方式密切相关.
struct eventpoll{// 红黑树的根节点,这棵树中存储着所有添加到 epoll 中的需要监控的时间struct rb_root rbr;// 双链表中则存放着将要通过 epoll_wait 返回给用户的满足条件的事件struct list_head rdlist;
};
  • 每一个 epoll 对象都有一个独立的 eventpoll 结构体用于存放通过 epoll_ctl 方法向 epoll 对象中添加进来的事件
  • 这些事件就会挂载在红黑树中,如此,重复添加的事件就可以通过红黑树而高效地是识别出来(红黑树地插入时间效率是 lgn,其中 n 为树地高度)
  • >所有添加到 epoll 中的事件都会与设备(网卡)驱动程序建立回调关系</font,也就是说,当相应地事件发生时会调用这个回调方法
  • 这个回调方法在内核中叫 ep_poll_callback,它将发生的事件添加到 rdlist 双链表中
  • epoll 中,对于每一个事件,都会建立一个 epitem 结构体
struct epitem{struct rb_node rbn;         // 红黑树节点struct list_head rdlink;    // 双向链表节点struct epoll_filefd ffd;    // 事件句柄信息struct eventpoll* ep;       // 指向其所属地 eventpoll 对象struct epoll_event event;   // 其弟啊发生地事件类型
};
  • 当调用 epoll_wait 检查是否有事件发生时,只需要检查 eventpoll 对象中 rdlist 双链表中是否有 epitem 元素即可
  • 如果 rdlist 不会空,则把发生的事件复制到用户态,同时将事件数量返回给用户,这个操作的时间复杂度是 O(1)

总结一下, epoll 的使用过程就是三部曲

  1. 调用 epoll_create 创建一个 epoll 句柄
  2. 调用 epoll_ctl ,将要监控的文件描述符进行注册
  3. 调用 epoll_wait 等待文件描述符就绪

在这里插入图片描述

3.3 模拟演示

3.3.1 EpollServer 类

3.3.1.1 基本结构

EpollServer 类的成员变量包括 端口号,listen套接字,epfd(epoll_create()函数的返回值),接收事件的数组
成员函数与 PollServer 类基本一致!

class EpollServer{const static int gsize = 128;const static int gnum = 1024;public:EpollServer(uint16_t port);void InitServer();void Loop();~EpollServer();private:uint16_t _port;SockPtr _lostensock;int _epfd;struct epoll_event _events[gnum];
};
3.3.1.2 构造函数、析构函数

构造函数 初始化端口号,根据端口号创建监听套接字对象 以及 创建epoll句柄
析构函数 关闭 epfd(合法的前提下) 和 listensock

EpollServer(uint16_t port): _port(port), _listensock(std::unique_ptr<TcpSocket>()) {_listensock->BuildListenSocket(_port);_epfd = ::epoll_create(gsize);if (_epfd < 0) {LOG(LogLevel::FATAL) << "epoll_create error: " << errno;exit(1);}LOG(LogLevel::INFO) << "epoll_create success. epfd: " << _epfd;
}
~EpollServer() {if (_epfd >= 0)::close(_epfd);_listensock->Close();
}
3.3.1.3 初始化函数 InitServer()

InitServer() 函数使用系统调用(epoll_ctl),将 listensock 添加到 epoll

void InitServer() {struct epoll_event ev;ev.events = EPOLLIN;ev.data.fd = _listensock->Sockfd();// 将监听套接字添加到 epoll 中int n = epoll_ctl(_epfd, EPOLL_CTL_ADD, _listensock->Sockfd(), &ev);if (n < 0) {LOG(LogLevel::FATAL) << "epoll_ctl errno\n";exit(2);}LOG(LogLevel::INFO) << "epoll_ctl success, listensockfd: "<< _listensock->Sockfd();
}
3.3.1.4 循环函数 Loop()

Loop() 函数调用 epoll_wait 系统调用进行等待,根据返回值执行对应的操作:

  1. 返回值为0 :打印超时日志,并退出循环
  2. 返回值为-1 :打印出错日志,并退出循环
  3. 返回值大于0 :打印事件发生日志,并处理合法事件
void Loop() {int timeout = 1000;while (true) {int n = ::epoll_wait(_epfd, _events, gnum, timeout);switch (n) {case 0:LOG(LogLevel::INFO) << "epoll timeout";break;case -1:LOG(LogLevel::ERROR) << "epoll_wait error: " << errno;break;default:LOG(LogLevel::INFO) << "haved event happened, nums : " << n;HandlerEvent(n);break;}}
}
3.3.1.5 处理就绪事件函数 HandlerEvent()

HandlerEvent() 函数处理就绪事件,主要分为以下两步:

  1. 从事件数组中读取 合法fdevents
  2. 判断读事件是否就绪
    • listensock 就绪
    • normal sockfd 就绪
void HandlerEvent(int nums) {for (int i = 0; i < nums; ++i) {// 1. 从事件数组中读取合法的 fd 和 eventsint fd = _events[i].data.fd;uint32_t events = _events[i].events;LOG(LogLevel::INFO) << "上面有事件就绪了,具体事件是:" << fd << " "<< std::to_string(events).c_str();// 2. 判断是监听套接字就绪还是其他套接字就绪if (events & EPOLLIN) {if (fd == _listensock->Sockfd())HandlerNewConnection();elseHandlerIO(fd);}}
}
3.3.1.6 监听套接字处理函数 HandlerNewConnection()
  1. 获取链接
  2. 获取链接成功将新的 fd读事件 添加到 epoll(使用epoll_ctl系统调用)
void HandlerNewConnection() {InetAddr client;int sockfd = _listensock->Accepter(&client);if (sockfd < 0) {LOG(LogLevel::ERROR) << "accept error: " << errno;return;}LOG(LogLevel::INFO) << "get a new connection from "<< client.AddrStr().c_str() << ", sockfd : " << sockfd;struct epoll_event ev;ev.data.fd = sockfd;ev.events = EPOLLIN;::epoll_ctl(_epfd, EPOLL_CTL_ADD, sockfd, &ev);LOG(LogLevel::INFO) << "epoll_ctl success, sockfd: " << sockfd;
}
3.3.1.7 普通套接字处理函数 HandlerIO()

HandlerIO() 函数处理普通fd情况,直接读取文件描述符中的数据,根据recv()函数的返回值做出不一样的决策,主要分为以下三种情况:

  1. 返回值大于0,读取文件描述符中的数据,并使用 send() 函数做出回应!
  2. 返回值等于0,读到文件结尾,打印客户端退出的日志,关闭文件描述符,将 epfd 从 epoll 中移除并关闭 fd
  3. 返回值小于0,读取文件错误,打印接受失败的日志,然后同上!
void HandlerIO(int fd) {char buffer[1024];ssize_t n = ::recv(fd, buffer, sizeof(buffer) - 1, 0);if (n > 0) {buffer[n] = 0;std::cout << buffer;std::string content = "<html><body><h1>hello linux</h1></body></html>";std::string echo_str = "HTTP/1.0 200 OK\r\n";echo_str += "Content-Type: text/html\r\n";echo_str +="Content-Length: " + std::to_string(content.size()) + "\r\n\r\n";echo_str += content;::send(fd, echo_str.c_str(), echo_str.size(), 0);} else if (n == 0) {LOG(LogLevel::DEBUG) << "client " << fd << " closed";// 1. 从 epoll 中移除,从 epoll 中移除 fd,这个必须是健康 合法的// fd,否则会移除出错::epoll_ctl(_epfd, EPOLL_CTL_DEL, fd, nullptr);// 2. 关闭fd::close(fd);} else {LOG(LogLevel::ERROR) << "recv error: " << errno;// 1. 从 epoll 中移除,从 epoll 中移除 fd,这个必须是健康 合法的// fd,否则会移除出错::epoll_ctl(_epfd, EPOLL_CTL_DEL, fd, nullptr);// 2. 关闭fd::close(fd);}
}

3.3.2 EpollServer.cpp 主函数

根据主函数反向实现类和成员函数

#include "EpollServer.hpp"int main(int argc, char* argv[]){if(argc != 2){std::cerr << "Usage: " << argv[0] << " locak-port" << std::endl; }uint16_t port = std::stoi(argv[1]);std::unique_ptr<EpollServer> svr = std::make_unique<EpollServer>(port);svr->InitServer();svr->Loop();return 0;
}

3.3.3 运行结果

在这里插入图片描述

🏳️‍🌈四、epoll 的优点(与 select 的缺点对应)

  • 接口使用方便: 虽然拆分成了三个函数, 但是反而使用起来更方便高效. 不需要每次循环都设置关注的文件描述符, 也做到了输入输出参数分离开
  • 数据拷贝轻量: 只在合适的时候调用 EPOLL_CTL_ADD 将文件描述符结构拷贝到内核中, 这个操作并不频繁(而 select/poll 都是每次循环都要进行拷贝)
  • 事件回调机制: 避免使用遍历, 而是使用回调函数的方式, 将就绪的文件描述符结构加入到就绪队列中 , epoll_wait 返回直接访问就绪队列就知道哪些文件描述符就绪. 这个操作时间复杂度 O(1). 即使文件描述符数目很多, 效率也不会受到影响.
  • 没有数量限制: 文件描述符数目无上限.

网上有些博客说, epoll 中使用了内存映射机制

  • 内存映射机制: 内核直接将就绪队列通过 mmap 的方式映射到用户态. 避免了拷贝内存这样的额外性能开销.
  • 这种说法是不准确的. 我们定义的 struct epoll_event 是我们在用户空间中分配好的内存. 势必还是需要将内核的数据拷贝到这个用户空间的内存中的.

🏳️‍🌈五、epoll 工作方式

你妈喊你吃饭的例子

你正在吃鸡, 眼看进入了决赛圈, 你妈饭做好了, 喊你吃饭的时候有两种方式:

  1. 如果你妈喊你一次, 你没动, 那么你妈会继续喊你第二次, 第三次…(亲妈,水平触发)
  2. 如果你妈喊你一次, 你没动, 你妈就不管你了(后妈, 边缘触发)

epoll 有 2 种工作方式 - 水平触发(LT)边缘触发(ET)

假如有这样一个例子:

  • 我们已经把一个 tcp socket 添加到 epoll 描述符
  • 这个时候 socket 的另一端被写入了 2KB 的数据
  • 调用 epoll_wait,并且它会返回. 说明它已经准备好读取操作
  • 然后调用 read, 只读取了 1KB 的数据
  • 继续调用 epoll_wait…

水平触发 Level Triggered 工作模式

  • epoll 默认状态下就是 LT 工作模式.
  • epoll 检测到 socket 上事件就绪的时候, 可以不立刻进行处理. 或者只处理一部分.
  • 如上面的例子, 由于只读了 1K 数据, 缓冲区中还剩 1K 数据, 在第二次调用epoll_wait 时, epoll_wait 仍然会立刻返回并通知 socket 读事件就绪.
  • 直到缓冲区上所有的数据都被处理完, epoll_wait 才不会立刻返回.
  • 支持 阻塞读写非阻塞读写

边缘触发 Edge Triggered 工作模式

  • 如果我们在第 1 步将 socket 添加到 epoll 描述符的时候使用了 EPOLLET 标志, epoll 进入 ET 工作模式.
  • epoll 检测到 socket 上事件就绪时, 必须立刻处理.
  • 如上面的例子, 虽然只读了 1K 的数据, 缓冲区还剩 1K 的数据, 在第二次调用epoll_wait 的时候, epoll_wait 不会再返回了.
  • 也就是说, ET 模式下, 文件描述符上的事件就绪后, 只有一次处理机会.
  • ET 的性能比 LT 性能更高( epoll_wait 返回的次数少了很多). Nginx 默认采用ET 模式使用 epoll.
  • 只支持 非阻塞 的读写

selectpoll 其实也是工作在 LT 模式下.
epoll 既可以支持 LT, 也可以支持 ET.

🏳️‍🌈六、对比 LT 和 ET

  1. LTepoll 的默认行为.
  2. 使用 ET 能够减少 epoll 触发的次数. 但是代价就是强逼着程序猿 一次响应就绪过程中就把所有的数据都处理完
    • 相当于一个文件描述符就绪之后, 不会反复被提示就绪, 看起来就比 LT 更高效一些. 但是在 LT 情况下如果也能做到每次就绪的文件描述符都立刻处理, 不让这个就绪被重复提示的话, 其实性能也是一样的.
  3. 另一方面, ET 的代码复杂程度更高了.
  4. ET 的通知效率更高
  5. ET可能给对方一个更大的接受窗口增加IO效率 – 即ET的IO效率更高

🏳️‍🌈七、理解 ET 模式和 非阻塞文件描述符

使用 ET 模式的 epoll, 需要将文件描述设置为非阻塞. 这个不是接口上的要求, 而是 “工程实践” 上的要求,逻辑如下:

在这里插入图片描述
有一个问题:LT也可以设置非阻塞,LT我也可以循环读取完毕啊,为什么要有ET呢?

最简单的理解,ET是被强制要求非阻塞的,但是LT可以是阻塞也可以是非阻塞!

假设这样的场景: 服务器接收到一个 10k 的请求, 会向客户端返回一个应答数据. 如果客户端收不到应答, 不会发送第二个 10k 请求

在这里插入图片描述

如果服务端写的代码是阻塞式的 read, 并且一次只 read 1k 数据的话(read 不能保证一次就把所有的数据都读出来, 参考 man 手册的说明, 可能被信号打断), 剩下的 9k 数据就会待在缓冲区中.

在这里插入图片描述

此时由于 epoll 是 ET 模式, 并不会认为文件描述符读就绪. epoll_wait 就不会再次返回. 剩下的 9k 数据会一直在缓冲区中. 直到下一次客户端再给服务器写数据.epoll_wait 才能返回

但是问题来了

  • 服务器只读到 1k 个数据, 要 10k 读完才会给客户端返回响应数据.
  • 客户端要读到服务器的响应, 才会发送下一个请求
  • 客户端发送了下一个请求, epoll_wait 才会返回, 才能去读缓冲区中剩余的数据.

在这里插入图片描述

所以, 为了解决上述问题(阻塞 read 不一定能一下把完整的请求读完), 于是就可以使用非阻塞轮训的方式来读缓冲区, 保证一定能把完整的请求都读出来.

而如果是 LT 没这个问题. 只要缓冲区中的数据没读完, 就能够让 epoll_wait 返回文件描述符读就绪.

🏳️‍🌈八、ET 模式的典型陷阱​

  1. ​事件丢失​
    • 若未在 ET 模式下一次性处理完所有数据,剩余数据不会再次触发 EPOLLIN,导致数据滞留。
    • 解决:必须循环读取直到 EAGAIN。
  2. 饥饿问题​
    • 若某个 fd 持续有数据到达,可能独占事件循环,导致其他 fd 得不到处理。
    • ​解决: 设置处理上限(如每次最多读取 10 次),或使用 EPOLLONESHOT 标志。
  3. 错误处理遗漏​
    • 未处理 EPOLLERR 或 EPOLLHUP,导致程序无法感知连接异常。
    • ​解决: 始终优先检查错误事件:

🏳️‍🌈九、epoll 的使用场景

epoll 的高性能, 是有一定的特定场景的. 如果场景选择的不适宜, epoll 的性能可能适得其反.

  • 对于多连接, 且多连接中只有一部分连接比较活跃时, 比较适合使用 epoll.

例如, 典型的一个需要处理上万个客户端的服务器, 例如各种互联网 APP 的入口服务器,这样的服务器就很适合 epoll.

如果只是系统内部, 服务器和服务器之间进行通信, 只有少数的几个连接, 这种情况下用 epoll 就并不合适. 具体要根据需求和场景特点来决定使用哪种 IO 模型.


👥总结

本篇博文对 【Linux网络】I/O多路转接技术 - epoll 做了一个较为详细的介绍,不知道对你有没有帮助呢

觉得博主写得还不错的三连支持下吧!会继续努力的~

相关文章:

【Linux网络】I/O多路转接技术 - epoll

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;博客仓库&#xff1a;https://gitee.com/JohnKingW/linux_test/tree/master/lesson &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &…...

机器学习经典算法:用决策树原理优化新能源汽车续航能力

&#x1f525; “用决策树重构新能源车能量大脑&#xff01;算法推导代码实战全解&#xff0c;续航暴增15%” 决策树算法就像我们生活中做决策的 “流程指南”&#xff0c;通过层层判断得出最终结论。比如你去超市买水果&#xff0c;站在琳琅满目的货架前&#xff0c;就不自觉地…...

深入探讨宾馆一次性牙刷价格,市场价格区间差异大

在我们日常出行、住宿的时候&#xff0c;宾馆的一次性牙刷是常见的、标配的物品。许多人或许都会感到好奇&#xff0c;这些一次性牙刷到底值多少钱。下面就来深入探讨一下宾馆一次性牙刷价格方面的问题。 市场价格区间 宾馆一次性牙刷价格差距大&#xff0c;便宜的一支可能只…...

深入解析 .NET Kestrel:高性能 Web 服务器的架构与最佳实践

Kestrel 是 .NET 中用于处理 HTTP 请求的高性能 Web 服务器。作为 ASP.NET Core 的默认服务器&#xff0c;Kestrel 被设计为在高并发、高吞吐量的环境下表现优异&#xff0c;并且能够支持多种协议和跨平台操作。本文将深入探讨 Kestrel 的架构设计、工作原理、配置方式、性能优…...

ZYNQ 纯PL端逻辑资源程序固化流程

ZYNQ 纯PL端逻辑资源程序固化 ZYNQ的程序固化流程比传统的FPGA固化流程复杂很多&#xff0c;Vivado生成的bit文件无法直接固化在ZYNQ芯片中。因为ZYNQ 非易失性存储器的引脚&#xff08;如 SD 卡、QSPI Flash&#xff09;是 ZYNQ PS 部分的专用引脚。这些非易失性存储器由 PS …...

【树莓派Pico FreeRTOS】-FreeRTOS-SMP移植

FreeRTOS-SMP移植 文章目录 FreeRTOS-SMP移植1、Raspberry Pi Pico SDK准备2、下载最新FreeRTOS-Kernel源码3、Raspberry Pi Pico的开发环境搭建4、编译配置5、FreeRTOSConfig.h文件设置6、演示代码RP2040 由 Raspberry Pi 设计,具有双核 Arm Cortex-M0+ 处理器和 264KB 内部 …...

数字智慧方案5961丨智慧能源与运维云平台解决方案(52页PPT)(文末有下载方式)

详细资料请看本解读文章的最后内容。 资料解读&#xff1a;智慧能源与运维云平台解决方案 在当今数字化时代&#xff0c;能源管理与设备运维的智能化、高效化成为企业发展的关键。智慧能源与运维云平台解决方案应运而生&#xff0c;为企业提供了全面且先进的能源管理和运维手段…...

2025东三省C题深圳杯C题数学建模挑战赛数模思路代码文章教学: 分布式能源接入配电网的风险分析

完整内容请看文章最下面的推广群 数据整理与分析 表1&#xff1a;有源配电网62节点系统负荷参数 内容&#xff1a;列出了62个节点的有功负荷&#xff08;单位&#xff1a;kW&#xff09;。 特点&#xff1a; 负荷范围&#xff1a;24 kW&#xff08;节点19&#xff09;到420 …...

腾讯云BI VS quickbi 企业选型(从企业实际功能使用和费用对比)

腾讯云BI VS quickbi 选型 一、总结 前段时间领导让调研腾讯云BI&#xff0c;用来做BI选型&#xff0c;现根据公司实际使用功能做如下总结。 建议继续使用quickbi&#xff0c;不选择腾讯云BI 原因&#xff1a; 腾讯云BI专业版&#xff0c;官方价格最低101996元。并且只能选…...

WebDeveloper 流量分析、sudo提权,靶场通关WP

一、信息收集 1、主机探测 arp-scan -l netdiscover -i eth0 -r 192.168.33.0/24 nmap -sP 192.168.66.0/24 2、端口扫描 nmap -sS -sV 192.168.66.141 PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4 (Ubuntu Linux; protocol 2.0) 80/tcp op…...

编写教育网站后端页面笔记

callbacktitle.html 对应表: 对应的功能: 控制器层数据: 页面没有写内容 chapter.html 对应表: questionbank ,intofloortime,questionBank,title,didtitles,option,answer,analyse 对应的功能:问题反馈页面 控制器层数据(控制器类): ChapterQuestionbankTitle c…...

C++漫溯键值的长河:map set

文章目录 1.关联式容器2.set2.1 find2.2 lower_bound、upper_bound 3.multiset3.1 count3.2 equal_range 4.map4.1 insert4.2 operate->4.3 operate[ ]4.4 map的应用实践&#xff1a;随机链表的复制 5.multimap希望读者们多多三连支持小编会继续更新你们的鼓励就是我前进的动…...

西门子数字化研发设计制造一体化规划案例P87(87页PPT)(文末有下载方式)

资料解读&#xff1a;《西门子数字化研发设计制造一体化规划案例》 详细资料请看本解读文章的最后内容。 该文档围绕西门子为企业打造的智能化制造研发工艺生产一体化平台规划方案展开&#xff0c;全面阐述了从业务现状分析到项目实施及案例分享的整个过程。 业务现状与需求分析…...

Rust多线程性能优化:打破Arc+锁的瓶颈,效率提升10倍

一、引言 在 Rust 开发中&#xff0c;多线程编程是提升程序性能的重要手段。Arc&#xff08;原子引用计数&#xff09;和锁的组合是实现多线程数据共享的常见方式。然而&#xff0c;很多程序员在使用 Arc 和锁时会遇到性能瓶颈&#xff0c;导致程序运行效率低下。本文将深入剖…...

基于python的人工智能应用简述

基于Python的人工智能应用简述 Python已成为人工智能(AI)开发的首选语言,凭借其简洁性、丰富的库生态系统和强大的社区支持,广泛应用于各类AI应用场景。以下是Python在人工智能领域的主要应用领域和技术实现。 1. 机器学习(Machine Learning) Python通过Scikit-learn、Ten…...

《Android 应用开发基础教程》——第十章:使用 Gson 实现网络 JSON 数据解析与对象映射

目录 第十章&#xff1a;使用 Gson 实现网络 JSON 数据解析与对象映射 &#x1f539; 10.1 什么是 Gson&#xff1f; &#x1f538; 10.2 添加依赖 &#x1f538; 10.3 基础使用 ✦ 示例 JSON 字符串&#xff1a; ✦ 定义对应的 Java 类&#xff1a; ✦ JSON ➜ 对象&am…...

【Android】四大组件之BroadcastReceiver

目录 一、什么是BroadcastReceiver 二、创建和使用BroadcastReceiver 三、跨应用广播接收权限 四、广播方式 五、广播类型与特性 六、BroadcasReceiver注册方式 七、BroadcasReceiver工作流程 你可以把广播接收器想象成一个“收音机”。它的作用是监听系统或应用发出的“…...

[UVM]寄存器模型的镜像值和期望值定义是什么?他们会保持一致吗?

寄存器模型的镜像值和期望值定义是什么&#xff1f;他们会保持一致吗&#xff1f; 摘要&#xff1a;在 UVM (Universal Verification Methodology) 寄存器模型中&#xff0c;镜像值 (mirrored value) 和期望值 (desired value) 是两个非常重要的概念&#xff0c;用于管理寄存器…...

OpenGL-ES 学习(12) ---- VBO EBO VAO

目录 VBO 定义VBO 创建统一VertexData使用 VBO 绘制VAO VBO 定义 VBO(Vertex Buffer Object) 是指顶点缓冲区对象&#xff0c;而 EBO(Element Buffer Object)是指图元索引缓冲区对象&#xff0c;VBO 和 EBO实际上是同一类 buffer 按照用途的不同称呼 OpenGL-ES2.0 编程中&…...

【Redis分布式】主从复制

&#x1f525;个人主页&#xff1a; 中草药 &#x1f525;专栏&#xff1a;【中间件】企业级中间件剖析 一、主从复制 在分布式系统之中为了解决单点问题&#xff08;1、可用性问题&#xff0c;该机器挂掉服务会停止2、性能支持的并发量是有限的&#xff09;通常会把数据复制多…...

Node.js心得笔记

npm init 可用npm 来调试node项目 浏览器中的顶级对象时window <ref *1> Object [global] { global: [Circular *1], clearImmediate: [Function: clearImmediate], setImmediate: [Function: setImmediate] { [Symbol(nodejs.util.promisify.custom)]: [Getter] }, cl…...

多智能体空域协同中的伦理博弈与系统调停

在多智能体系统&#xff08;MAS&#xff09;广泛应用于低空飞行调度、应急响应与城市管理的背景下&#xff0c;AI之间的“协同”不仅是算法效率问题&#xff0c;更是伦理角色之间的权责动态博弈。尤其在高频互动、任务冲突、资源抢占等复杂场景中&#xff0c;智能体不再是“工具…...

面试中系统化地解答系统设计题:通用方法论

目录 一、明确需求(Clarify Requirements) (一)理解业务背景 (二)功能性需求(Functional Requirements) 1. 分析目标 2. 功能需求分类 A. 用户交互类功能 B. 数据处理类功能 C. 管理与运维类功能 D. 外部系统交互类功能 示例场景详解 3. 捕捉隐藏需求的技巧…...

kotlin中 热流 vs 冷流 的本质区别

&#x1f525; 冷流&#xff08;Cold Flow&#xff09; vs 热流&#xff08;Hot Flow&#xff09;区别 特性冷流&#xff08;Cold Flow&#xff09;热流&#xff08;Hot Flow&#xff09;数据生产时机每次 collect 才开始执行启动时就开始生产、始终运行生命周期与 collect 者…...

机器视觉开发-打开摄像头

以下是使用Python和OpenCV打开摄像头的最简单实现&#xff1a; import cv2# 打开默认摄像头&#xff08;通常是0&#xff09; cap cv2.VideoCapture(0)# 检查摄像头是否成功打开 if not cap.isOpened():print("无法打开摄像头")exit()print("摄像头已打开 - 按…...

Rerank详解

疑惑一 我对rag的流程理解是。后端首先建立embedding后的向量数据库&#xff0c;用户提问使用相同的embedding模型进行向量化&#xff0c;使用阈值控制相似度找出前topk个数据。然后rerank&#xff0c;将rerank的结果打包成prompt返回给大模型进行解答。我对于rerank的过程不是…...

深度探索DeepSeek:从架构设计到性能优化的实战指南

深度解码DeepSeek&#xff1a;从架构设计到工业级部署的全链路优化实践 引言&#xff1a;大模型时代的工程挑战 在人工智能技术进入工业化落地阶段的今天&#xff0c;大模型训练与推理的工程化能力已成为衡量企业技术实力的重要标尺。DeepSeek作为当前业界领先的超大规模语言…...

d202551

目录 一、175. 组合两个表 - 力扣&#xff08;LeetCode&#xff09; 二、511. 游戏玩法分析 I - 力扣&#xff08;LeetCode&#xff09; 三、1204. 最后一个能进入巴士的人 - 力扣&#xff08;LeetCode&#xff09; 一、175. 组合两个表 - 力扣&#xff08;LeetCode&#xf…...

(C题|社交媒体平台用户分析问题)2025年第二十二届五一数学建模竞赛(五一杯/五一赛)解题思路|完整代码论文集合

我是Tina表姐&#xff0c;毕业于中国人民大学&#xff0c;对数学建模的热爱让我在这一领域深耕多年。我的建模思路已经帮助了百余位学习者和参赛者在数学建模的道路上取得了显著的进步和成就。现在&#xff0c;我将这份宝贵的经验和知识凝练成一份全面的解题思路与代码论文集合…...

计网_PPP协议

2024.10.15&#xff1a;beokayy计算机网络学习笔记 PPP协议 PPP协议的特点PPP协议应满足的需求&#xff08;了解&#xff09;PPP协议的组成&#xff08;PPP协议有三个组成部分&#xff09; PPP协议的帧格式PPP协议的工作状态 ISP指的是运营商&#xff0c;比如中国联通、中国电信…...

Mem0.ai研究团队开发的全新记忆架构系统“Mem0”正式发布

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…...

二叉树删除结点详细代码

#include<stdio.h> #include<stdlib.h> #include<string.h> #include<time.h>typedef int data_t; typedef struct _node {data_t data;struct _node* left;struct _node* right; }node_t;int bst_create(node_t**, data_t);//函数声明BST创建 int bst…...

PyTorch线性代数操作详解:点积、矩阵乘法、范数与轴求和

本文通过代码示例详细讲解PyTorch中常用的线性代数操作&#xff0c;包括点积、矩阵乘法、范数计算以及按轴求和等操作&#xff0c;帮助读者掌握张量运算的核心方法。 1. 点积运算 点积&#xff08;Dot Product&#xff09;是两个向量对应元素相乘后求和的结果。 实现代码&…...

Java SE(6)——类和对象

1.初始面向对象 1.1 什么是面向对象 Java是一门纯面向对象的编程语言(Object Oriented Program&#xff0c;简称OOP)&#xff0c;在面向对象的世界里&#xff0c;一切皆为对象。面向对象是解决问题的一种思想&#xff0c;主要依靠对象之间的交换来完成一件事情 1.2 面向过程…...

Kubernetes(k8s)的API Server 组件原理与结合生产实战教程

一、API Server 架构深度解析 1. 核心架构设计 二、生产环境安全加固实战 1. 认证&#xff08;Authentication&#xff09; 2. 授权&#xff08;Authorization&#xff09; 3. 准入控制&#xff08;Admission Control&#xff09; 三、性能优化与调参 1. 关键启动参数 四…...

Java面试高频问题(31-33)

三十一、服务网格&#xff1a;东西向流量治理与故障注入 服务网格架构分层 mermaid graph BT subgraph Control Plane APilot --> BEnvoy Sidecar CMixer --> B DCitadel --> B end subgraph Data Plane B --> E服务A B --> F服务B B --> G服务C end 核心能…...

VSCode开发调试Python入门实践(Windows10)

我的Windows10上的python环境是免安装直接解压的Python3.8.x老版本&#xff0c;可参见《Windows下Python3.8环境快速安装部署。 1. 安装VSCode 在Windows 10系统上安装Visual Studio Code&#xff08;VS Code&#xff09;是一个简单的过程&#xff0c;以下是详细的安装方法与…...

C++——入门基础(2)

文章目录 一、前言二、C入门2.1 缺省参数2.2 函数重载2.2.1 参数类型不同2.2.1.1 整体参数类型不同2.2.1.2 参数类型顺序不同 2.2.2 参数个数不同2.2.3 避坑注意2.2.3.1无参与有参2.2.3.2 返回值不同 2.3 引用2.3.1 引用的概念2.3.2引用的结构2.3.3 引用的特点2.3.4引用的作用2…...

【MySQL】复合查询与内外连接

目录 一、复合查询 1、基本查询回顾&#xff1a; 2、多表查询&#xff1a; 3、自连接&#xff1a; 4、子查询&#xff1a; 单列子查询 多行子查询&#xff1a; 多列子查询&#xff1a; 在from语句中使用子查询&#xff1a; 5、合并查询&#xff1a; union&#xff1…...

第3篇:请求参数处理与数据校验

在 Web 开发中&#xff0c;请求参数处理与数据校验是保障系统稳定性的第一道防线。本文将深入探讨 Egg.js 框架中参数处理的完整解决方案&#xff0c;涵盖常规参数获取、高效校验方案、文件流处理等核心功能&#xff0c;并分享企业级项目中的最佳实践。 一、多场景参数获取策略…...

Android JIT编译:adb shell cmd package compile选项

Android JIT编译&#xff1a;adb shell cmd package compile选项 例如&#xff1a; adb shell cmd package compile -m speed -f --full 包名 配置参数指令说明&#xff1a; compile [-r COMPILATION_REASON] [-m COMPILER_FILTER] [-p PRIORITY] [-f] [--primary-dex] …...

排序算法——冒泡排序

一、介绍 「冒泡排序bubblesort」通过连续地比较与交换相邻元素实现排序。这个过程就像气泡从底部升到顶部一样&#xff0c;因此得名冒泡排序。 冒泡过程可以利用元素交换操作来模拟&#xff1a;从数组最左端开始向右遍历&#xff0c;依次比较相邻元素大小&#xff0c;如果“左…...

文献阅读篇#5:5月一区好文阅读,BFA-YOLO,用于建筑信息建模!(上)

期刊简介&#xff1a;《Advanced Engineering Informatics》创刊于2002年&#xff0c;由Elsevier Ltd出版商出版&#xff0c;出版周期Quarterly。该刊已被SCIE数据库收录&#xff0c;在中科院最新升级版分区表中&#xff0c;该刊分区信息为大类学科工程技术1区&#xff0c;2023…...

工行手机银行安全吗?在应用商店下载工商银行安全吗?

现在很多的人都会用手机银行&#xff0c;其中工行的使用几率也是比较高的&#xff0c;但大家在使用的过程中就会比较关心使用工行手机银行是否安全。如果直接在应用商店下载&#xff0c;是否有安全保障&#xff1f; 工行的手机银行会拥有较高的保障&#xff0c;从技术到服务都可…...

python如何word转pdf

在Python中&#xff0c;将Word文档&#xff08;.docx或.doc&#xff09;转换为PDF可以通过多种库实现。以下是几种常见的方法及详细步骤&#xff1a; 方法1&#xff1a;使用 python-docx comtypes&#xff08;仅Windows&#xff0c;需安装Word&#xff09; 适用于Windows系统…...

在阿里云 Ubuntu 24.04 上部署 RabbitMQ:一篇实战指南

前言 RabbitMQ 是业界常用的开源消息中间件,支持 AMQP 协议,易于部署、高可用、插件丰富。本文以阿里云 ECS 上运行的 Ubuntu 24.04 LTS 为例,手把手带你完成 RabbitMQ 从仓库配置到运行的全流程,并分享在国内环境下常见的坑与对应解决方案。 环境概况 操作系统:Ubuntu …...

Linux Shell 重定向与管道符号(>, >>, |)的实现机制

文章目录 Linux Shell 重定向与管道符号&#xff08;>, >>, |&#xff09;的实现机制一、重定向基础&#xff1a;dup2() 的核心作用二、输出重定向的实现原理>&#xff08;覆盖重定向&#xff09;>>&#xff08;追加重定向&#xff09; 三、| 管道符的实现原…...

GitHub 趋势日报 (2025年04月30日)

本日报由 TrendForge 系统生成 https://trendforge.devlive.org/ &#x1f4c8; 今日整体趋势 Top 10 排名项目名称项目描述今日获星总星数语言1hacksider/Deep-Live-Camreal time face swap and one-click video deepfake with only a single image⭐ 1686⭐ 54925Python2Qwe…...

计算机操作系统知识集合

主要来自小林coding 硬件结构 cpu位宽 如果用 32 位 CPU 去加和两个 64 位大小的数字&#xff0c;就需要把这 2 个 64 位的数字分成 2 个低位 32 位数字和 2 个高位 32 位数字来计算&#xff0c;先加个两个低位的 32 位数字&#xff0c;算出进位&#xff0c;然后加和两个高位…...

PostgreSQL数据类型

数据类型 数值类型 整数类型 SMALLINT 小范围整数&#xff0c;取值范围&#xff1a;-32768 ~ 32767 INT&#xff08;INTEGER&#xff09; 普通大小整数&#xff0c;取值范围&#xff1a;-2147483648 ~ 2147483647 浮点数类型 REAL 6位十进制数字精度 NUMERIC(m, n) 任意精度…...