<kernel>kernel 6.4 USB-之-hub_port_connect_change()分析

news2025/1/17 0:19:48

<kernel>kernel 6.4 USB-之-hub_port_connect_change()分析

kernel 6.4 USB系列文章如下:
<kernel>kernel 6.4 USB-之-hub_event()分析
<kernel>kernel 6.4 USB-之-port_event()分析

本文是基于linux kernel 6.4版本内核分析;源码下载路径:linux kernel
在这里插入图片描述

一、前言

hub_port_connect_change()函数主要用于处理USB集线器端口的连接状态变化。这个函数在以下情况下被调用:
当端口的连接状态发生变化;
当端口的使能状态发生变化(通常由电磁干扰引起);
当usb_reset_and_verify_device函数遇到改变的描述符(比如固件下载后)。

以下是这段代码的详细过程和作用:

函数首先获取指定端口的设备和集线器对象,并打印一条关于端口状态和变化的调试信息。

如果集线器有LED指示器,那么就将指定端口的LED设置为自动模式。

对于OTG设备,如果当前是B主机,那么不会重复去消抖。

如果端口上有设备连接,并且设备的状态不是USB_STATE_NOTATTACHED,那么就尝试复活设备。复活设备的方式取决于端口和设备的状态。如果端口已经使能,并且设备的描述符没有改变,那么就不做任何操作。如果端口没有使能,但设备处于挂起状态,并且设备支持持久连接,那么就尝试唤醒设备。否则,不会复活设备。

清除指定端口的状态变化标志。

如果成功复活设备,那么就直接返回。

如果没有成功复活设备,那么就调用hub_port_connect函数,处理设备的连接状态变化。

总的来说,这个函数的作用就是处理USB集线器端口的连接状态变化。这可能包括设备的连接、断开连接、重新枚举等操作。

二、hub_port_connect_change()函数

hub_port_connect_change()函数内容如下:

static void hub_port_connect_change(struct usb_hub *hub, int port1,
					u16 portstatus, u16 portchange)
		__must_hold(&port_dev->status_lock)
{
	struct usb_port *port_dev = hub->ports[port1 - 1];
	struct usb_device *udev = port_dev->child;
	struct usb_device_descriptor descriptor;
	int status = -ENODEV;
	int retval;

	dev_dbg(&port_dev->dev, "status %04x, change %04x, %s\n", portstatus,
			portchange, portspeed(hub, portstatus));

	if (hub->has_indicators) {
		set_port_led(hub, port1, HUB_LED_AUTO);
		hub->indicator[port1-1] = INDICATOR_AUTO;
	}

#ifdef	CONFIG_USB_OTG
	/* during HNP, don't repeat the debounce */
	if (hub->hdev->bus->is_b_host)
		portchange &= ~(USB_PORT_STAT_C_CONNECTION |
				USB_PORT_STAT_C_ENABLE);
#endif

	/* Try to resuscitate an existing device */
	if ((portstatus & USB_PORT_STAT_CONNECTION) && udev &&
			udev->state != USB_STATE_NOTATTACHED) {
		if (portstatus & USB_PORT_STAT_ENABLE) {
			/*
			 * USB-3 connections are initialized automatically by
			 * the hostcontroller hardware. Therefore check for
			 * changed device descriptors before resuscitating the
			 * device.
			 */
			descriptor = udev->descriptor;
			retval = usb_get_device_descriptor(udev,
					sizeof(udev->descriptor));
			if (retval < 0) {
				dev_dbg(&udev->dev,
						"can't read device descriptor %d\n",
						retval);
			} else {
				if (descriptors_changed(udev, &descriptor,
						udev->bos)) {
					dev_dbg(&udev->dev,
							"device descriptor has changed\n");
					/* for disconnect() calls */
					udev->descriptor = descriptor;
				} else {
					status = 0; /* Nothing to do */
				}
			}
#ifdef CONFIG_PM
		} else if (udev->state == USB_STATE_SUSPENDED &&
				udev->persist_enabled) {
			/* For a suspended device, treat this as a
			 * remote wakeup event.
			 */
			usb_unlock_port(port_dev);
			status = usb_remote_wakeup(udev);
			usb_lock_port(port_dev);
#endif
		} else {
			/* Don't resuscitate */;
		}
	}
	clear_bit(port1, hub->change_bits);

	/* successfully revalidated the connection */
	if (status == 0)
		return;

	usb_unlock_port(port_dev);
	hub_port_connect(hub, port1, portstatus, portchange);
	usb_lock_port(port_dev);
}

