Linux内核USB3.0驱动框架分析--USB Hub代码分析

一、Linux 下USB Hub热插拔处理

1.1 Linux下USB HUB的驱动的实现和分析:
USB设备是热插拔,因此在hub_probe函数中调用hub_configure函数来配置hub,在这个函数中主要是利用函数usb_alloc_urb函数来分配一个urb,利用usb_fill_int_urb来初始化这个urb结构,包括hub的中断服务程序hub_irq的,查询的周期等。
每当有设备连接到USB接口时,USB总线在查询hub状态信息的时候会触发hub的中断服务程序hub_irq, 在该函数中置位event_bits,运行工作队列。进入hub_event函数,该函数用来处理端口变化的事件。然后通过一个for循环来检测每个端口的状态信息。利用usb_port_status获取端口信息,如果发生变化就调用hub_port_connect_change函数来配置端口等。

在系统初始化的时候在usb_init函数中调用usb_hub_init函数,就进入了hub的初始化。

代码路径:drivers\usb\core\hub.c

在usb_hub_init函数中完成了注册hub驱动,并且利用函数alloc_workqueue创建一个工作队列。

1.2 初始化
这里我们主要分析khub的工作原理: 硬件层次是hub的工作,如何和host及其设备间通信及相应事件
hub的驱动定义为hub_driver,也是一个usb_driver结构体。hub驱动的匹配方式由hub_id_table定义,可以根据PID、VID、接口类型匹配。
[usb/core/hub.c ]

