深入理解Linux网络随笔(一):内核是如何接收网络包的(下篇)

3、接收网络数据

3.1.1硬中断处理

数据帧从网线到达网卡时候,首先到达网卡的接收队列,网卡会在初始化时分配给自己的RingBuffer中寻找可用内存位置,寻找成功后将数据帧DMA到网卡关联的内存里,DMA操作完成后,网卡会向CPU发起一个硬中断,通知CPU有数据到达。

在这里插入图片描述

启动网卡到硬中断注册处理函数调用流程ign_open-->igb_request_irq-->igb_request_msix-->igb_msix_ring

static irqreturn_t igb_msix_ring(int irq, void *data)
{
	struct igb_q_vector *q_vector = data;

	/* Write the ITR value calculated from the previous interrupt. */
    //记录硬件中断频率
	igb_write_itr(q_vector);
    //调度NAPI机制
	napi_schedule(&q_vector->napi);

	return IRQ_HANDLED;
}

napi_schedule将q_vector关联的NAPI结构添加于调度队列,函数调用关系napi_schedule-->__napi_schedule-->____napi_schedule

static inline void ____napi_schedule(struct softnet_data *sd,
				     struct napi_struct *napi)
{
	......
	list_add_tail(&napi->poll_list, &sd->poll_list);
	__raise_softirq_irqoff(NET_RX_SOFTIRQ);
}

基于软中断的NAPI处理调用list_add_tail修改Per-CPU变量的softnet_datapoll_list,将驱动napi_struct传入的poll_list添加于软中断的poll_list,触发NET_RX_SOFTIRQ类型软中断。

void __raise_softirq_irqoff(unsigned int nr)
{
	//禁中断
    lockdep_assert_irqs_disabled();
    //追踪软中断
	trace_softirq_raise(nr);
    //触发
	or_softirq_pending(1UL << nr);
}
#define or_softirq_pending(x)  (S390_lowcore.softirq_pending |= (x))

通过or操作符将软中断nr对应的位设置为1,调用or_softirq_pending将该标志位添加于软中断挂起队列,触发软中断。

3.1.2软中断处理

前文分析软中断处理通过ksfortirq内核线程处理,会调用两个函数ksoftirqd_should_runrun_ksoftirqd,均调用local_softirq_pending进行处理。

在这里插入图片描述

#define local_softirq_pending() (S390_lowcore.softirq_pending)
static int ksoftirqd_should_run(unsigned int cpu)
{
	return local_softirq_pending();
}
static void run_ksoftirqd(unsigned int cpu)
{
	ksoftirqd_run_begin();
	if (local_softirq_pending()) {
		/*
		 * We can safely run softirq on inline stack, as we are not deep
		 * in the task stack here.
		 */
		__do_softirq();
		ksoftirqd_run_end();
		cond_resched();
		return;
	}
	ksoftirqd_run_end();
}

硬中断处理是由硬件中断服务例程(ISR)触发的,调用local_softirq_pending标记软中断挂起状态,真正的中断处理由ksfortirq内核线程处理,函数调用逻辑run_ksoftirqd-->__do_softirq

asmlinkage __visible void __softirq_entry __do_softirq(void)
{
    .....
    // 获取当前 CPU 上待处理的软中断类型的掩码
    pending = local_softirq_pending();
    // 遍历所有待处理的软中断
    while ((softirq_bit = ffs(pending))) {
        unsigned int vec_nr;
        int prev_count;
        // 将指针 'h' 移动到当前待处理软中断类型的处理函数
        h += softirq_bit - 1;
        // 计算当前软中断处理函数在软中断处理数组中的索引
        vec_nr = h - softirq_vec;  
        // 获取当前任务的预占用计数
        prev_count = preempt_count();
        // 统计当前软中断类型的处理次数
        kstat_incr_softirqs_this_cpu(vec_nr);
        // 调用 trace 函数跟踪软中断的进入
        trace_softirq_entry(vec_nr);
        // 执行软中断的处理函数
        h->action(h);
        // 调用 trace 函数跟踪软中断的退出
        trace_softirq_exit(vec_nr);
        ......
        // 处理下一个软中断类型
        h++;
        // 右移 pending 位图,检查下一个待处理的软中断
        pending >>= softirq_bit;
    }
}