下面就对hub_port_connect_change()函数内容详细分析:

2.1 第5-6行:获取USB集线器的指定端口及该端口上连接的设备

第5-6行:主要作用是获取USB集线器的指定端口及该端口上连接的设备。
以下是这段代码的详细过程和作用:
struct usb_port *port_dev = hub->ports[port1 - 1];:这行代码从hub->ports数组中获取指定端口的usb_port结构体对象。hub->ports是一个指向usb_port结构体对象的指针数组,每个元素对应集线器的一个端口。注意,端口编号port1从1开始,所以在获取端口对象时需要减1。

struct usb_device *udev = port_dev->child;:这行代码获取端口上连接的设备。port_dev->child是一个指向usb_device结构体对象的指针,表示端口上连接的设备。如果端口上没有设备连接,那么port_dev->child的值为NULL。

总的来说,这段代码的作用就是获取USB集线器的指定端口及该端口上连接的设备。

2.2 第14-17行:设置USB集线器端口的LED指示器状态

第14-17行:主要作用是设置USB集线器端口的LED指示器状态。
以下是这段代码的详细过程和作用:

if (hub->has_indicators) { … }:这个if语句块在集线器有LED指示器时执行。

set_port_led(hub, port1, HUB_LED_AUTO);:这行代码调用set_port_led函数,将指定端口的LED指示器设置为自动模式。HUB_LED_AUTO是一个宏,表示LED指示器的自动模式。

hub->indicator[port1-1] = INDICATOR_AUTO;:这行代码将指定端口的LED指示器状态保存在hub->indicator数组中。INDICATOR_AUTO是一个宏,表示LED指示器的自动模式。

总的来说,这段代码的作用就是设置USB集线器端口的LED指示器状态。如果集线器有LED指示器,那么就将指定端口的LED指示器设置为自动模式,并将这个状态保存在hub->indicator数组中。

2.2.1 set_port_led()

/*USB 2.0规范第11.24.2.7.1.10节和表11-7中有关使用端口指示器的信息*/
static void set_port_led(struct usb_hub *hub, int port1, int selector)
{
	struct usb_port *port_dev = hub->ports[port1 - 1];
	int status;

	status = set_port_feature(hub->hdev, (selector << 8) | port1,
			USB_PORT_FEAT_INDICATOR);
	dev_dbg(&port_dev->dev, "indicator %s status %d\n",
		to_led_name(selector), status);
}

主要作用是设置USB集线器端口的LED指示器状态。

以下是这段代码的详细过程和作用:

if (hub->has_indicators) { … }:这个if语句块在集线器有LED指示器时执行。

set_port_led(hub, port1, HUB_LED_AUTO);:这行代码调用set_port_led函数,将指定端口的LED指示器设置为自动模式。HUB_LED_AUTO是一个宏,表示LED指示器的自动模式。

hub->indicator[port1-1] = INDICATOR_AUTO;:这行代码将指定端口的LED指示器状态保存在hub->indicator数组中。INDICATOR_AUTO是一个宏,表示LED指示器的自动模式。

总的来说,这段代码的作用就是设置USB集线器端口的LED指示器状态。如果集线器有LED指示器,那么就将指定端口的LED指示器设置为自动模式,并将这个状态保存在hub->indicator数组中。

2.3 第19-24行:USB设备进行HNP过程中不对端口的连接和使能状态进行消抖

第19-24行:主要作用是在USB设备进行Host Negotiation Protocol (HNP)过程中,不对端口的连接和使能状态进行消抖。
以下是这段代码的详细过程和作用:

#ifdef CONFIG_USB_OTG:这是一个预处理指令,用于检查CONFIG_USB_OTG是否被定义。如果CONFIG_USB_OTG被定义,那么就编译和执行后面的代码。CONFIG_USB_OTG通常在配置内核时被定义,表示内核支持USB On-The-Go (OTG)功能。