int usb_hub_init(void)
{if (usb_register(&hub_driver) < 0) { //注册usb HUB设备printk(KERN_ERR "%s: can't register hub driver\n",usbcore_name);return -1;}/** The workqueue needs to be freezable to avoid interfering with* USB-PERSIST port handover. Otherwise it might see that a full-speed* device was gone before the EHCI controller had handed its port* over to the companion full-speed controller.*//* 工作队列需要是可冻结的,以避免干扰usb持续的端口切换。否则它可能会看到全速设备在EHCI控制器把端口交给全速控制器之前就消失了 */hub_wq = alloc_workqueue("usb_hub_wq", WQ_FREEZABLE, 0);  //创建工作队列if (hub_wq)return 0;
}
[drivers/usb/core/hub.c]
static const struct usb_device_id hub_id_table[] = {{ .match_flags = USB_DEVICE_ID_MATCH_VENDOR| USB_DEVICE_ID_MATCH_PRODUCT| USB_DEVICE_ID_MATCH_INT_CLASS,.idVendor = USB_VENDOR_SMSC,.idProduct = USB_PRODUCT_USB5534B,.bInterfaceClass = USB_CLASS_HUB,.driver_info = HUB_QUIRK_DISABLE_AUTOSUSPEND},{ .match_flags = USB_DEVICE_ID_MATCH_VENDOR| USB_DEVICE_ID_MATCH_PRODUCT,.idVendor = USB_VENDOR_CYPRESS,.idProduct = USB_PRODUCT_CY7C65632,.driver_info = HUB_QUIRK_DISABLE_AUTOSUSPEND},{ .match_flags = USB_DEVICE_ID_MATCH_VENDOR| USB_DEVICE_ID_MATCH_INT_CLASS,.idVendor = USB_VENDOR_GENESYS_LOGIC,.bInterfaceClass = USB_CLASS_HUB,.driver_info = HUB_QUIRK_CHECK_PORT_AUTOSUSPEND},{ .match_flags = USB_DEVICE_ID_MATCH_DEV_CLASS,.bDeviceClass = USB_CLASS_HUB},{ .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS,.bInterfaceClass = USB_CLASS_HUB},{ }						/* Terminating entry */
};MODULE_DEVICE_TABLE(usb, hub_id_table);
static struct usb_driver hub_driver = {.name                 =	    "hub",.probe                =	    hub_probe,.disconnect           =	    hub_disconnect,.suspend              =	    hub_suspend,.resume               =	    hub_resume,.reset_resume         =		hub_reset_resume,.pre_reset            =		hub_pre_reset,.post_reset           =		hub_post_reset,.unlocked_ioctl       = 	hub_ioctl,.id_table             =		hub_id_table,.supports_autosuspend =		1,
};

hub驱动匹配和初始化过程如下图所示,主要的工作流程有:

当注册root hub时,会调用device_add注册设备。内核调用usb_hub_init函数初始化hub,此时会调用usb_register_driver注册hub驱动hub_driver。hub设备或驱动注册时都会调用usb_device_match匹配到对方,若匹配成功,则hub_probe函数被调用,进入hub的初始化流程。
hub_probe函数中做了两件事,第一件是初始化轮询hub所用的工作队列和定时器,hub_event比较重要,用于处理USB设备连接、断开、低功耗模式等,后续会介绍,第二件是配置hub,具体的工作如下:
获取hub描述符,hub有专用的描述。
初始化用于事务分割的工作队列。主要是将USB2.0事物转换成USB1.0或USB1.1。
通过请求获取hub的信息和状态。
分配hub中断传输的urb,用以查询hub状态。
填充hub中断传输的urb,回调函数为hub_irq。
给hub的每个port创建usb_port数据结构。
使能hub,主要是给port上电,检查port状态、提交之前设置的中断传输urb,用于查询hub的状态;最后调度处理hub事件的工作队列,当有事件发生时会调用hub_event处理。

然后进入hub的probe函数,主要是一些工作的初始化和hub的配置,我这里化简了

static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
{hub = kzalloc(sizeof(*hub), GFP_KERNEL); //分配usb_hub结构体if (!hub)return -ENOMEM;INIT_DELAYED_WORK(&hub->leds, led_work); //用于hub led闪烁的指示灯INIT_DELAYED_WORK(&hub->init_work, NULL);INIT_WORK(&hub->events, hub_event); //用于处理hub的事件if (hub_configure(hub, &desc->endpoint[0].desc) >= 0) //设置hub的端点0return 0;
}

hub_configure配置hub,包括不同的hub的判断和配置,但Linux认为最多只能31个接口,里面比较复杂,这里也化简了

static int hub_configure(struct usb_hub *hub,struct usb_endpoint_descriptor *endpoint)
{ret = hub_hub_status(hub, &hubstatus, &hubchange); //获取hub的状态,主要是通过usb_control_msg进行通信hub->urb = usb_alloc_urb(0, GFP_KERNEL); //分配urb/* UHCI必须要知道HUB的端口的一些连接状态,因此,需要HUB周期性的上报它的端口连接状态.这个URB就是用来做这个用途的.UHCI周期性的发送IN方向中断传输传输给HUB.HUB就会通过这个URB将端口信息发送给UHCI.那这个轮询周期是多长呢?根据我们之前分析的UHCI的知识,它的调度周期是由endpoint的bInterval 字段所决定的.*/usb_fill_int_urb(hub->urb, hdev, pipe, *hub->buffer, maxp, hub_irq,hub, endpoint->bInterval); //填充urb,完成之后调用hub_irq函数for (i = 0; i < maxchild; i++) {ret = usb_hub_create_port_device(hub, i + 1); //创建hub的端点设备,比如/sys/devices/platform/soc@0/38100000.usb/xhci-hcd.0.auto/usb1/1-0:1.0/usb1-port1}}hub_activate(hub, HUB_INIT);return 0;
}

继续分析hub_activate,主要是启动hub,我们这里传入的参数是HUB_INIT

static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
{/* Continue a partial initialization */if (type == HUB_INIT2 || type == HUB_INIT3) {device_lock(&hdev->dev);/* Was the hub disconnected while we were waiting? */if (hub->disconnected)goto disconnected;if (type == HUB_INIT2)goto init2;goto init3;}if (type == HUB_INIT) {delay = hub_power_on_good_delay(hub); //上电后延时,使hub稳定hub_power_on(hub, false); //对所有的端点上电,usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0), USB_REQ_SET_FEATURE, USB_RT_PORT, feature, port1, NULL, 0, 1000);INIT_DELAYED_WORK(&hub->init_work, hub_init_func2);queue_delayed_work(system_power_efficient_wq,&hub->init_work,msecs_to_jiffies(delay)); //进入初始化的第二阶段hub_init_func2,也就是下面的 init2:init2:/** Check each port and set hub->change_bits to let hub_wq know* which ports need attention.*/for (port1 = 1; port1 <= hdev->maxchild; ++port1) {struct usb_port *port_dev = hub->ports[port1 - 1];struct usb_device *udev = port_dev->child;u16 portstatus, portchange;portstatus = portchange = 0;status = hub_port_status(hub, port1, &portstatus, &portchange); //获取端口的状态,..........//后面进行一系列判断,包括usb2.0,3.0等。并且set_bit(port1, hub->change_bits);/* If no port-status-change flags were set, we don't need any* debouncing.  If flags were set we can try to debounce the* ports all at once right now, instead of letting hub_wq do them* one at a time later on.** If any port-status changes do occur during this delay, hub_wq* will see them later and handle them normally.*/if (need_debounce_delay) { //用于消抖delay = HUB_DEBOUNCE_STABLE;/* Don't do a long sleep inside a workqueue routine */if (type == HUB_INIT2) {INIT_DELAYED_WORK(&hub->init_work, hub_init_func3); //进行第三个阶段init3:queue_delayed_work(system_power_efficient_wq,&hub->init_work,msecs_to_jiffies(delay));device_unlock(&hdev->dev);return;        /* Continues at init3: below */}init3:status = usb_submit_urb(hub->urb, GFP_NOIO); //提交urb,等执行完成就会回调hub_irqif (status < 0)dev_err(hub->intfdev, "activate --> %d\n", status);if (hub->has_indicators && blinkenlights) //如果有指示灯,点亮queue_delayed_work(system_power_efficient_wq,&hub->leds, LED_CYCLE_PERIOD);/* Scan all ports that need attention */kick_hub_wq(hub); //主要是queue_work(hub_wq, &hub->events),也就是把hub_event加入工作队列,开始运行
}

我们来看看hub_event,前面int2的时候有设置hub->change_bits,这里会进行处理

static void hub_event(struct work_struct *work)
{if (hub->error) {dev_dbg(hub_dev, "resetting for error %d\n", hub->error);ret = usb_reset_device(hdev); //警告接口驱动程序并执行USB端口重置}/* deal with port status changes */for (i = 1; i <= hdev->maxchild; i++) {struct usb_port *port_dev = hub->ports[i - 1];if (test_bit(i, hub->event_bits)|| test_bit(i, hub->change_bits)|| test_bit(i, hub->wakeup_bits)) { //如果这几个条件都满足,就port_event/** The get_noresume and barrier ensure that if* the port was in the process of resuming, we* flush that work and keep the port active for* the duration of the port_event().  However,* if the port is runtime pm suspended* (powered-off), we leave it in that state, run* an abbreviated port_event(), and move on.*/pm_runtime_get_noresume(&port_dev->dev);pm_runtime_barrier(&port_dev->dev);usb_lock_port(port_dev);port_event(hub, i); //处理事件usb_unlock_port(port_dev);pm_runtime_put_sync(&port_dev->dev);}}
}

我们再看看 port_event做了什么。
来看一下,什么情况下, hub_port_connect_change才会被设为1.
1:端口在hub->change_bits中被置位.搜索整个代码树,发生在设置hub->change_bits的地方,只有在hub_port_logical_disconnect()中手动将端口禁用,会将对应位置1.
2:hub上没有这个设备树上没有这个端口上的设备.但显示端口已经连上了设备
3:hub这个端口上的连接发生了改变,从端口有设备连接变为无设备连接,或者从无设备连接变为有设备连接.
4:hub的端口变为了disable,此时这个端口上连接了设备,但被显示该端口已经变禁用,需要将connect_change设为1.
5:端口状态从SUSPEND变成了RESUME,远程唤醒端口上的设备失败,就需要将connect_change设为1.

static void port_event(struct usb_hub *hub, int port1)__must_hold(&port_dev->status_lock)
{if (hub_port_status(hub, port1, &portstatus, &portchange) < 0) //确认端口改变了return;if (connect_change)hub_port_connect_change(hub, port1, portstatus, portchange); //处理端口改变的情况
}

调用hub_port_connect_change再调用hub_port_connect

static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus,u16 portchange)
{/* Disconnect any existing devices under this port */if (udev) {if (hcd->usb_phy && !hdev->parent) //断开该端口设备的连接:如果是root hub,挂接在控制器上的,则断开该端口下的设备。usb_phy_notify_disconnect(hcd->usb_phy, udev->speed);usb_disconnect(&port_dev->child);}for (i = 0; i < SET_CONFIG_TRIES; i++) {/* reallocate for each attempt, since references* to the previous one can escape in various ways*/// 分配usb设备内存并初始化bus、type、group、设备在系统中的路径(dev->path)、ep0的属性并设置设备状态为attached。udev = usb_alloc_dev(hdev, hdev->bus, port1);if (!udev) {dev_err(&port_dev->dev,"couldn't allocate usb_device\n");goto done;}usb_set_device_state(udev, USB_STATE_POWERED); //设置为 power状态,并设置电源等。udev->bus_mA = hub->mA_per_port;udev->level = hdev->level + 1;udev->wusb = hub_is_wusb(hub);/* Devices connected to SuperSpeed hubs are USB 3.0 or later */if (hub_is_superspeed(hub->hdev)) //判断速度是否为高速udev->speed = USB_SPEED_SUPER;elseudev->speed = USB_SPEED_UNKNOWN;choose_devnum(udev); //获取设备号,在usbfs中,设备号被用作文件名.if (udev->devnum <= 0) {status = -ENOTCONN;    /* Don't retry */goto loop;}/* reset (non-USB 3.0 devices) and get descriptor */usb_lock_port(port_dev);status = hub_port_init(hub, udev, port1, i); //初始化设备,设置地址,读取设备描述符usb_unlock_port(port_dev);/* Run it through the hoops (find a driver, etc) */if (!status) {status = usb_new_device(udev); //执行初始设备设置if (status) {mutex_lock(&usb_port_peer_mutex);spin_lock_irq(&device_state_lock);port_dev->child = NULL;spin_unlock_irq(&device_state_lock);mutex_unlock(&usb_port_peer_mutex);} else {if (hcd->usb_phy && !hdev->parent)usb_phy_notify_connect(hcd->usb_phy,udev->speed);}}}
}

1.3 初始化流程图
在这里插入图片描述
二,枚举设备

2.1 Hub部分热拔插
前面主要是初始化的操作,也会去识别开机的时候已经插入的设备,如果是开机之后插入的设备,又是什么流程呢

前面是每当有设备连接到USB接口时,USB总线在查询hub状态信息的时候会触发hub的中断服务程序hub_irq

static void hub_irq(struct urb *urb)
{switch (status) {case -ENOENT:        /* synchronous unlink */case -ECONNRESET:    /* async unlink */case -ESHUTDOWN:    /* hardware going away */return;default:        /* presumably an error *//* Cause a hub reset after 10 consecutive errors */dev_dbg(hub->intfdev, "transfer --> %d\n", status);if ((++hub->nerrors < 10) || hub->error)goto resubmit;hub->error = status;/* FALL THROUGH *//* let hub_wq handle things */case 0:            /* we got data:  port status changed */bits = 0;for (i = 0; i < urb->actual_length; ++i)bits |= ((unsigned long) ((*hub->buffer)[i]))<< (i*8);hub->event_bits[0] = bits; //置位event_bits /* status change bitmask */break;}hub->nerrors = 0;/* Something happened, let hub_wq figure it out */kick_hub_wq(hub); //}

kick_hub_wq主要是queue_work(hub_wq, &hub->events),也就是把hub_event加入工作队列,开始运行,前面有分析了。
我们重点分析一下usb_new_device,初始化设备

int usb_new_device(struct usb_device *udev)
{err = usb_enumerate_device(udev);    /* Read descriptors */ //读取描述符/* Tell the world! */announce_device(udev); //打印出来,需要内核打开CONFIG_USB_ANNOUNCE_NEW_DEVICES宏/* Register the device.  The device driver is responsible* for configuring the device and invoking the add-device* notifier chain (used by usbfs and possibly others).*/err = device_add(&udev->dev); //将设备添加到设备层次结构中}

USB主机通过hub感知USB设备的连接、断开、状态变化等事件。当事件发生会产生xHCI中断,在中断处理函数中处理USB设备事件,具体的流程如下:

产生中断后会最终调用到handle_port_status函数处理hub port口事件,没事件发生则直接退出,有事件发生,且status_urb空闲,则走下面的流程。
设置HCD_FLAG_POLL_RH标志,调用usb_hcd_poll_rh_status函数开始轮询hub状态。
调用xHCI驱动的xhci_hub_status_data函数查询root hub每个port的状态,若有变化,则会反汇事件数据的长度。port的状态由port_开头的宏定义定义。
清除轮询hub的标志,将查询hub状态的status_urb从对应的端点队中移除,然后调用usb_hcd_giveback_urb,最终通过调度tasklet处理hub事件。详细的处理过程后续介绍。
当status_urb被占用时,即hcd->status_urb为NULL时,说明上一个hub事件还没处理完(tasklet没处理完),因此会设置HCD_FLAG_POLL_RH标志,调用mod_timer开启定时器,定时器到期后调用rh_timer_func处理hub事件,处理流程和上面一样。
在这里插入图片描述
三,总结

在众多事件中,枚举USB设备是最重要的,下图描述了USB主机枚举USB设备的过程。主要的工作有:

通过usb_hcd_poll_rh_status函数轮询到了hub的port上有事件发生,最终通过调用hub_irq函数处理这些事件。
hub_irq函数做了两件事,第一件是重新提交查询hub状态的urb,即设置hcd->status_urb = urb,当下一次处理hub事件时,又会调用到hub_irq;第二件事是调度工作队列处理hub事件。
hub事件通过hub_event函数处理,该函数会遍历每个port,当port上有USB设备连接时,调用hub_port_connect处理。
首先为设备分配usb_device数据结构,接着设置USB设备号,然后初始化设备,包括复位设备并获取设备速度、设置设备地址、获取设备描述符。
枚举USB设备和匹配USB设备驱动在usb_new_device函数中完成,主要的工作如下:
获取USB设备的配置、接口、端点等描述符,若开启了相关选项,则内核会打印USB设备的详细信息。
通过usb_device_match匹配设备驱动,即usb_device_type类型,最终会调用到内核提供的通用的USB设备驱动usb_generic_driver_probe。
在通用的USB设备驱动中,首先会选择配置,某些USB设备有多个配置,因此需要选择其中一个用于工作。
接着设置配置,包括为USB设备分配带宽、设置配置等。
最后遍历USB设备的所有接口,调用usb_device_match为接口匹配驱动(USB驱动和USB接口对应,此时的匹配类型为usb_if_device_type),匹配成功后接口驱动的probe函数被调用。
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

金九银十软件测试面试题(800道)

今年你的目标是拿下大厂offer&#xff1f;还是多少万年薪&#xff1f;其实这些都离不开日积月累的过程。 为此我特意整理出一份&#xff08;超详细笔记/面试题&#xff09;它几乎涵盖了所有的测试开发技术栈&#xff0c;非常珍贵&#xff0c;人手一份 肝完进大厂 妥妥的&#…

【Linux】操作系统基础

1.冯诺依曼体系结构介绍 冯诺依曼体系结构如下&#xff1a; 在上图中「输⼊设备」和「输出设备」⼀般被称为计算机的外设&#xff0c;⽽「存储器」在冯 诺依曼体系结构中表示「内存」 输⼊设备⼀般包括&#xff1a;⽹卡、磁盘、键盘、触摸屏等 输出设备⼀般包括&#xff1a;…

java 自定义填充excel并导出

首先在resources下面放一个excel模板 1. 方法签名和请求映射 RequestMapping(value "/ExportXls") public ResponseEntity<byte[]> rwzcExportXls(HttpServletRequest request, RequestBody JSONArray jsonArray) throws IOException { RequestMapping(val…

剧场的客户端形式区别,APP,小程序,H5的不同优势以及推广方案

剧场的客户端形式区别与推广策略 在数字化时代&#xff0c;剧场的线上化成为大势所趋。不同的线上平台如APP、小程序和H5各有千秋&#xff0c;如何选择最适合自己的平台&#xff0c;并制定有效的推广方案&#xff0c;成为了剧场管理者需要考虑的重要问题。 APP&#xff1a;深度…

【ONE·Web || HTML】

总言 主要内容&#xff1a;HTML基本知识入门&#xff0c;主要介绍了常见的一些标签使用&#xff0c;以及简单案例演示。       文章目录 总言0、前置说明1、认识HTML1.1、是什么1.2、初识 HTML 标签、HTML 文件基本结构1.2.1、相关说明1.2.2、vscode如何快速生成代码 2、HT…

污水排放口细粒度检测数据集,污-水排放口的类型包括10类目标,10000余张图像,yolo格式目标检测,9GB数据量。

污水排放口细粒度检测数据集&#xff0c;污-水排放口的类型包括10类目标&#xff08;1 合流下水道&#xff0c;2 雨水&#xff0c;3 工业废水&#xff0c;4 农业排水&#xff0c;5 牲畜养殖&#xff0c;6 水产养殖&#xff0c;7 地表径流&#xff0c;8 废水处理厂&…

三菱FX3U PLC绝对定位- DRVA指令

指令格式 相关软元件一览 功能和动作 这是采用绝对驱动的单速定位指令。采用从原点(0点)开始的距离指定方式&#xff0c; 也被称为绝对驱动方式。 1、在指令执行过程中&#xff0c;即使改变操作数的内容&#xff0c;也不反映到当前的运行中。 在下次的指令驱动时才有效…

QT 中如何保存matlab 能打开的.mat数据矩阵!

Windows 上安装并使用 MATIO 库来保存 MATLAB 格式的 .mat 文件&#xff0c;需要进行以下步骤&#xff1a; 1. 下载并安装 CMake MATIO 使用 CMake 构建项目&#xff0c;因此你需要先安装 CMake。 前往 CMake 官网下载适用于 Windows 的安装程序并安装。 2. 下载 MATIO 库源…

Windows,MySQL主从复制搭建

前提&#xff1a;windows环境&#xff0c;同一个服务器安装多个相同版本的mysql数据库 多个MySQL服务搭建完成后&#xff0c;下面我们进行主从复制的相关配置 1.主数据库 执行指令 #创建用户 CREATE USER slavelocalhost IDENTIFIED BY 123456;#授权 GRANT REPLICATION SLA…

专线监控的使用方法:运维团队的全面实战指南

在当今高度信息化的时代&#xff0c;专线网络已成为企业连接不同地域、保障业务连续性的重要基础设施。然而&#xff0c;随着网络架构的复杂化和业务需求的多样化&#xff0c;运维团队面临着前所未有的挑战。为了有效应对这些挑战&#xff0c;运维团队需要深入了解并熟练掌握专…

前端埋点学习

前端埋点 前端数据埋点是在前端页面中通过代码的方式手机用户行为数据和页面性能的过程&#xff0c;通过在页面中插入指定的代码&#xff0c;实现实时监控用户在页面上的操作行为。 通常包括一下事件 定义事件: 定义需要手机的数据事件&#xff0c;如点击&#xff0c;浏览等添…

Linux系列-常见的指令(二)

&#x1f308;个人主页&#xff1a; 羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” mv 剪切文件&#xff0c;目录 重命名 比如说&#xff0c;我们在最开始创建一个新的文件hello.txt 然后我们将这个文件改一个名字&#xff0c;改成world.txt 所以&#xff0c;…

UE5 武器IK瞄准系统

创建空项目 创建基础蓝图类My_GameMode&#xff0c;My_HUD&#xff0c;My_PlayChar&#xff0c;My_PlayController 项目设置地图模式 近裁平面 0.1 My_PlayChar蓝图中添加摄像机&#xff0c;角色骨骼网格体&#xff0c;武器骨骼网格体 编辑角色骨骼&#xff0c;预览控制器使用…

本地生活服务项目入局方案解析!本地生活服务商系统能实现怎样的作业效果?

当前&#xff0c;各大平台的本地生活服务业务日渐兴盛&#xff0c;提高创业者入局意向的同时&#xff0c;也让本地生活服务项目有哪些等问题也成为了多个创业者社群中的热议对象。而从目前的讨论情况来看&#xff0c;在创业者们所询问的众多本地生活服务项目中&#xff0c;通过…

apisix云原生网关

定义 企业级网关通过域名、路由将请求分发到对应的应用上&#xff0c;通常承载数千个服务的流量&#xff0c;对稳定性有较高要求。 CNCF全景图 选型 Kubernetes抽象出两个核心概念&#xff1a;Service&#xff0c;为多个Pod提供统一的访问入口&#xff1b;Ingress&#xff…

DBA | 如何将 .mdf 与 .ldf 的数据库文件导入到SQL Server 数据库中?

[ 知识是人生的灯塔&#xff0c;只有不断学习&#xff0c;才能照亮前行的道路 ] 原文链接&#xff1a;DBA | 如何将 .mdf 与 .ldf 的数据库文件导入到SQL Server 数据库中? 如何将 (.mdf) 和 (.ldf) 的SQL Server 数据库文件导入到当前数据库中? Step 1.登录到 Sql Server 服…

18770 差值最大

### 思路 为了找到两个数x和y使得x - y的值最大&#xff0c;并且x在y的右侧&#xff0c;我们可以使用以下方法&#xff1a; 1. 从右向左遍历数组&#xff0c;记录当前遍历到的最大值max_right。 2. 对于每个元素a[i]&#xff0c;计算max_right - a[i]&#xff0c;并更新最大差…

《RabbitMQ篇》消息应答和发布确认

消息应答 消息应答机制&#xff1a;消费者接收信息并处理完之后&#xff0c;告诉rabbitmq该信息已经处理&#xff0c;rabbitmq可以把该信息删除了. 消息自动重新入队&#xff1a;如果处理某个消息的消费者异常关闭了&#xff0c;没有发送ACK确认&#xff0c;rabbitmq会将其重…

C++ | Leetcode C++题解之第463题岛屿的周长

题目&#xff1a; 题解&#xff1a; class Solution {constexpr static int dx[4] {0, 1, 0, -1};constexpr static int dy[4] {1, 0, -1, 0}; public:int dfs(int x, int y, vector<vector<int>> &grid, int n, int m) {if (x < 0 || x > n || y <…

YOLO11改进 | 注意力机制 | 结合静态和动态上下文信息的注意力机制

秋招面试专栏推荐 &#xff1a;深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转 &#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 上下文Transformer&#xff08;CoT&…