__do_softirq根据传入的软中断类型处理所有挂起的软中断,通过h->action(h)执行具体的软中断处理函数。硬中断中的设置软中断标记,和ksoftirqd中的判断是否有软中断到达,都是基于smp_processor_id()的。只要硬中断在哪个CPU上被响应,那么软中断也是在这个CPU上处理的,针对Linux软中断消耗集中一个核现象,方法:调整硬中断CPU亲和性,硬中断打散于不同核上。

设备初始化时调用open_softirq(NET_RX_SOFTIRQ, net_rx_action),将网络接收软中断 NET_RX_SOFTIRQ 绑定到 net_rx_action 处理函数,收到软中断类型NET_RX_SOFTIRQ会调用net_rx_action接收软中断,处理网络数据包。

static __latent_entropy void net_rx_action(struct softirq_action *h)
{
    struct softnet_data *sd = this_cpu_ptr(&softnet_data);  // 获取当前 CPU 的软中断数据
    unsigned long time_limit = jiffies +
        usecs_to_jiffies(READ_ONCE(netdev_budget_usecs));  // 计算软中断的超时时间
    int budget = READ_ONCE(netdev_budget);  // 获取软中断的处理预算(每次允许处理的最大数据包数量)
    LIST_HEAD(list);  // 创建链表 list,用于存储要处理的 napi 结构体
    LIST_HEAD(repoll);  // 创建链表 repoll,用于存储需要重新投递的 napi 结构体

    local_irq_disable();  // 关闭CPU硬中断
    list_splice_init(&sd->poll_list, &list);  // 将当前 CPU 上的 poll_list 中的元素移动到 list 中
    local_irq_enable();  // 重新启用本地中断

    for (;;) {
        struct napi_struct *n;

        skb_defer_free_flush(sd);  // 清理延迟释放的数据包

        if (list_empty(&list)) {  // 如果没有要处理的 napi 结构体
            if (!sd_has_rps_ipi_waiting(sd) && list_empty(&repoll))  // 如果没有需要处理的 RPS IPI 和 repoll 列表为空
                goto end;  // 结束处理
            break;  // 如果有需要的工作,继续处理
        }

        n = list_first_entry(&list, struct napi_struct, poll_list);  // 获取待处理的第一个 napi 结构体
        budget -= napi_poll(n, &repoll);  // 调用 napi_poll 处理数据包,更新剩余预算

        /* 如果软中断窗口已耗尽,则退出处理
         * 允许最多运行 2 个 jiffies,这会允许平均延迟为 1.5/HZ
         */
        if (unlikely(budget <= 0 ||
                     time_after_eq(jiffies, time_limit))) {
            sd->time_squeeze++;  // 记录时间压缩(即软中断处理超时)
            break;  // 退出循环
        }
    }

    local_irq_disable();  // 禁用本地中断

    // 将 repoll 和 list 的元素合并到 sd->poll_list 中
    list_splice_tail_init(&sd->poll_list, &list);
    list_splice_tail(&repoll, &list);
    list_splice(&list, &sd->poll_list);

    // 如果 poll_list 中仍有元素,重新唤起软中断
    if (!list_empty(&sd->poll_list))
        __raise_softirq_irqoff(NET_RX_SOFTIRQ);

    net_rps_action_and_irq_enable(sd);  // 处理 RPS(Receive Packet Steering)和启用中断
end:;
}

遍历所有待处理的网络接收软中断,核心获取当前CPU软中断数据softnet_datalist_first_entry遍历poll_list,调用 napi_poll 处理数据包,上文中分析了NAPI机制的poll函数是igb_poll

static int igb_poll(struct napi_struct *napi, int budget)
{
.....
    //TX发送队列
	if (q_vector->tx.ring)
		clean_complete = igb_clean_tx_irq(q_vector, budget);
    //RX接收队列
	if (q_vector->rx.ring) {
		int cleaned = igb_clean_rx_irq(q_vector, budget);
     // 累计本次轮询处理的数据包数量
		work_done += cleaned;
		if (cleaned >= budget)
			clean_complete = false;
	}
......
	return work_done;
}

igb_polligb 网卡驱动中 NAPI 轮询的核心函数,负责清理发送和接收队列的中断。核心处理逻辑igb_clean_rx_irqigb_clean_tx_irq