if (hub->hdev->bus->is_b_host) { … }:这个if语句块在当前设备是B主机时执行。在USB OTG中,设备可以是A主机或B主机。A主机是提供电源的设备,B主机是接收电源的设备。在进行HNP过程时,B主机可以变成A主机,A主机可以变成B主机。

portchange &= ~(USB_PORT_STAT_C_CONNECTION | USB_PORT_STAT_C_ENABLE);:这行代码清除portchange的连接状态变化标志和使能状态变化标志。USB_PORT_STAT_C_CONNECTION和USB_PORT_STAT_C_ENABLE是两个宏,分别表示端口的连接状态变化标志和使能状态变化标志。~操作符将它们的值取反,|操作符将它们的值进行按位或操作,&=操作符将portchange的值和它们的值进行按位与操作。

总的来说,这段代码的作用就是在USB设备进行HNP过程中,不对端口的连接和使能状态进行消抖。这是通过清除portchange的连接状态变化标志和使能状态变化标志实现的。

2.4 第26-76行: 处理USB设备复位和唤醒的逻辑

第26-76行:
处理USB设备复位和唤醒的逻辑。

以下是这段代码的详细过程和作用:
代码首先检查指定USB端口是否有连接的设备(portstatus & USB_PORT_STAT_CONNECTION),并且这个设备的状态不是USB_STATE_NOTATTACHED。如果这两个条件满足,那么进入if语句块。

在if语句块中,首先检查端口是否已经使能(portstatus & USB_PORT_STAT_ENABLE)。如果端口已经使能,那么就读取设备的描述符,并检查设备的描述符是否已经改变。如果设备的描述符已经改变,那么就更新设备的描述符。否则,就将status设置为0,表示没有需要做的事情。

如果端口没有使能,但设备处于挂起状态,并且设备的持久连接功能已经打开(只有在配置了电源管理(CONFIG_PM)的情况下才会检查),那么就尝试唤醒设备。这是通过调用usb_remote_wakeup函数实现的。

如果上述两个条件都不满足,那么就不对设备进行复位或唤醒操作。

总的来说,这段代码的作用就是处理USB设备的复位和唤醒操作。在设备已经连接并且端口已经使能的情况下,会检查设备的描述符是否已经改变,如果已经改变,那么就更新设备的描述符。在设备已经连接但端口没有使能的情况下,如果设备处于挂起状态并且设备的持久连接功能已经打开,那么就尝试唤醒设备。

2.4.1 usb_get_device_descriptor()

路径:drivers\usb\core\message.c
int usb_get_device_descriptor(struct usb_device *dev, unsigned int size)
{
	struct usb_device_descriptor *desc;
	int ret;

	if (size > sizeof(*desc))
		return -EINVAL;
	desc = kmalloc(sizeof(*desc), GFP_NOIO);
	if (!desc)
		return -ENOMEM;

	ret = usb_get_descriptor(dev, USB_DT_DEVICE, 0, desc, size);
	if (ret >= 0)
		memcpy(&dev->descriptor, desc, size);
	kfree(desc);
	return ret;
}

主要用于读取或重新读取USB设备的设备描述符。

以下是这段代码的详细过程和作用:

函数接收两个参数:一个指向usb_device结构体的指针dev,表示要操作的USB设备;一个无符号整数size,表示要读取的设备描述符的大小。

在函数中,首先检查size是否大于设备描述符的大小。如果size大于设备描述符的大小,那么返回-EINVAL,表示参数无效。

然后,调用kmalloc函数,分配一个usb_device_descriptor结构体的内存空间,并将返回的指针赋值给desc。如果kmalloc函数返回NULL,那么返回-ENOMEM,表示内存不足。

接着,调用usb_get_descriptor函数,读取设备的设备描述符。usb_get_descriptor函数会将读取到的设备描述符存储在desc指向的内存空间中。

如果usb_get_descriptor函数成功执行,那么就将desc指向的设备描述符复制到dev->descriptor中。

最后,调用kfree函数,释放desc指向的内存空间,并返回usb_get_descriptor函数的返回值。

总的来说,这个函数的作用就是读取或重新读取USB设备的设备描述符。这是通过调用usb_get_descriptor函数实现的,该函数会将读取到的设备描述符存储在一个临时的内存空间中,然后再将这个设备描述符复制到设备结构体中。

