深入理解Linux网络随笔(五):深度理解本机网络I/O
深入理解Linux网络随笔(五):深度理解本机网络I/O
文章目录
- 深入理解Linux网络随笔(五):深度理解本机网络I/O
- 本机发送过程
- 本机接收过程
- 总结
分析本机网络I/O部分源码需要知道本机I/O是什么?扮演什么角色?
本机网络 I/O(Local Network I/O)是指发生在同一台设备上的网络输入/输出操作,数据在本机内部流转,而不经过外部网络。主要包括以下几种情况:
(1)回环通信
通过 127.0.0.1
(IPv4)或 ::1
(IPv6) 进行的网络通信,发送到 回环接口 lo
,数据不会经过网卡,而是直接在内核中处理。
(2)主机内部不同进程的网络通信
两个进程使用本机 IP(非 127.0.0.1
)进行通信。
(3)通过 TUN/TAP 设备的虚拟网络通信
VPN、容器、虚拟网络设备 等使用 TUN/TAP 设备进行本机 I/O,关于本机网络I/O主要分为两部分内容:本机发送过程和本机接收过程。
本机发送过程
前几篇文章中已经分析了网络层的入口函数是ip_queue_xmit
,在网络层会进行路由相关工作。
int __ip_queue_xmit(struct sock *sk, struct sk_buff *skb, struct flowi *fl,__u8 tos)
{struct inet_sock *inet = inet_sk(sk);struct net *net = sock_net(sk);struct ip_options_rcu *inet_opt;struct flowi4 *fl4;struct rtable *rt;struct iphdr *iph;int res;//检查socket是否有缓存的路由表/* Make sure we can route this packet. */rt = (struct rtable *)__sk_dst_check(sk, 0);if (!rt) {__be32 daddr;/* Use correct destination address if we have options. */daddr = inet->inet_daddr;if (inet_opt && inet_opt->opt.srr)daddr = inet_opt->opt.faddr;/* If this fails, retransmit mechanism of transport layer will* keep trying until route appears or the connection times* itself out.*///查找路由rt = ip_route_output_ports(net, fl4, sk,daddr, inet->inet_saddr,inet->inet_dport,inet->inet_sport,sk->sk_protocol,RT_CONN_FLAGS_TOS(sk, tos),sk->sk_bound_dev_if);if (IS_ERR(rt))goto no_route;//设置路由信息到skbsk_setup_caps(sk, &rt->dst);}skb_dst_set_noref(skb, &rt->dst);
}
调用逻辑ip_route_output_ports-->ip_route_output_flow-->__ip_route_output_key-->ip_route_output_key_hash-->ip_route_output_key_hash_rcu
,在ip_route_output_key_hash_rcu
函数中完成路由查找。
struct rtable *ip_route_output_key_hash_rcu(struct net *net, struct flowi4 *fl4,struct fib_result *res,const struct sk_buff *skb)
{struct net_device *dev_out = NULL;//网络设备loint orig_oif = fl4->flowi4_oif;//原始输出接口索引unsigned int flags = 0;//路由标志位struct rtable *rth;//路由表条目int err;// 执行查找转发信息库(FIB),获取路由err = fib_lookup(net, fl4, res, 0);if (err) {res->fi = NULL;res->table = NULL;if (fl4->flowi4_oif &&(ipv4_is_multicast(fl4->daddr) || !fl4->flowi4_l3mdev)) {if (fl4->saddr == 0)fl4->saddr = inet_select_addr(dev_out, 0,RT_SCOPE_LINK);res->type = RTN_UNICAST;//单播路由goto make_route;}rth = ERR_PTR(err);goto out;}//路由类型是本地路由if (res->type == RTN_LOCAL) {if (!fl4->saddr) {if (res->fi->fib_prefsrc)//存在首选源地址fl4->saddr = res->fi->fib_prefsrc;else//否则选择目标地址fl4->saddr = fl4->daddr;}//根据路由结果设备确定L3主设备,默认回环设备dev_out = l3mdev_master_dev_rcu(FIB_RES_DEV(*res)) ? :net->loopback_dev;//FIB输出接口orig_oif = FIB_RES_OIF(*res);//设置输出接口fl4->flowi4_oif = dev_out->ifindex;flags |= RTCF_LOCAL;//设置本地标志goto make_route;}fib_select_path(net, res, fl4, skb);dev_out = FIB_RES_DEV(*res);// 获取路由的输出设备
}
针对传入的路由类型进行操作,RTN_LOCAL
类型是对本地local表展开查询,并设置了net->loopback_dev
,所以不论是本机IP还是127.0.0.1回环地址都会被添加到 local
路由表,查询完成之后调用fib_lookup
,fib_lookup
会先检查local
表,只有未在local
表找到匹配项时才会继续查找main
表。这种设计优化了本机通信的性能,因为本机通信的数据包只需要在local
表中查找即可。
static inline int fib_lookup(struct net *net, const struct flowi4 *flp,struct fib_result *res, unsigned int flags)
{struct fib_table *tb;int err = -ENETUNREACH; // 默认返回 "网络不可达" 错误// 加读锁,保护 RCU 访问的 FIB 结构rcu_read_lock();// 获取主路由表 RT_TABLE_MAIN(ID = 254)tb = fib_get_table(net, RT_TABLE_MAIN);if (tb)// 在主路由表中查找匹配的路由项err = fib_table_lookup(tb, flp, res, flags | FIB_LOOKUP_NOREF);// 如果查找返回 -EAGAIN,则转换为 -ENETUNREACHif (err == -EAGAIN)err = -ENETUNREACH;// 释放 RCU 读锁rcu_read_unlock();return err; // 返回查找结果
}
通过ip route list table local
查看本地路由表,但是一般我们只看到了local表,为什么?因为在fib_lookup()
路由查找函数,它会依次检查多个路由表,但当查找到 local
表时,就会直接返回,不再继续查找 main
表,只有当 local
表未匹配时,才会去 main
表查询。如下图,可以看到直接走 lo
设备,根本不会去 main
表查找。
数据包经过网络层到达邻居子系统再到网络设备子系统,前文也分析了网络设备子系统的入口函数是dev_queue_xmit
。对于普通网卡,数据包进入__dev_xmit_skb
入队,当q->enqueue
为空,说明当前是lo回环设备,调用dev_hard_start_xmit
进行入队。
int __dev_queue_xmit(struct sk_buff *skb, struct net_device *sb_dev)
{struct net_device *dev = skb->dev;struct netdev_queue *txq = NULL;struct Qdisc *q;int rc = -ENOMEM;bool again = false;if (q->enqueue) {rc = __dev_xmit_skb(skb, q, dev, txq);goto out;
}if (dev->flags & IFF_UP) {...skb = dev_hard_start_xmit(skb, dev, txq, &rc);
}}
通过 dev->netdev_ops
获取到该设备的操作集合 netdev_ops
,包含了设备相关操作的指针,然后调用__netdev_start_xmit
实际执行数据包的发送操作。
static inline netdev_tx_t netdev_start_xmit(struct sk_buff *skb, struct net_device *dev,struct netdev_queue *txq, bool more)
{const struct net_device_ops *ops = dev->netdev_ops;netdev_tx_t rc;rc = __netdev_start_xmit(ops, skb, dev, more);if (rc == NETDEV_TX_OK)txq_trans_update(txq);return rc;
}
static const struct net_device_ops loopback_ops = {.ndo_init = loopback_dev_init,//初始化.ndo_start_xmit = loopback_xmit,//发送数据.ndo_get_stats64 = loopback_get_stats64,//获取回环接口的64位信息.ndo_set_mac_address = eth_mac_addr,//设置网络设备的 MAC 地址
};
调用skb_orphan
将数据包与原始 socket 之间的联系,将其状态设置为“孤立”,因为lo回环设备用于数据包的自我发送和接收。当数据包从一个应用程序发送到环回设备时,它会返回到发送应用程序。这个过程不会经过网络堆栈的复杂路由和传输层处理,而是直接通过环回接口返回。所以不希望将数据包与某个特定的应用层套接字直接绑定,仅仅在设备间完成传输。接着调用__netif_rx
发送数据包。
static netdev_tx_t loopback_xmit(struct sk_buff *skb,struct net_device *dev)S
{int len;//计算数据包发送时间戳skb_tx_timestamp(skb);/* do not fool net_timestamp_check() with various clock bases *///清除skb_clear_tstamp(skb);//剥离掉和原socket联系skb_orphan(skb);/* Before queueing this packet to __netif_rx(),* make sure dst is refcounted.*/skb_dst_force(skb);skb->protocol = eth_type_trans(skb, dev);len = skb->len;//数据包交付if (likely(__netif_rx(skb) == NET_RX_SUCCESS))dev_lstats_add(dev, len);return NETDEV_TX_OK;
}
函数调用逻辑__netif_rx-->netif_rx_internal-->enqueue_to_backlog
。该函数将skb添加在等待队列尾部,主要完成三件事:
1、使用per_cpu宏访问每个CPU核心对应的softnet_data
数据结构(softnet_data
保存每个CPU核心的网络接收队列等,避免CPU之间竞争资源)
2、__skb_queue_tail
将skb添加到等待队列尾部
3、napi_schedule_rps
NAPI机制处理接收的数据包
static int enqueue_to_backlog(struct sk_buff *skb, int cpu,unsigned int *qtail)
{enum skb_drop_reason reason;struct softnet_data *sd;unsigned long flags;unsigned int qlen;//丢包原因:未指定reason = SKB_DROP_REASON_NOT_SPECIFIED;//per_cpu宏访问每个CPU核心对应的softnet_data数据结构sd = &per_cpu(softnet_data, cpu);//获取锁保存当前中断状态rps_lock_irqsave(sd, &flags);if (!netif_running(skb->dev))goto drop;//获取接收队列长度qlen = skb_queue_len(&sd->input_pkt_queue);//检查队列长度是否小于等于网络设备的最大容纳的队列长度,是否到达了流量限制if (qlen <= READ_ONCE(netdev_max_backlog) && !skb_flow_limit(skb, qlen)) {if (qlen) {
enqueue:
//将skb加入等待队列尾部__skb_queue_tail(&sd->input_pkt_queue, skb);//更新队尾指针并保存队列状态input_queue_tail_incr_save(sd, qtail);//释放锁恢复中断rps_unlock_irq_restore(sd, &flags);//入队成功return NET_RX_SUCCESS;}/* Schedule NAPI for backlog device* We can use non atomic operation since we own the queue lock*///NAPI处理if (!__test_and_set_bit(NAPI_STATE_SCHED, &sd->backlog.state))napi_schedule_rps(sd);goto enqueue;}reason = SKB_DROP_REASON_CPU_BACKLOG;......
}
这个函数分为两种情况,分别是将开启RPS和不开启RPS,开启RPS机制时,当接收到网络数据包,RPS 会决定是否将数据包分配给其他 CPU 进行处理。如果是,当前 CPU 会把任务添加到目标 CPU 的 rps_ipi_list
中,并触发软中断。如果没有开启RPS那么就是NAPI轮询调度数据包,调用__napi_schedule_irqoff
函数。
static int napi_schedule_rps(struct softnet_data *sd)
{struct softnet_data *mysd = this_cpu_ptr(&softnet_data);#ifdef CONFIG_RPSif (sd != mysd) {sd->rps_ipi_next = mysd->rps_ipi_list;mysd->rps_ipi_list = sd;__raise_softirq_irqoff(NET_RX_SOFTIRQ);return 1;}#endif /* CONFIG_RPS */__napi_schedule_irqoff(&mysd->backlog);return 0;
}
__napi_schedule_irqoff
主要时调度NAPI任务,处理网络接收的队列,根据开启的内核配置分两种调度场景,开启CONFIG_PREEMPT_RT
采用实时调度策略,调用__napi_schedule
,非实时调度根据当前 CPU 的软中断队列和网络数据结构来进行调度,调用函数____napi_schedule
。
void __napi_schedule_irqoff(struct napi_struct *n)
{//非实时调度if (!IS_ENABLED(CONFIG_PREEMPT_RT))//NAPI调度____napi_schedule(this_cpu_ptr(&softnet_data), n);//实时调度else__napi_schedule(n);
}
EXPORT_SYMBOL(__napi_schedule_irqoff);
input_pkt_queue
是 softnet_data
结构中的一个队列,用于存储接收到的网络数据包(skb
),____napi_schedule
主要完成两件事:
1、将发送的skb添加到softnet_data
的input_pkt_queue
队列;
2、触发软中断
static inline void ____napi_schedule(struct softnet_data *sd,struct napi_struct *napi)
{struct task_struct *thread;lockdep_assert_irqs_disabled();
......//将napi添加到当前CPU的softnet_data结构体的poll_list链表尾部list_add_tail(&napi->poll_list, &sd->poll_list);//触发软中断,类型NET_RX_SOFTIRQ__raise_softirq_irqoff(NET_RX_SOFTIRQ);
}
本机接收过程
本机I/O接收过程主要是驱动部分存在不同,在初始化的时候会设置数据包等待队列的回调函数sd->backlog.poll = process_backlog
,核心是skb_queue_splice_tail_init
函数的调用,将 sd->input_pkt_queue
中的所有元素(即网络数据包)移动到 sd->process_queue
队列的尾部。
- input_pkt_queue队列:接收的网络数据包队列
- process_queue队列:将要被处理的数据包队列
static int process_backlog(struct napi_struct *napi, int quota)
{struct softnet_data *sd = container_of(napi, struct softnet_data, backlog);bool again = true;int work = 0;/* Check if we have pending ipi, its better to send them now,* not waiting net_rx_action() end.*///是否存在等待处理的rps中断if (sd_has_rps_ipi_waiting(sd)) {local_irq_disable();net_rps_action_and_irq_enable(sd);}//读取设备的最大接受数据包的数量napi->weight = READ_ONCE(dev_rx_weight);while (again) {struct sk_buff *skb;//从process_queue队列取出skbwhile ((skb = __skb_dequeue(&sd->process_queue))) {rcu_read_lock();//发送数据包__netif_receive_skb(skb);rcu_read_unlock();//更新数据包,包已处理input_queue_head_incr(sd);if (++work >= quota)return work;}rps_lock_irq_disable(sd);//input_pkt_queue 等待队列空if (skb_queue_empty(&sd->input_pkt_queue)) {/** Inline a custom version of __napi_complete().* only current cpu owns and manipulates this napi,* and NAPI_STATE_SCHED is the only possible flag set* on backlog.* We can use a plain write instead of clear_bit(),* and we dont need an smp_mb() memory barrier.*/napi->state = 0;again = false;} else {//队列合并skb_queue_splice_tail_init(&sd->input_pkt_queue,&sd->process_queue);}rps_unlock_irq_enable(sd);}return work;
}
总结
本机网络I/O并不会经过网卡,Linux 内核在进行路由查找时,优先查询 RT_TABLE_LOCAL
路由表,具体表现如下:
- 如果目标 IP 是本机地址(如
127.0.0.1
或者主机上任意IP
地址),查找RT_TABLE_LOCAL
并匹配RTN_LOCAL
路由类型,数据包的 下一跳(next hop) 指向loopback_dev
(即回环设备)。 - 如果未命中
RT_TABLE_LOCAL
,才会查main
路由表,决定是否通过真实网卡发送。
相关文章:
深入理解Linux网络随笔(五):深度理解本机网络I/O
深入理解Linux网络随笔(五):深度理解本机网络I/O 文章目录 深入理解Linux网络随笔(五):深度理解本机网络I/O本机发送过程本机接收过程总结 分析本机网络I/O部分源码需要知道本机I/O是什么?扮演什…...
Debian12生产环境配置笔记
在 Debian 12 上进行生产环境配置的详细步骤,涵盖软件更新、基础软件安装、Docker 及 Redis 部署,以及 Nginx 配置多个虚拟主机等内容。所有命令均以 root 用户身份执行,无需添加 sudo 1. 更新软件 首先,确保系统上的所有软件包…...
工业物联网的范式革命:从“云边“ 到“边边” 协的技术跃迁
基于DIOS操作系统的去中心化重构 一、云边协同模式的局限性:技术瓶颈与成本困局 当前工业物联网主流的云边协同架构(Cloud-Edge Collaboration)已暴露出显著短板,其核心问题源于对中心云的过度依赖: 带宽资源挤占与…...
python学习笔记--实现简单的爬虫(二)
任务:爬取B站上最爱欢迎的编程课程 网址:编程-哔哩哔哩_bilibili 打开网页的代码模块,如下图: 标题均位于class_"bili-video-card__info--tit"的h3标签中,下面通过代码来实现,需要说明的是URL中…...
【蓝桥杯速成】| 8.回溯算法
因为在进行背包问题的练习时,发现很多题目需要回溯,但本人作为小白当然是啥也不知道 那么就先来补充一下回溯算法的知识点,再进行练习 理论基础 回溯算法本质上是一种递归函数,是纯暴力搜索方法, 适合组合问题、排列…...
聚水潭商品信息集成MySQL的高效解决方案
聚水潭商品信息集成到MySQL的技术案例分享 在数据驱动的业务环境中,如何高效、准确地将聚水潭系统中的商品信息集成到MySQL数据库,是许多企业面临的重要挑战。本文将详细介绍一个实际运行的方案——“聚水潭-商品信息查询-->BI崛起-商品信息表_copy”…...
【数学建模】多目标规划模型:原理、方法与应用
多目标规划模型:原理、方法与应用 文章目录 多目标规划模型:原理、方法与应用引言1. 多目标规划的基本概念1.1 数学模型1.2 Pareto最优解/有效解1.3 满意解方法 2. 多目标规划的主要求解方法2.1 加权求和法2.2 ε-约束法2.3 理想点法2.4 优先级法&#x…...
基于Spring Boot的党员学习交流平台的设计与实现(LW+源码+讲解)
专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/同行前辈交流合作✌。 技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:…...
Flink CDC 与 SeaTunnel CDC 简单对比
Flink CDC 与 SeaTunnel CDC 简单对比 CDC 技术概述 变更数据捕获(Change Data Capture,简称 CDC)是一种用于捕获数据库中数据变更的技术,能够实时识别、捕获并输出数据库中的插入、更新和删除操作。CDC 技术在现代数据架构中扮…...
ARM 汇编基础
ARM 汇编是嵌入式开发、操作系统底层编程和性能优化的核心技能之一。以下是一份系统的 ARM 汇编指令教学指南,涵盖基础语法、核心指令、编程模式和实用示例。 1. ARM 汇编基础 1.1 寄存器 ARM 架构(32位)包含 16 个通用寄存器&…...
【嵌入式狂刷100题】- 1基础知识部分
准备新开专栏【嵌入式狂刷100题】😶🌫️😶🌫️🤧加油!!!,内容包括 基础知识部分操作系统部分处理器架构部分外设驱动部分通信协议部分存储器管理部分硬件设计部分多媒体部分调试故障排除部分编码开发部…...
【模板】计算几何入门
来源 计算几何基本模板(二维) 目录 基本设置点 向量 Point(Vector)点积(数量积、内积)向量积,叉积两点间距离向量的模单位向量两向量的夹角判断点在直线的哪边逆转角 线 直线表达式Line判断…...
PostgreSQL 数据库中导入大量数据
在 PostgreSQL 数据库中导入大量数据,可根据数据来源和格式选择不同的方法。以下为你详细介绍几种常见的方式: 1. 使用 COPY 命令(适用于本地数据文件) COPY 命令是 PostgreSQL 内置的高效数据导入工具,适合处理本地的数据文件。 步骤 准备数据文件 确保你的数据文件格…...
DeepSeek和Kimi在Neo4j中的表现
以下是2个最近爆火的人工智能工具, DeepSeek:DeepSeek Kimi: Kimi - 会推理解析,能深度思考的AI助手 1、提示词: 你能帮我生成一个知识图谱吗,等一下我会给你一篇文章,帮我从内容中提取关键要素,然后以N…...
xQueueSendToBack的中文释义和裸机调用
如果不在 FreeRTOS 环境下运行,而是裸机环境中实现类似的功能,需要移除 xQueueSendToBack 的依赖,并直接调用 CAN 发送函数。以下是修改后的代码和实现思路: 1. FreeRTOS 中的 xQueueSendToBack 功能 作用:将消息发送…...
2025年- G24-Lc98-217.包含重复(使用hashSet解决)-java版
1.题目描述 2.思路 思路一: 我的想法是直接用集合来判断,如果集合的元素不能添加说明之前已经存在这个元素,也就是发现了重复元素,所以返回false。 补充一: Map、ArrayList的定义和声明 3.代码实现 class Soluti…...
【树莓派驱动验证步骤】
终端操作和输出: 清理项目 adaraspberrypi:~/mt3502 $ make clean make -C /lib/modules/6.6.51rpt-rpi-v8/build M/home/ada/mt3502 clean make[1]: 进入目录“/usr/src/linux-headers-6.6.51rpt-rpi-v8”CLEAN /home/ada/mt3502/Module.symvers make[1]: 离开…...
百度SEO和必应SEO优化方法
如需SEO服务,可以搜索:深圳市信科网络科技有限公司。 一、搜索引擎生态格局:流量入口的重新洗牌 2025 年,中国 PC 端搜索引擎市场正经历戏剧性变革。StatCounter 数据显示,必应凭借 Edge 浏览器的预装优势与 ChatGPT …...
2025年3月AI搜索发展动态与趋势分析:从技术革新到生态重构
025年3月AI搜索发展动态与趋势分析:从技术革新到生态重构 一、行业动态:巨头布局与技术升级 谷歌推出“AI模式”,重新定义搜索体验 谷歌上线全新“AI模式”,集成多模态交互与实时数据能力,用户可通过文本、图片或语音…...
封闭图形个数
0封闭图形个数 - 蓝桥云课 小蓝对蓝桥王国的数字大小规则十分感兴趣。现在,他将给定你n个数a1, a2, ..., an,请你按照蓝桥王国的数字大小规则,将这n数从小到大排序,并输出排序后结果。 输入格式 第一行包含一个整数n࿰…...
VSCode 抽风之 两个conda环境同时在被激活
出现了神奇的(toolsZCH)(base) 提示符,如下图所示: 原因大概是:conda 环境的双重激活:可能是 conda 环境没有被正确清理或初始化,导致 base 和 toolsZCH 同时被激活。 解决办法就是 :conda deactivate 两次…...
Django 生产环境静态文件处理
python manage.py collectstatic 是 Django 提供的一个非常重要的管理命令,用于将项目中的静态文件收集到一个指定的目录中。这在部署 Django 项目时尤其重要,因为静态文件需要被 Web 服务器(如 Nginx 或 Apache)提供服务…...
语法: result=frexp(value, exp);
FREXP()是C语言里的内部函数,根据需要了解。 语法: resultfrexp(value, &exp); 参数: value是一个浮点数; exp是一个有符号的整型数; 返回值: 返回值result是一个浮点数,其有效范围是 0.5(含)到 1.0(不含&…...
ArcGIS Pro 制作风台路径图:从数据到可视化
一、引言 台风,作为自然界极具破坏力的气象现象之一,其路径的精准预测与直观呈现对于防灾减灾工作至关重要。 在数字化时代,借助专业的地理信息系统(GIS)软件,如 ArcGIS Pro,我们能够高效地将…...
#pandas #python#数据标注 pd.crosstab()
题目: device_status.txt 存储了工业互联网平台上收集的设备运行状态数据,数 据中有以下内容: device_id:设备编号,字符串类型,长度为 8 status_time:状态时间,日期时间类型&…...
self Attention为何除以根号dk?(全新角度)
全网最独特解析:self Attention为何除根号dk? 一、假设条件:查询向量和键向量服从正态分布 假设查询向量 q i q_i qi和键向量 k j k_j kj的每个分量均为独立同分布的随机变量,且服从标准正态分布,即:…...
SpringBoot @Scheduled注解详解
Scheduled 是 Spring Framework 中用于实现定时任务的核心注解,能够方便地配置方法在特定时间或周期执行。以下是详细解析: 1. 启用定时任务 在 Spring Boot 中,需在配置类添加 EnableScheduling 注解以启用定时任务支持: Co…...
在大数据开发中spark是指什么?
hello宝子们...我们是艾斯视觉擅长ui设计和前端数字孪生、大数据、三维建模、三维动画10年经验!希望我的分享能帮助到您!如需帮助可以评论关注私信我们一起探讨!致敬感谢感恩! 在数字经济蓬勃发展的今天,数据已成为驱动商业决策、科学研究和城市治理的核心燃料。面对…...
从点灯开始的51单片机生活
陵谷纷纭新事改,筑台土石未应迟。 目录 sfr与sbit?不靠定时器的delay_ms延时函数所谓寄存器 sfr与sbit? 这第一课咱们主要来先理解一下sfr与sbit,以下可能是咱们这些新手朋友常见的点灯代码: #include<regx52.h&g…...
AI大模型落地:昆仑技术的东方解法
DeepSeek的横空出世,一举打破“算力封锁”的神话,标志着中国AI企业在AI大模型技术路径上取得彻底突破。 不过,DeepSeek等AI大模型的突破,固然大幅推动AI产业的整体发展,但算力基础设施能否跟上,也将决定未…...
Spring Boot 与 MyBatis Plus 整合 KWDB 实现 JDBC 数据访问
引言 本文主要介绍如何在 IDEA 中搭建一个使用 Maven 管理的 Spring Boot 应用项目工程,并结合在本地搭建的 KWDB 数据库(版本为:2.0.3)来演示 Spring Boot 与 MyBatis Plus 的集成,以及对 KWDB 数据库的数据操作…...
VSCode+arm-none-eabi-gcc交叉编译+CMake构建+OpenOCD(基于STM32的标准库/HAL库)
前言:什么是CMake? Answer:简而言之,CMake是Make的maker。 一、CMake的安装 进入CMake官网的下载地址Get the Software,根据系统安装对应的Binary distributions。 或者在CMake——国内镜像获取二进制镜像安装包。 …...
MarsCode AI实战:利用DeepSeek 快速搭建你的口语学习搭子
资料来源:火山引擎-开发者社区 成品抢先看! 自从MarsCode AI Chat模型全新升级,接入 Deepseek-R1、Deepseek-V3和豆包大模型1.5 三大模型,越来越多朋友注意到了AI编程能给我们带来的无限可能,也开始跃跃欲试想要尝试从…...
导出的使用
一.导出的具体使用步骤 1.在web开发中,导出是很常见的一个功能,当我进行个人项目练习的时候,导出的时候无法控制列宽以及居中样式,后续发现导出插件无法进行修改,整个插件较为简便易懂的同时,对于EX的控制…...
【OCR】总结github上开源 OCR 工具:让文字识别更简单
前言 在数字化的时代,光学字符识别(OCR)技术成为了我们处理文档、图像文字信息的得力助手。它能够将图像中的文字信息转换为可编辑和可处理的文本数据,极大地提高了信息处理的效率。今天,我要给大家介绍一些优秀的开源…...
struts1+struts2项目兼容升级到了spring boot 2.7
原项目比较复杂,集成了各种框架(struts1 struts2 spring3等),趁工作之余练练手,学习一下springboot。大概花了一周时间才调通。 一、调整jar版本,寻找合适的版本。 第一步、首先原项目JDK6,要…...
Odoo 18 中的列表(list) 、表单(Form)、数据透视表、图表视图、看板视图、活动视图、日历视图等综合应用实例
Odoo 18 中的 视图应用实例 在 Odoo 中,视图是用户界面中表示业务对象的重要组成部分。无论您是扩展现有功能还是创建全新的功能,业务对象都至关重要。这些对象通过不同类型的视图向用户展示,而 Odoo 会根据 XML 描述动态生成这些视图。 列…...
单元测试mock
一、背景 现在有A类,B类,C类,A类依赖B类,依赖C类,如果想要测试A类中的某个方法的业务逻辑。A类依赖其他类,则把其他类给mock,然后A类需要真实对象。这样就可以测试A类中的方法。 举例:Ticket类需要调用Flight类和Pas…...
PDF文件转Markdown,基于开源项目marker
首先我们来问下deepseek 为啥要选marker呢 基于深度学习,一看就逼格拉满。搞科研必备,效果应该不会太差。跟其他的阿猫阿狗工具没法比。 看下官网 https://github.com/VikParuchuri/marker 一看头像是个印度佬,自吹——又快又好。…...
mysql中find_in_set()函数用法详解及增强函数
MySQL的 FIND_IN_SET()函数是一种特殊的函数,它主要用于搜索一个字符串在一个逗号分隔的字符串列表中的位置。 函数的基本语法 FIND_IN_SET(str, strlist) 其中,str是你想要查找的字符串,而 strlist是一个包含多个以逗号分隔的字符串的列表…...
深入理解 JavaScript/TypeScript 中的假值(Falsy Values)与逻辑判断 ✨
🕹️ 深入理解 JavaScript/TypeScript 中的假值(Falsy Values)与逻辑判断 在 JavaScript/TypeScript 开发中,if (!value) 是最常见的条件判断之一。它看似简单,却隐藏着语言的核心设计逻辑,也是许多开发者…...
批量合并 PPT 文件,支持合并成单个文件也支持按文件夹合并
合并多个 PPT 为一个 PPT 文档是我们经常会碰到的需求,合并后不仅更容易管理,在某些场景(比如批量打印)下也非常的有用,那当我们需要批量合并多个 PPT 文档地时候,我们有没有比较高效的方法呢?今…...
Java复习
在开篇前首先申明一下,本文虽不够系统,但复习够用,尤其是快速回忆( •̀ ω •́ )✧与提问。 主打一个速度。 本文将会从Java的基础语法、面向对象、API、字符串、集合、进阶...等六方面讲起。 一、Java的基础语法: 1、Java入门…...
keepalived+nginx+tomcat高可用
1.要求 角色主机名软件IP地址用户client192.168.72.90keepalivedvip192.168.72.100mastermasterkeepalived, nginx192.168.72.30backupbackupkeepalived, nginx192.168.72.32webtomcat1tomcat192.168.72.41webtomcat2tomcat192.168.72.42 1.搭建Tomcat 1.1下载jdk wget http…...
RK3568 Android11 sh366006驱动
sh366006.c /* 谁愿压抑心中怒愤冲动咒骂这虚与伪与假从没信要屈膝面对生命纵没有别人帮一生只靠我双手让我放声疯狂叫囔今天的他 呼风可改雨不可一世太嚣张 --《不可一世》Beyond */ #include <linux/module.h> #include <linux/init.h> #include <linux/fs.h…...
实现分布式锁需要考虑哪些问题?
🔒 什么是分布式锁? 分布式锁是在分布式系统中控制共享资源访问的机制,用于解决高并发场景下数据不一致、操作冲突等问题。核心目标是保证跨进程 / 跨节点的互斥性,常见实现方案包括:数据库锁、Redis 锁、ZooKeeper 锁…...
【UI设计】一些好用的免费图标素材网站
阿里巴巴矢量图标库https://www.iconfont.cn/国内最大的矢量图标库之一,拥有 800 万 图标资源。特色功能包括团队协作、多端适配、定制化编辑等,适合企业级项目、电商设计、中文产品开发等场景。IconParkhttps://iconpark.oceanengine.com/home字节跳动…...
mysql-大批量插入数据的三种方式和使用场景
1.批量插入三种方式 INSERT INTO … SELECTINSERT INTO … VALUES (…)LOAD DATA INFILE ‘/path/to/datafile.csv’ INTO TABLE table_name 2.批量插入 2.1 INSERT INTO … SELECT 用途:从另一个表中选择数据并插入到目标表中。 语法示例: INSERT …...
创建自己的github.io
1、创建GitHub账号 GitHub地址:https://github.com/ 点击Sign up创建账号 如果已创建,点击Sign in登录 2、创建仓库 假设Owner为username,则Repository name为username.github.io说明: 1、Owner为用户名 2、Repository name为仓…...
Oracle 常用语法汇总
系列文章目录 本文对Oracle 常用的语法进行汇总 文章目录 系列文章目录一、Oracle 表&表字段操作:1.1 DDL语句(数据定义语言)Create、Alter、Drop、Truncate:1.1.1 建表:建表:注释COMMENT :表中字段的约束:表中字…...