static int igb_clean_rx_irq(struct igb_q_vector *q_vector, const int budget)
{
	......
    rx_desc = IGB_RX_DESC(rx_ring, rx_ring->next_to_clean);  // 获取当前的接收描述符
    size = le16_to_cpu(rx_desc->wb.upper.length);  // 获取数据包的大小
    rx_buffer = igb_get_rx_buffer(rx_ring, size, &rx_buf_pgcnt);  // 获取接收缓冲区
    pktbuf = page_address(rx_buffer->page) + rx_buffer->page_offset;  // 获取数据包的缓冲区地址
    if (!skb) {
    unsigned char *hard_start = pktbuf - igb_rx_offset(rx_ring);  // 获取数据包的起始地址
    unsigned int offset = pkt_offset + igb_rx_offset(rx_ring);  // 计算数据包的偏移

    xdp_prepare_buff(&xdp, hard_start, offset, size, true);  // 为 XDP 准备数据包
    xdp_buff_clear_frags_flag(&xdp);  // 清除 XDP 的分段标志

    skb = igb_run_xdp(adapter, rx_ring, &xdp);  // 运行 XDP 处理,构建 skb
}
    if (IS_ERR(skb)) {
    unsigned int xdp_res = -PTR_ERR(skb);

    if (xdp_res & (IGB_XDP_TX | IGB_XDP_REDIR)) {  // 如果是 XDP 传输或重定向
        xdp_xmit |= xdp_res;
        igb_rx_buffer_flip(rx_ring, rx_buffer, size);  // 切换缓冲区
    } else {
        rx_buffer->pagecnt_bias++;  // 增加页面计数偏移
    }
    total_packets++;  // 增加数据包计数
    total_bytes += size;  // 增加字节数
}else if (skb) {
    igb_add_rx_frag(rx_ring, rx_buffer, skb, size);  // 将接收到的数据包添加到 skb 中
}
    napi_gro_receive(&q_vector->napi, skb);  // 将 skb 传递给 NAPI 进行进一步处理
    igb_put_rx_buffer(rx_ring, rx_buffer, rx_buf_pgcnt);  // 释放接收缓冲区
    if (cleaned_count)
		igb_alloc_rx_buffers(rx_ring, cleaned_count);
    ......
}

igb_clean_tx_irq核心将数据帧从RingBuffer中摘下,igb_alloc_rx_buffers重新申请新的skb再重新挂起,NAPI机制下一步处理调用napi_gro_receive

gro_result_t napi_gro_receive(struct napi_struct *napi, struct sk_buff *skb)
{
	......
	ret = napi_skb_finish(napi, skb, dev_gro_receive(napi, skb));
	......
	return ret;
}
EXPORT_SYMBOL(napi_gro_receive);

napi_gro_receive用于网卡GRO特性,合并多个小的数据包(通常是同一流的 TCP 数据包)为一个较大的数据包,从而减少协议栈的处理开销。调用napi_skb_finish完成GRO处理。

static gro_result_t napi_skb_finish(struct napi_struct *napi,
				    struct sk_buff *skb,
				    gro_result_t ret)
{
	switch (ret) {
	case GRO_NORMAL:
		gro_normal_one(napi, skb, 1);
		break;

	case GRO_MERGED_FREE:
		if (NAPI_GRO_CB(skb)->free == NAPI_GRO_FREE_STOLEN_HEAD)
			napi_skb_free_stolen_head(skb);
		else if (skb->fclone != SKB_FCLONE_UNAVAILABLE)
			__kfree_skb(skb);
		else
			__kfree_skb_defer(skb);
		break;

	case GRO_HELD:
	case GRO_MERGED:
	case GRO_CONSUMED:
		break;
	}

	return ret;
}

正常数据包处理GRO_NORMAL,需要合并的数据包处理GRO_MERGED_FREE,根据不同的方式选择不同的释放方式,函数调用逻辑gro_normal_one-->gro_normal_list-->netif_receive_skb_list_internal-->__netif_receive_skb_list-->__netif_receive_skb_core,数据包发送于协议栈。

3.1.3网络协议栈处理
static int __netif_receive_skb_core(struct sk_buff **pskb, bool pfmemalloc,
				    struct packet_type **ppt_prev)
{
    ......
    // 遍历全局的协议类型链表并传递 skb,tcpdump入口
    list_for_each_entry_rcu(ptype, &ptype_all, list) {
        if (pt_prev)
            ret = deliver_skb(skb, pt_prev, orig_dev);
        pt_prev = ptype;
    }
    type = skb->protocol;
    