2.4.2 usb_get_descriptor()

路径:drivers\usb\core\message.c
int usb_get_descriptor(struct usb_device *dev, unsigned char type,
		       unsigned char index, void *buf, int size)
{
	int i;
	int result;

	if (size <= 0)		/* No point in asking for no data */
		return -EINVAL;

	memset(buf, 0, size);	/* Make sure we parse really received data */

	for (i = 0; i < 3; ++i) {
		/* retry on length 0 or error; some devices are flakey */
		result = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
				USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,
				(type << 8) + index, 0, buf, size,
				USB_CTRL_GET_TIMEOUT);
		if (result <= 0 && result != -ETIMEDOUT)
			continue;
		if (result > 1 && ((u8 *)buf)[1] != type) {
			result = -ENODATA;
			continue;
		}
		break;
	}
	return result;
}
EXPORT_SYMBOL_GPL(usb_get_descriptor);

主要用于发起一个标准的GET_DESCRIPTOR请求,以获取USB设备的描述符。

以下是这段代码的详细过程和作用:

函数接收五个参数:一个指向usb_device结构体的指针dev,表示要操作的USB设备;一个无符号字符type,表示要获取的描述符的类型;一个无符号字符index,表示要获取的描述符的编号;一个指针buf,表示存放描述符的缓冲区;一个整数size,表示缓冲区的大小。

在函数中,首先检查size是否小于等于0。如果size小于等于0,那么返回-EINVAL,表示参数无效。

然后,使用memset函数将缓冲区的内容全部设置为0。这样可以确保解析的是真正接收到的数据。

接着,进行最多3次尝试,发起GET_DESCRIPTOR请求。在每次尝试中,首先调用usb_control_msg函数,发起GET_DESCRIPTOR请求。如果usb_control_msg函数返回的结果小于等于0并且结果不是-ETIMEDOUT,那么就继续下一次尝试。如果结果大于1并且缓冲区的第二个字节(描述符的类型)不等于type,那么就将结果设置为-ENODATA,并继续下一次尝试。如果以上两个条件都不满足,那么就跳出循环。

最后,返回usb_control_msg函数的返回值。

总的来说,这个函数的作用就是发起一个标准的GET_DESCRIPTOR请求,以获取USB设备的描述符。这是通过调用usb_control_msg函数实现的,该函数会将接收到的描述符存储在提供的缓冲区中。

2.4.3 descriptors_changed()

static int descriptors_changed(struct usb_device *udev,
		struct usb_device_descriptor *old_device_descriptor,
		struct usb_host_bos *old_bos)
{
	int		changed = 0;
	unsigned	index;
	unsigned	serial_len = 0;
	unsigned	len;
	unsigned	old_length;
	int		length;
	char		*buf;

	if (memcmp(&udev->descriptor, old_device_descriptor,
			sizeof(*old_device_descriptor)) != 0)
		return 1;

	if ((old_bos && !udev->bos) || (!old_bos && udev->bos))
		return 1;
	if (udev->bos) {
		len = le16_to_cpu(udev->bos->desc->wTotalLength);
		if (len != le16_to_cpu(old_bos->desc->wTotalLength))
			return 1;
		if (memcmp(udev->bos->desc, old_bos->desc, len))
			return 1;
	}

	/* Since the idVendor, idProduct, and bcdDevice values in the
	 * device descriptor haven't changed, we will assume the
	 * Manufacturer and Product strings haven't changed either.
	 * But the SerialNumber string could be different (e.g., a
	 * different flash card of the same brand).
	 */
	if (udev->serial)
		serial_len = strlen(udev->serial) + 1;

	len = serial_len;
	for (index = 0; index < udev->descriptor.bNumConfigurations; index++) {
		old_length = le16_to_cpu(udev->config[index].desc.wTotalLength);
		len = max(len, old_length);
	}

	buf = kmalloc(len, GFP_NOIO);
	if (!buf)
		/* assume the worst */
		return 1;