	// 如果没有精确匹配,处理协议类型
	if (likely(!deliver_exact)) {
    deliver_ptype_list_skb(skb, &pt_prev, orig_dev, type,
                           &ptype_base[ntohs(type) & PTYPE_HASH_MASK]);
}

	// 传递到设备特定协议链表
	deliver_ptype_list_skb(skb, &pt_prev, orig_dev, type,
                       &orig_dev->ptype_specific);

	// 如果 skb 的设备不是原始设备,进行协议处理
	if (unlikely(skb->dev != orig_dev)) {
    deliver_ptype_list_skb(skb, &pt_prev, orig_dev, type,
                           &skb->dev->ptype_specific);
	}  
}

ptype_all 是一个全局链表,包含了所有已注册的协议类型及其处理回调,在 __netif_receive_skb_core 函数中,首先会遍历这个链表,依次处理每个协议类型,并调用与协议相关的处理函数,ptype_base 是一个基于协议类型的哈希表,它包含了协议类型(如 IPv4、IPv6、TCP 等)对应的特定处理函数,当数据包的协议类型与哈希表中的某个条目匹配时,数据包会被传递到该处理函数。例如,ip_rcv 的地址通常是保存在 ptype_base 哈希表中的。

static inline int deliver_skb(struct sk_buff *skb,
			      struct packet_type *pt_prev,
			      struct net_device *orig_dev)
{
	......
    //调用协议处理函数 (pt_prev->func) 并将 skb 传递给它
	return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
}
static struct packet_type ip_packet_type __read_mostly = {
    .type = cpu_to_be16(ETH_P_IP),
    .func = ip_rcv,
};

deliver_skb 将接收到的 skb传递给协议处理函数。从 packet_type 结构体中获取 func 字段,并将 skb 数据包传递给该函数进行处理。这里的 pt_prev->func 是一个协议回调函数,指向处理该协议类型数据包的函数(例如,对于 IPv4 数据包,func 指向 ip_rcv 函数)。

3.1.4IP层处理

数据包经过协议栈处理后会被传递到IP层进行处理,收包方向IP层入口函数ip_rcv

int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt,
	   struct net_device *orig_dev)
{
	struct net *net = dev_net(dev);

	skb = ip_rcv_core(skb, net);
	if (skb == NULL)
		return NET_RX_DROP;

	return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING,
		       net, NULL, skb, dev, NULL,
		       ip_rcv_finish);
}

接收到的数据包会经过ip_rcv_core进行基本处理,例如对数据包进行有效性检查、协议解析等,处理成功会触发IPV4数据包Netfilter钩子链,在 NF_INET_PRE_ROUTING 钩子处插入数据包处理,NF_INET_PRE_ROUTING 是所有接收数据包到达的第一个 hook 触发点,在路由判断之前执行,对应的回调函数ip_rcv_finish

static int ip_rcv_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
{
	struct net_device *dev = skb->dev;
	int ret;

	/* if ingress device is enslaved to an L3 master device pass the
	 * skb to its handler for processing
	 */
	skb = l3mdev_ip_rcv(skb);
	if (!skb)
		return NET_RX_SUCCESS;

	ret = ip_rcv_finish_core(net, sk, skb, dev, NULL);
	if (ret != NET_RX_DROP)
		ret = dst_input(skb);
	return ret;
}

ip_rcv_finish负责接收和处理通过 IPv4 协议栈传输的网络数据包,核心的IP数据包处理函数调用dst_input传递数据包。

static inline int dst_input(struct sk_buff *skb)
{
	return INDIRECT_CALL_INET(skb_dst(skb)->input,
				  ip6_input, ip_local_deliver, skb);
}

基于路由类型skb_dst(skb)选择对应的处理函数,通过INDIRECT_CALL_INET宏选择IPV4/IPV6协议数据包处理函数,IPV4选择调用ip_local_deliver

int ip_local_deliver(struct sk_buff *skb)
{
	/*
	 *	Reassemble IP fragments.
	 */
	struct net *net = dev_net(skb->dev);

	if (ip_is_fragment(ip_hdr(skb))) {
		if (ip_defrag(net, skb, IP_DEFRAG_LOCAL_DELIVER))
			return 0;
	}

	return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN,
		       net, NULL, skb, skb->dev, NULL,
		       ip_local_deliver_finish);
}
EXPORT_SYMBOL(ip_local_deliver);