	for (index = 0; index < udev->descriptor.bNumConfigurations; index++) {
		old_length = le16_to_cpu(udev->config[index].desc.wTotalLength);
		length = usb_get_descriptor(udev, USB_DT_CONFIG, index, buf,
				old_length);
		if (length != old_length) {
			dev_dbg(&udev->dev, "config index %d, error %d\n",
					index, length);
			changed = 1;
			break;
		}
		if (memcmp(buf, udev->rawdescriptors[index], old_length)
				!= 0) {
			dev_dbg(&udev->dev, "config index %d changed (#%d)\n",
				index,
				((struct usb_config_descriptor *) buf)->
					bConfigurationValue);
			changed = 1;
			break;
		}
	}

	if (!changed && serial_len) {
		length = usb_string(udev, udev->descriptor.iSerialNumber,
				buf, serial_len);
		if (length + 1 != serial_len) {
			dev_dbg(&udev->dev, "serial string error %d\n",
					length);
			changed = 1;
		} else if (memcmp(buf, udev->serial, length) != 0) {
			dev_dbg(&udev->dev, "serial string changed\n");
			changed = 1;
		}
	}

	kfree(buf);
	return changed;
}

主要用于检查USB设备的描述符是否发生了变化。

以下是这段代码的详细过程和作用:

函数接收三个参数:一个指向usb_device结构体的指针udev,表示要检查的USB设备;一个指向usb_device_descriptor结构体的指针old_device_descriptor,表示旧的设备描述符;一个指向usb_host_bos结构体的指针old_bos,表示旧的BOS描述符。

在函数中,首先比较新旧设备描述符是否相同。如果不同,那么返回1,表示描述符发生了变化。

然后,检查新旧BOS描述符是否相同。如果不同,那么返回1,表示描述符发生了变化。

接着,遍历设备的所有配置描述符,比较新旧配置描述符是否相同。如果不同,那么就将changed设置为1,表示描述符发生了变化,并跳出循环。

如果设备有序列号,那么就比较新旧序列号是否相同。如果不同,那么就将changed设置为1,表示描述符发生了变化。

最后,释放临时缓冲区buf,并返回changed。

总的来说,这个函数的作用就是检查USB设备的描述符是否发生了变化。这是通过比较新旧设备描述符、BOS描述符、配置描述符和序列号实现的。如果任何一个描述符发生了变化,那么就返回1,表示描述符发生了变化。

2.5 第68行:清除USB集线器指定端口的状态变化标志

第68行:作用是清除USB集线器指定端口的状态变化标志。

以下是这段代码的详细过程:

clear_bit(port1, hub->change_bits);:这行代码调用clear_bit函数,将hub->change_bits的第port1位设置为0。clear_bit是一个宏,用于将指定位置的位设置为0。hub->change_bits是一个位图,每一位对应集线器的一个端口,如果某一位为1,表示对应的端口的状态发生了变化。
总的来说,这段代码的作用就是清除USB集线器指定端口的状态变化标志。这是通过将hub->change_bits的第port1位设置为0实现的。这样,下次检查端口状态变化时,就不会误认为这个端口的状态发生了变化。

2.6 第70-72行:判断USB的操作结果

第70-72行:判断status的状态,前面会对这个标志位进行操作,如果无需操作 或 USB唤醒成功等 表示 已成功重新验证连接的状态下。
if (status == 0):这行代码是一个条件判断语句,它检查变量status是否等于0。
return;:如果status等于0,那么就执行这行代码。这行代码表示立即结束当前函数的执行,并返回到函数被调用的地方。
总的来说,这段代码的作用就是检查status是否等于0,如果等于0,那么就立即结束当前函数的执行。这通常表示一个操作成功完成,不需要进行后续的处理。

2.7 第74-76行:处理USB集线器中的端口连接

第74-76行:主要作用是处理USB集线器中的端口连接。

以下是这段代码的详细过程和作用:

usb_unlock_port(port_dev);:这行代码调用usb_unlock_port函数,解锁指定的USB端口。在多线程环境中,锁是用来保护共享资源的一种机制,防止多个线程同时访问或修改共享资源。解锁端口表示允许其他线程访问或修改端口。

hub_port_connect(hub, port1, portstatus, portchange);:这行代码调用hub_port_connect函数,处理端口的连接。这个函数会根据端口的状态(portstatus)和变化(portchange)来决定如何处理端口的连接。

usb_lock_port(port_dev);:这行代码调用usb_lock_port函数,锁定指定的USB端口。锁定端口表示阻止其他线程访问或修改端口,直到端口再次被解锁。

总的来说,这段代码的作用就是处理USB集线器中的端口连接。这是通过解锁端口,处理端口的连接,然后再锁定端口来实现的。这样可以保证在处理端口的连接过程中,端口不会被其他线程访问或修改。

三、总结

以上是对USB port event下的端口链接发生变化处理函数hub_port_connect_change()的分析。后面将继续分析,如果USB device 下hub port connect 发生变化的处理过程,例如USB设备插入、USB设备拔出等链接发生变化情况的处理。 由hub_port_connect()函数处理。请看篇分析。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

kubernetes--技术文档--基本概念--《10分钟快速了解》

官网主页&#xff1a; Kubernetes 什么是k8s Kubernetes 也称为 K8s&#xff0c;是用于自动部署、扩缩和管理容器化应用程序的开源系统。 它将组成应用程序的容器组合成逻辑单元&#xff0c;以便于管理和服务发现。Kubernetes 源自Google 15 年生产环境的运维经验&#xff0c…

最长重复子数组(力扣)动态规划 JAVA

给两个整数数组 nums1 和 nums2 &#xff0c;返回 两个数组中 公共的 、长度最长的子数组的长度 。 示例 1&#xff1a; 输入&#xff1a;nums1 [1,2,3,2,1], nums2 [3,2,1,4,7] 输出&#xff1a;3 解释&#xff1a;长度最长的公共子数组是 [3,2,1] 。 示例 2&#xff1a; 输…

Visual Studio 2019源码编译cpu版本onnxruntime

1.下载onnxruntime源码 源码地址&#xff1a;gitee 》https://gitee.com/mirrors/onnx-runtime github 》https://github.com/microsoft/onnxruntime git clone --recursive https://gitee.com/mirrors/onnx-runtime 2.安装anaconda并配置python环境 安装anaconda时记得勾选默…

关于分压电阻电路电压的计算

分压电路是低成本测量电压的方法 1.知道Vin,R1,R2,求Vout Vout Vin / (R1/(R1R2)) 6/(10/20)3V 2.知道Vou,R1,R2,求Vin Vin Vout*(R1R2)/R2 3 *(1010)/10 6V

如遭遇DDoS等攻击会对企业和个人造成严重影响,包括以下

1. 服务不可用&#xff1a;正常用户无法访问目标服务器&#xff0c;导致业务中断&#xff0c;影响用户体验。 2. 数据泄露&#xff1a;攻击者可能会在攻击过程中窃取用户数据&#xff0c;导致隐私泄露和财产损失。 3. 经济损失&#xff1a;由于服务中断&#xff0c;企业可能遭受…

CSS 背景属性

前言 背景属性 属性说明background-color背景颜色background-image背景图background-repeat背景图平铺方式background-position背景图位置background-size背景图缩放background-attachment背景图固定background背景复合属性 背景颜色 可以使用background-color属性来设置背景…

el-transfer穿梭框使用(传值、清空)

一、组件的使用 <el-transferref"myTransfer"filterable:titles"[待选用户, 已选用户]":filter-method"filterMethod"filter-placeholder"请输入关键字查询"v-model"selectedUserIds":data"userData":props&qu…

DyLoRA:使用动态无搜索低秩适应的预训练模型的参数有效微调

又一个针对LoRA的改进方法&#xff1a; DyLoRA: Parameter-Efficient Tuning of Pretrained Models using Dynamic Search-Free Low Rank Adaptation https://arxiv.org/pdf/2210.07558v2.pdf https://github.com/huawei-noah/KD-NLP/tree/main/DyLoRA Part1前言 LoRA存在…

秋招刷题网站推荐

codefun2000.com 最近准备秋招发现了这个网站&#xff0c;里面的题目都是acm输入输出的&#xff0c;包括了最近开的一些公司的笔试真题&#xff0c;秋招笔试就靠这个练习了。 而且里面还有博客和思维导图&#xff0c;讲解比较全面&#xff0c;还能在评论区求助大佬解答。

基于 Debian 12 的MX Linux 23 正式发布!