ip_local_deliver处理本地IPV4数据包,接收一个网络数据包skb,调用ip_is_fragment检查是否需要进行IP分片重组,对不分片/已重组的数据包调用NF_HOOKNF_INET_LOCAL_IN处理本地接收到的数据包(并不是经过路由转发的数据包),对应的处理函数ip_local_deliver_finish

参考资料:《深入理解Linux网络》

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

新版电脑通过wepe安装系统

官方下载链接 WIN10下载 WIN11下载 微PE 启动盘制作 1&#xff1a;选择启动盘的设备 2&#xff1a;选择对应的U盘设备&#xff0c;点击安装就可以&#xff0c;建议大于8g 3&#xff1a;在上方链接下载需要安装的程序包&#xff0c;放入启动盘&#xff0c;按需 更新系统 …

蓝桥杯之KMP算法

算法思想 代码实现 int* getnext() {int* next new int[s2.size()];int j 0;//用来遍历子串int k -1;//子串中公共子串的长度next[0] -1;while (j < s2.size() - 1){if (k-1||s2[k] s2[j]){k;j;if (s2[k] s2[j]){next[j] next[k];}else{next[j] k;}}else{k next[k…

jsp页面跳转失败

今天解决一下jsp页面跳转失败的问题 在JavaWeb的学习过程中&#xff0c;编写了这样一段代码&#xff1a; <html> <body> <h2>Hello World!</h2><%--这里提交的路径&#xff0c;需要寻找到项目的路径--%> <%--${pageContext.request.context…

如何实现对 ELK 各组件的监控?试试 Metricbea

上一章基于 Filebeat 的日志收集使用Filebeat收集文件中的日志&#xff0c;而Metricbeat则是收集服务器存活性监测和系统指标的指标。 1. Filebeat和Metricbeat的区别 特性FilebeatHeartbeat作用收集和转发日志监测服务可用性数据来源服务器上的日志文件远程主机、API、服务主…

DeepSeek-VL2 环境配置与使用指南

DeepSeek-VL2 环境配置与使用指南 DeepSeek-VL2 是由 DeepSeek 公司开发的一种高性能视觉-语言模型&#xff08;VLM&#xff09;。它是 DeepSeek 系列多模态模型中的一个版本&#xff0c;专注于提升图像和文本之间的交互能力。 本文将详细介绍如何配置 DeepSeek-VL2 的运行环…

Golang的并发编程问题解决思路

Golang的并发编程问题解决思路 一、并发编程基础 并发与并行 在计算机领域&#xff0c;“并发”和“并行”经常被混为一谈&#xff0c;但它们有着不同的含义。并发是指一段时间内执行多个任务&#xff0c;而并行是指同时执行多个任务。在 Golang 中&#xff0c;通过 goroutines…

多能互补综合能源系统,改变能源结构---安科瑞 吴雅芳

多能互补综合能源系统是一种通过整合多种能源的形势&#xff08;如电力、天然气、热能、冷能等&#xff09;和多种能源技术&#xff08;如可再生能源、储能技术、智能电网等&#xff09;&#xff0c;实现能源利用和配置调整的系统。其目标是通过多能互补和协同优化&#xff0c;…

Linux部署DeepSeek r1 模型训练

之前写过一篇windows下部署deepseekR1的文章&#xff0c;有小伙伴反馈提供一篇linux下部署DeepSeek r1 模型训练教程&#xff0c;在 Linux 环境下&#xff0c;我找了足够的相关资料&#xff0c;花费了一些时间&#xff0c;我成功部署了 DeepSeek R1 模型训练任务&#xff0c;结…

使用pyCharm创建Django项目

使用pyCharm创建Django项目 1. 创建Django项目虚拟环境&#xff08;最新版版本的Django) 使用pyCharm的创建项目功能&#xff0c;选择Django,直接创建。 2. 创建Django项目虚拟环境&#xff08;安装特定版本&#xff09; 2.1创建一个基础的python项目 2.2 安装指定版本的D…

基于vue3实现的课堂点名程序

设计思路 采用vue3实现的课堂点名程序&#xff0c;模拟课堂座位布局&#xff0c;点击开始点名按钮后&#xff0c;一朵鲜花在座位间传递&#xff0c;直到点击结束点名按钮&#xff0c;鲜花停留的座位被点名。 课堂点名 座位组件 seat.vue <script setup>//组合式APIimpo…

Springboot 中如何使用Sentinel

在 Spring Boot 中使用 Sentinel 非常方便&#xff0c;Spring Cloud Alibaba 提供了 spring-cloud-starter-alibaba-sentinel 组件&#xff0c;可以快速将 Sentinel 集成到你的 Spring Boot 应用中&#xff0c;并利用其强大的流量控制和容错能力。 下面是一个详细的步骤指南 …

IoTDB 导入数据时提示内存不足如何处理

问题现象 IoTDB 导入数据时提示内存不足&#xff0c;该如何处理&#xff1f; 解决方案 数据导入脚本会在触发内存不足的时候主动进行重试。当遇到此问题时&#xff0c;用户不用做任何操作&#xff0c;脚本也可以正确进行处理。如果想从根源减少此类提示&#xff0c;可以按照下…

计算机视觉中图像的基础认知

一、图像/视频的基本属性 在计算机视觉中&#xff0c;图像和视频的本质是多维数值矩阵。图像或视频数据的一些基本属性。 宽度&#xff08;W&#xff09; 和 高度&#xff08;H&#xff09; 定义了图像的像素分辨率&#xff0c;单位通常是像素。例如&#xff0c;一张 1920x10…

【React】组件通信

组件通信 父传子 - props function Article(props) {return (<div><h2>{props.title}</h2><p>{props.content}</p><p>状态&#xff1a; {props.active ? 显示 : 隐藏}</p></div>) } // 设置默认值方式一 // 使用 defaultPr…

Tomcat添加到Windows系统服务中,服务名称带空格

要将Tomcat添加到Windows系统服务中&#xff0c;可以通过Tomcat安装目录中“\bin\service.bat”来完成&#xff0c;如果目录中没有service.bat&#xff0c;则需要使用其它方法。 打到CMD命令行窗口&#xff0c;通过cd命令跳转到Tomcat安装目录的“\bin\”目录&#xff0c;然后执…

基于Python深度学习的【蘑菇识别】系统~卷积神经网络+TensorFlow+图像识别+人工智能

一、介绍 蘑菇识别系统&#xff0c;本系统使用Python作为主要开发语言&#xff0c;基于TensorFlow搭建卷积神经网络算法&#xff0c;并收集了9种常见的蘑菇种类数据集【“香菇&#xff08;Agaricus&#xff09;”, “毒鹅膏菌&#xff08;Amanita&#xff09;”, “牛肝菌&…

124 巨坑uni-app踩坑事件 uniCloud本地调试服务启动失败

1.事情是这样的 事情是这样的&#xff0c;我上午在运行项目的时候还是好好的&#xff0c;我什么都没干&#xff0c;没动代码&#xff0c;没更新&#xff0c;就啥也没干&#xff0c;代码我也还原成好好的之前的样子&#xff0c;就报这个错&#xff0c;但是我之前没用过这个服务呀…

Android Studio “Sync project with Gradle Files”按钮消失——文件层级打开不对

问题出现的背景 Android Studio显示&#xff0c;后来查找解决方案&#xff0c;里面提到“Sync project with Gradle Files”按钮&#xff0c;一检查发现自己的软件上面没有这个选项&#xff0c;于是参考 https://debugah.com/android-studio-can-not-find-sync-project-with-g…

什么是HTTP Error 429以及如何修复

为了有效管理服务器资源并确保所有用户都可以访问&#xff0c;主机提供商一般都会对主机的请求发送速度上做限制&#xff0c;一旦用户在规定时间内向服务器发送的请求超过了允许的限额&#xff0c;就可能会出现429错误。 例如&#xff0c;一个API允许每个用户每小时发送100个请…

LAWS是典型的人机环境系统

致命性自主武器系统&#xff08;Lethal Autonomous Weapons Systems&#xff0c;LAWS&#xff09;是一种典型的人机环境系统&#xff0c;它通过高度集成的传感器、算法和武器平台&#xff0c;在复杂的战场环境中自主执行任务。LAWS能够自主感知环境、识别目标、做出决策并实施攻…