导读MX Linux 是基于 Debian 稳定分支的面向桌面的 Linux 发行&#xff0c;它是 antiX 及早先的 MEPIS Linux 社区合作的产物。它采用 Xfce 作为默认桌面环境&#xff0c;是一份中量级操作系统&#xff0c;并被设计为优雅而高效的桌面与如下特性的结合&#xff1a;配置简单、高…

数据化决策,揭秘BI工具与数据可视化的魔力

在当今数据驱动的时代&#xff0c;企业越来越需要深入了解自身运营情况&#xff0c;以便做出明智的决策和战略规划。在这个背景下&#xff0c;商业智能&#xff08;Business Intelligence&#xff0c;简称BI&#xff09;工具和数据可视化技术逐渐崭露头角&#xff0c;成为企业成…

tcpip协议族

现在Internet(因特网&#xff09;使用的主流协议族是TCP/IP协议族&#xff0c;它是一个分层、多协议的通信体系。TCP/IP协议族是一个四层协议系统&#xff0c;自底而上分别是数据链路层、网络层、传输层和应用层。每一层完成不同的能&#xff0c;且通过若干协议来实现&#xff…

CW4-3A-S(004)CW4-6A-S(004)CW4-10A-S(004)CW4-20A-S(004)CW4-30A-S(004)端子台式滤波器

CW4L3-3A-S(003) CW4L3-6A-S(003) CW4L3-10A-S(003) CW4L3-20A-S(003) CW4L3-30A-S(003)端子台式滤波器 CW4-3A-S(004) CW4-6A-S(004) CW4-10A-S(004) CW4-20A-S(004) CW4-30A-S(004)端子台式滤波器 CW4L4-3A-R CW4L4--6A-R CW4L4-10A-R CW4L4-20A-R CW4L4-30A-R端…

字节二面:10Wqps会员系统,如何设计?

说在前面 在尼恩的&#xff08;50&#xff09;读者社区中&#xff0c;经常遇到一个 非常、非常高频的一个面试题&#xff0c;但是很不好回答&#xff0c;类似如下&#xff1a; 千万级数据&#xff0c;如何做系统架构&#xff1f; 亿级数据&#xff0c;如何做系统架构&#xf…

工业物联网网关是什么?有什么作用?

工业物联网网关是工业领域中的一种重要设备&#xff0c;它在工业物联网系统中充当桥梁和连接器的角色。作为边缘计算的关键组件之一&#xff0c;工业物联网网关用于实现工业设备、传感器、PLC、DCS、OPC等各种设备的数据采集、处理、转发和控制。它在工业物联网系统中发挥着关键…

BEiT: BERT Pre-Training of Image Transformers 论文笔记

BEiT: BERT Pre-Training of Image Transformers 论文笔记 论文名称&#xff1a;BEiT: BERT Pre-Training of Image Transformers 论文地址&#xff1a;2106.08254] BEiT: BERT Pre-Training of Image Transformers (arxiv.org) 代码地址&#xff1a;unilm/beit at master …

恒运资本:如何融券做空?融资做多?

在股票商场经常听到做多、做空两种战略。那么。如何融券做空&#xff1f;融资做多&#xff1f;下面恒运资本为大家准备了相关内容&#xff0c;以供参阅。 如何融券做空&#xff1f; 融券做空的意思是投资者以为未来某只股票会跌落&#xff0c;因而向证券公司借入某只股票&…

浅谈智能建筑中电力监控系统的应用与产品选型

贾丽丽 安科瑞电气股份有限公司 上海嘉定201801 摘要&#xff1a;近几十年&#xff0c;中国现代化经济不断发展&#xff0c;计算机技术、信息技术等相关产业也取得了飞跃性的进步。随着商业、生活以及公共建筑不断提高智能管理和节能的要求&#xff0c;电力监控系统开始逐渐渗…

带你掌握Stable Diffution商业级玩法

课程介绍 学习地址 《Stable Diffusion商业级玩法》通过详细讲解AI绘画技巧、实操演示和个性化指导&#xff0c;帮助您从零基础成为绘画高手&#xff0c;帮助您有效推广产品或服务&#xff0c;提升市场份额。教您掌握稳定扩散绘画技巧&#xff0c;开启艺术创作新篇章。