Linux内核网络UDP数据包发送(四)——Linux netdevice 子系统

Linux阅码场 2021-07-31 17:18

Linux内核网络UDP数据包发送系列:

Linux内核网络UDP数据包发送(一)

Linux内核网络UDP数据包发送(二)——UDP协议层分析

Linux内核网络UDP数据包发送(三)——IP协议层分析

1. 前言

在继续分析 dev_queue_xmit 发送数据包之前,我们需要了解以下重要概念。

Linux 支持流量控制(traffic control)的功能,此功能允许系统管理员控制数据包如何从机器发送出去。流量控制系统包含几组不同的 queue system,每种有不同的排队特征。各个排队系统通常称为 qdisc,也称为排队规则。可以将 qdisc 视为调度程序, qdisc 决定数据包的发送时间和方式。

Linux 上每个 device 都有一个与之关联的默认 qdisc。对于仅支持单发送队列的网卡,使用默认的 qdisc pfifo_fast。支持多个发送队列的网卡使用 mq 的默认 qdisc。可以运行 tc qdisc 来查看系统 qdisc 信息。某些设备支持硬件流量控制,这允许管理员将流量控制 offload 到网络硬件,节省系统的 CPU 资源。

现在我们从 net/core/dev.c 继续分析 dev_queue_xmit

2. dev_queue_xmit and __dev_queue_xmit

dev_queue_xmit 简单封装了__dev_queue_xmit:

int dev_queue_xmit(struct sk_buff *skb)
{
return __dev_queue_xmit(skb, NULL);
}
EXPORT_SYMBOL(dev_queue_xmit);

__dev_queue_xmit 才是干脏活累活的地方,我们一点一点来看:

static int __dev_queue_xmit(struct sk_buff *skb, void *accel_priv)
{
struct net_device *dev = skb->dev;
struct netdev_queue *txq;
struct Qdisc *q;
int rc = -ENOMEM;

skb_reset_mac_header(skb);

/* Disable soft irqs for various locks below. Also
* stops preemption for RCU.
*/

rcu_read_lock_bh();

skb_update_prio(skb);

开始的逻辑:

  1. 声明变量

  2. 调用 skb_reset_mac_header,准备发送 skb。这会重置 skb 内部的指针,使得 ether 头可以被访问

  3. 调用 rcu_read_lock_bh,为接下来的读操作加锁

  4. 调用 skb_update_prio,如果启用了网络优先级 cgroups,这会设置 skb 的优先级

现在,我们来看更复杂的部分:

        txq = netdev_pick_tx(dev, skb, accel_priv);

这会选择发送队列。

2.1 netdev_pick_tx

netdev_pick_tx 定义在net/core/flow_dissector.c

struct netdev_queue *netdev_pick_tx(struct net_device *dev,
struct sk_buff *skb,
void *accel_priv)
{
int queue_index = 0;

if (dev->real_num_tx_queues != 1) {
const struct net_device_ops *ops = dev->netdev_ops;
if (ops->ndo_select_queue)
queue_index = ops->ndo_select_queue(dev, skb,
accel_priv);
else
queue_index = __netdev_pick_tx(dev, skb);

if (!accel_priv)
queue_index = dev_cap_txqueue(dev, queue_index);
}

skb_set_queue_mapping(skb, queue_index);
return netdev_get_tx_queue(dev, queue_index);
}

如上所示,如果网络设备仅支持单个 TX 队列,则会跳过复杂的代码,直接返回单个 TX 队列。大多高端服务器上使用的设备都有多个 TX 队列。具有多个 TX 队列的设备有两种情况:

  1. 驱动程序实现 ndo_select_queue,以硬件或 feature-specific 的方式更智能地选择 TX 队列

  2. 驱动程序没有实现 ndo_select_queue,这种情况需要内核自己选择设备

从 3.13 内核开始,没有多少驱动程序实现 ndo_select_queue。bnx2x 和 ixgbe 驱动程序实现了此功能,但仅用于以太网光纤通道FCoE。鉴于此,我们假设网络设备没有实现 ndo_select_queue 和没有使用 FCoE。在这种情况下,内核将使用__netdev_pick_tx 选择 tx 队列。

一旦__netdev_pick_tx 确定了队列号,skb_set_queue_mapping 将缓存该值(稍后将在流量控制代码中使用),netdev_get_tx_queue 将查找并返回指向该队列的指针。让我们 看一下__netdev_pick_tx 在返回__dev_queue_xmit 之前的工作原理。

2.2 __netdev_pick_tx

我们来看内核如何选择 TX 队列。net/core/flow_dissector.c:

u16 __netdev_pick_tx(struct net_device *dev, struct sk_buff *skb)
{
struct sock *sk = skb->sk;
int queue_index = sk_tx_queue_get(sk);

if (queue_index < 0 || skb->ooo_okay ||
queue_index >= dev->real_num_tx_queues) {
int new_index = get_xps_queue(dev, skb);
if (new_index < 0)
new_index = skb_tx_hash(dev, skb);

if (queue_index != new_index && sk &&
rcu_access_pointer(sk->sk_dst_cache))
sk_tx_queue_set(sk, new_index);

queue_index = new_index;
}

return queue_index;
}

代码首先调用 sk_tx_queue_get 检查发送队列是否已经缓存在 socket 上,如果尚未缓存, 则返回-1。

下一个 if 语句检查是否满足以下任一条件:

  1. queue_index < 0:表示尚未设置 TX queue 的情况

  2. ooo_okay 标志是否非零:如果不为 0,则表示现在允许无序(out of order)数据包。协议层必须正确地设置此标志。当 flow 的所有 outstanding(需要确认的)数据包都已确认时,TCP 协议层将设置此标志。当发生这种情况时,内核可以为此数据包选择不同的 TX 队列。UDP 协议层不设置此标志 ,因此 UDP 数据包永远不会将 ooo_okay 设置为非零值。

  3. TX queue index 大于 TX queue 数量:如果用户最近通过 ethtool 更改了设备上的队列数, 则会发生这种情况。

以上任何一种情况,都表示没有找到合适的 TX queue,因此接下来代码会进入慢路径以继续寻找合适的发送队列。首先调用 get_xps_queue,它会使用一个由用户配置的 TX queue 到 CPU 的映射,这称为 XPS(Transmit Packet Steering ,发送数据包控制)。

如果内核不支持 XPS,或者系统管理员未配置 XPS,或者配置的映射引用了无效队列, get_xps_queue 返回-1,则代码将继续调用 skb_tx_hash

一旦 XPS 或内核使用 skb_tx_hash 自动选择了发送队列,sk_tx_queue_set 会将队列缓存 在 socket 对象上,然后返回。让我们看看 XPS,以及 skb_tx_hash 在继续调用 dev_queue_xmit 之前是如何工作的。

2.2.1 Transmit Packet Steering (XPS)

发送数据包控制(XPS)是一项功能,允许系统管理员配置哪些 CPU 可以处理网卡的哪些发送 队列。XPS 的主要目的是避免处理发送请求时的锁竞争。使用 XPS 还可以减少缓存驱逐, 避免NUMA机器上的远程内存访问等。

上面代码中,get_xps_queue 将查询这个用户指定的映射,以确定应使用哪个发送 队列。如果 get_xps_queue 返回-1,则将改为使用 skb_tx_hash

2.2.2 skb_tx_hash

如果 XPS 未包含在内核中,或 XPS 未配置,或配置的队列不可用(可能因为用户调整了队列数 ),skb_tx_hash 将接管以确定应在哪个队列上发送数据。准确理解 skb_tx_hash 的工作原理非常重要,具体取决于你的发送负载。include/linux/netdevice.h:

/*
* Returns a Tx hash for the given packet when dev->real_num_tx_queues is used
* as a distribution range limit for the returned value.
*/

static inline u16 skb_tx_hash(const struct net_device *dev,
const struct sk_buff *skb)
{
return __skb_tx_hash(dev, skb, dev->real_num_tx_queues);
}

直接调用了__skb_tx_hash, net/core/flow_dissector.c:

/*
* Returns a Tx hash based on the given packet descriptor a Tx queues' number
* to be used as a distribution range.
*/

u16 __skb_tx_hash(const struct net_device *dev, const struct sk_buff *skb,
unsigned int num_tx_queues)
{
u32 hash;
u16 qoffset = 0;
u16 qcount = num_tx_queues;

if (skb_rx_queue_recorded(skb)) {
hash = skb_get_rx_queue(skb);
while (unlikely(hash >= num_tx_queues))
hash -= num_tx_queues;
return hash;
}

这个函数中的第一个 if 是一个有趣的短路,函数名 skb_rx_queue_recorded 有点误导。skb 有一个 queue_mapping 字段,rx 和 tx 都会用到这个字段。无论如何,如果系统正在接收数据包并将其转发到其他地方,则此 if 语句都为 true。否则,代码将继续向下:

        if (dev->num_tc) {
u8 tc = netdev_get_prio_tc_map(dev, skb->priority);
qoffset = dev->tc_to_txq[tc].offset;
qcount = dev->tc_to_txq[tc].count;
}

要理解这段代码,首先要知道,程序可以设置 socket 上发送的数据的优先级。这可以通过 setsockopt 带 SOL_SOCKET 和 SO_PRIORITY 选项来完成。

如果使用 setsockopt 带 IP_TOS 选项来设置在 socket 上发送的 IP 包的 TOS 标志( 或者作为辅助消息传递给 sendmsg,在数据包级别设置),内核会将其转换为 skb->priority

如前所述,一些网络设备支持基于硬件的流量控制系统。如果 num_tc 不为零,则表示此设 备支持基于硬件的流量控制。这种情况下,将查询一个packet priority 到该硬件支持 的流量控制的映射,根据此映射选择适当的流量类型(traffic class)。

接下来,将计算出该 traffic class 的 TX queue 的范围,它将用于确定发送队列。如果 num_tc 为零(网络设备不支持硬件流量控制),则 qcount 和 qoffset 变量分 别设置为发送队列数和 0。

使用 qcount 和 qoffset,将计算发送队列的 index:

        if (skb->sk && skb->sk->sk_hash)
hash = skb->sk->sk_hash;
else
hash = (__force u16) skb->protocol;
hash = __flow_hash_1word(hash);

return (u16) (((u64) hash * qcount) >> 32) + qoffset;
}
EXPORT_SYMBOL(__skb_tx_hash);

最后,通过__netdev_pick_tx 返回选出的 TX queue index。

3. 继续__dev_queue_xmit

至此已经选到了合适的发送队列,继续__dev_queue_xmit:

        q = rcu_dereference_bh(txq->qdisc);

#ifdef CONFIG_NET_CLS_ACT
skb->tc_verd = SET_TC_AT(skb->tc_verd, AT_EGRESS);
#endif
trace_net_dev_queue(skb);
if (q->enqueue) {
rc = __dev_xmit_skb(skb, q, dev, txq);
goto out;
}

首先获取与此队列关联的 qdisc。之前我们看到单发送队列设备的默认类型是 pfifo_fast qdisc,而对于多队列设备,默认类型是 mq qdisc。

接下来,如果内核中已启用数据包分类 API,则代码会为 packet 分配 traffic class。接下来,检查 disc 是否有合适的队列来存放 packet。像 noqueue 这样的 qdisc 没有队列。如果有队列,则代码调用__dev_xmit_skb 继续处理数据,然后跳转到此函数的末尾。我们很快 就会看到__dev_xmit_skb。现在,让我们看看如果没有队列会发生什么,从一个非常有用的注释开始:

        /* The device has no queue. Common case for software devices:
loopback, all the sorts of tunnels...

Really, it is unlikely that netif_tx_lock protection is necessary
here. (f.e. loopback and IP tunnels are clean ignoring statistics
counters.)
However, it is possible, that they rely on protection
made by us here.

Check this and shot the lock. It is not prone from deadlocks.
Either shot noqueue qdisc, it is even simpler 8)
*/

if (dev->flags & IFF_UP) {
int cpu = smp_processor_id(); /* ok because BHs are off */

正如注释所示,唯一可以拥有”没有队列的 qdisc”的设备是环回设备和隧道设备。如果设备当前处于运行状态,则获取当前 CPU,然后判断此设备队列上的发送锁是否由此 CPU 拥有 :

                if (txq->xmit_lock_owner != cpu) {

if (__this_cpu_read(xmit_recursion) > RECURSION_LIMIT)
goto recursion_alert;

如果发送锁不由此 CPU 拥有,则在此处检查 per-CPU 计数器变量 xmit_recursion,判断其是 否超过 RECURSION_LIMIT。一个程序可能会在这段代码这里持续发送数据,然后被抢占, 调度程序选择另一个程序来运行。第二个程序也可能驻留在此持续发送数据。因此, xmit_recursion 计数器用于确保在此处竞争发送数据的程序不超过 RECURSION_LIMIT 个 。

我们继续:

                        HARD_TX_LOCK(dev, txq, cpu);

if (!netif_xmit_stopped(txq)) {
__this_cpu_inc(xmit_recursion);
rc = dev_hard_start_xmit(skb, dev, txq);
__this_cpu_dec(xmit_recursion);
if (dev_xmit_complete(rc)) {
HARD_TX_UNLOCK(dev, txq);
goto out;
}
}
HARD_TX_UNLOCK(dev, txq);
net_crit_ratelimited("Virtual device %s asks to queue packet!\n",
dev->name);
} else {
/* Recursion is detected! It is possible,
* unfortunately
*/

recursion_alert:
net_crit_ratelimited("Dead loop on virtual device %s, fix it urgently!\n",
dev->name);
}
}

接下来的代码首先尝试获取发送锁,然后检查要使用的设备的发送队列是否被停用。如果没有停用,则更新 xmit_recursion 计数,然后将数据向下传递到更靠近发送的设备。或者,如果当前 CPU 是发送锁定的拥有者,或者如果 RECURSION_LIMIT 被命中,则不进行发送,而会打印告警日志。函数剩余部分的代码设置错误码并返回。

由于我们对真正的以太网设备感兴趣,让我们来看一下之前就需要跟进去的 __dev_xmit_skb 函数,这是发送主线上的函数。

4. __dev_xmit_skb

现在我们带着排队规则 qdisc、网络设备 dev 和发送队列 txq 三个变量来到 __dev_xmit_skb,net/core/dev.c:

static inline int __dev_xmit_skb(struct sk_buff *skb, struct Qdisc *q,
struct net_device *dev,
struct netdev_queue *txq)
{
spinlock_t *root_lock = qdisc_lock(q);
bool contended;
int rc;

qdisc_pkt_len_init(skb);
qdisc_calculate_pkt_len(skb, q);
/*
* Heuristic to force contended enqueues to serialize on a
* separate lock before trying to get qdisc main lock.
* This permits __QDISC_STATE_RUNNING owner to get the lock more often
* and dequeue packets faster.
*/

contended = qdisc_is_running(q);
if (unlikely(contended))
spin_lock(&q->busylock);

代码首先使用 qdisc_pkt_len_init 和 qdisc_calculate_pkt_len 来计算数据的准确长度 ,稍后 qdisc 会用到该值。对于硬件 offload(例如 UFO)这是必需的,因为添加的额外的头 信息,硬件 offload 的时候回用到。

接下来,使用另一个锁来帮助减少 qdisc 主锁上的竞争(我们稍后会看到这第二个锁)。如 果 qdisc 当前正在运行,那么试图发送的其他程序将在 qdisc 的 busylock 上竞争。这允许 运行 qdisc 的程序在处理数据包的同时,与较少量的程序竞争第二个主锁。随着竞争者数量 的减少,这种技巧增加了吞吐量。接下来是主锁:

        spin_lock(root_lock);

接下来处理 3 种可能情况:

  1. 如果 qdisc 已停用

  2. 如果 qdisc 允许数据包 bypass 排队系统,并且没有其他包要发送,并且 qdisc 当前没有运 行。允许包 bypass 所谓的 work-conserving qdisc 那些用于流量整形(traffic reshaping)目的并且不会引起发送延迟的 qdisc

  3. 所有其他情况

让我们来看看每种情况下发生什么,从 qdisc 停用开始:

        if (unlikely(test_bit(__QDISC_STATE_DEACTIVATED, &q->state))) {
kfree_skb(skb);
rc = NET_XMIT_DROP;

如果 qdisc 停用,则释放数据并将返回代码设置为 NET_XMIT_DROP。接下来,如果 qdisc 允许数据包 bypass,并且没有其他包要发送,并且 qdisc 当前没有运行:

        } else if ((q->flags & TCQ_F_CAN_BYPASS) && !qdisc_qlen(q) &&
qdisc_run_begin(q)) {
/*
* This is a work-conserving queue; there are no old skbs
* waiting to be sent out; and the qdisc is not running -
* xmit the skb directly.
*/

if (!(dev->priv_flags & IFF_XMIT_DST_RELEASE))
skb_dst_force(skb);

qdisc_bstats_update(q, skb);

if (sch_direct_xmit(skb, q, dev, txq, root_lock)) {
if (unlikely(contended)) {
spin_unlock(&q->busylock);
contended = false;
}
__qdisc_run(q);
} else
qdisc_run_end(q);

rc = NET_XMIT_SUCCESS;

这个 if 语句有点复杂,如果满足以下所有条件,则整个语句的计算结果为 true:

  1. q-> flags&TCQ_F_CAN_BYPASS:qdisc 允许数据包绕过排队系统。对于所谓的“ work-conserving” qdiscs 这会是 true;即,允许 packet bypass 流量整形 qdisc。 pfifo_fast qdisc 允许数据包 bypass

  2. !qdisc_qlen(q):qdisc 的队列中没有待发送的数据

  3. qdisc_run_begin(p):如果 qdisc 未运行,此函数将设置 qdisc 的状态为“running”并返 回 true,如果 qdisc 已在运行,则返回 false

如果以上三个条件都为 true,那么:

  • 检查 IFF_XMIT_DST_RELEASE 标志,此标志允许内核释放 skb 的目标缓存。如果标志已禁用,将强制对 skb 进行引用计数

  • 调用 qdisc_bstats_update 更新 qdisc 发送的字节数和包数统计

  • 调用 sch_direct_xmit 用于发送数据包。我们将很快深入研究 sch_direct_xmit,因为慢路径也会调用到它

sch_direct_xmit 的返回值有两种情况:

  1. 队列不为空(返回> 0)。在这种情况下,busylock 将被释放,然后调用__qdisc_run 重新启动 qdisc 处理

  2. 队列为空(返回 0)。在这种情况下,qdisc_run_end 用于关闭 qdisc 处理

在任何一种情况下,都会返回 NET_XMIT_SUCCESS

检查最后一种情况:

        } else {
skb_dst_force(skb);
rc = q->enqueue(skb, q) & NET_XMIT_MASK;
if (qdisc_run_begin(q)) {
if (unlikely(contended)) {
spin_unlock(&q->busylock);
contended = false;
}
__qdisc_run(q);
}
}

在所有其他情况下:

  1. 调用 skb_dst_force 强制对 skb 的目标缓存进行引用计数

  2. 调用 qdisc 的 enqueue 方法将数据入队,保存函数返回值

  3. 调用 qdisc_run_begin(p)将 qdisc 标记为正在运行。如果它尚未运行(contended == false),则释放 busylock,然后调用__qdisc_run(p)启动 qdisc 处理

函数最后释放相应的锁,并返回状态码:

        spin_unlock(root_lock);
if (unlikely(contended))
spin_unlock(&q->busylock);
return rc;

5. 调优: Transmit Packet Steering (XPS)

使用 XPS 需要在内核配置中启用它,并提供一个位掩码,用于描述CPU 和 TX queue 的对应关系,这些位掩码类似于 RPS位掩码,简而言之,要修改的位掩码位于以下位置:

/sys/class/net/DEVICE_NAME/queues/QUEUE/xps_cpus

因此,对于 eth0 和 TX queue 0,需要使用十六进制数修改文件: /sys/class/net/eth0/queues/tx-0/xps_cpus,制定哪些 CPU 应处理来自 eth0 的发送队列 0 的发送过程。另外,内核文档Documentation/networking/scaling.txt#L412-L422 指出,在某些配置中可能不需要 XPS。

Reference:

https://blog.packagecloud.io/eng/2017/02/06/monitoring-tuning-linux-networking-stack-sending-data


Linux阅码场 专业的Linux技术社区和Linux操作系统学习平台,内容涉及Linux内核,Linux内存管理,Linux进程管理,Linux文件系统和IO,Linux性能调优,Linux设备驱动以及Linux虚拟化和云计算等各方各面.
评论 (0)
  • 在智慧城市领域中,当一个智慧路灯项目因信号盲区而被迫增设数百个网关时,当一个传感器网络因入网设备数量爆增而导致系统通信失效时,当一个智慧交通系统因基站故障而导致交通瘫痪时,星型网络拓扑与蜂窝网络拓扑在构建广覆盖与高节点数物联网网络时的局限性便愈发凸显,行业内亟需一种更高效、可靠与稳定的组网技术以满足构建智慧城市海量IoT网络节点的需求。星型网络的无线信号覆盖范围高度依赖网关的部署密度,同时单一网关的承载设备数量有限,难以支撑海量IoT网络节点的城市物联系统;而蜂窝网络的无线信号覆盖范围同样高度依
    华普微HOPERF 2025-03-24 17:00 143浏览
  • 近年来,随着半导体产业的快速发展和技术的不断迭代,物联网设备种类繁多(如智能家居、工业传感器),对算力、功耗、实时性要求差异大,单一架构无法满足所有需求。因此米尔推出MYD-YT113i开发板(基于全志T113-i)来应对这一市场需求。米尔基于全志T113-i核心板及开发板part 01  T113-i芯片及OpenAMP简介T113-i芯片简介T113-i由两颗ARM A7 、一颗C906(RISC-V)和一颗DSP(HIFI 4)组成。C906(RISC-V核)特性:主频
    米尔电子嵌入式 2025-03-21 16:28 40浏览
  • 核心板简介创龙科技 SOM-TL3562 是一款基于瑞芯微 RK3562J/RK3562 处理器设计的四核 ARM C ortex-A53 + 单核 ARM Cortex-M0 全国产工业核心板,主频高达 2.0GHz。核心板 CPU、R OM、RAM、电源、晶振等所有元器件均采用国产工业级方案,国产化率 100%。核心板通过 LCC 邮票孔 + LGA 封装连接方式引出 MAC、GMAC、PCIe 2.1、USB3.0、 CAN、UART、SPI、MIPI CSI、MIPI
    Tronlong 2025-03-24 09:59 157浏览
  •       知识产权保护对工程师的双向影响      正向的激励,保护了工程师的创新成果与权益,给企业带来了知识产权方面的收益,企业的创新和发明大都是工程师的劳动成果,他们的职务发明应当受到奖励和保护,是企业发展的重要源泉。专利同时也成了工程师职称评定的指标之一,专利体现了工程师的创新能力,在求职、竞聘技术岗位或参与重大项目时,专利证书能显著增强个人竞争力。专利将工程师的创意转化为受法律保护的“无形资产”,避免技术成果被他人抄袭或无偿使
    广州铁金刚 2025-03-25 11:48 91浏览
  • 在人工智能与物联网技术蓬勃发展的今天,语音交互已成为智能设备的重要功能。广州唯创电子推出的WT3000T8语音合成芯片凭借其高性能、低功耗和灵活的控制方式,广泛应用于智能家居、工业设备、公共服务终端等领域。本文将从功能特点、调用方法及实际应用场景入手,深入解析这款芯片的核心技术。一、WT3000T8芯片的核心功能WT3000T8是一款基于UART通信的语音合成芯片,支持中文、英文及多语种混合文本的实时合成。其核心优势包括:高兼容性:支持GB2312/GBK/BIG5/UNICODE编码,适应不同
    广州唯创电子 2025-03-24 08:42 152浏览
  • 文/郭楚妤编辑/cc孙聪颖‍在人工智能与实体经济深度融合的时代浪潮中,究竟何种 AI 产品,方能切实契合用户对美好未来的向往与期待?3 月 20 日,备受全球瞩目的中国家电及消费电子博览会(AWE2025)于上海新国际博览中心盛大开幕。展会首日,长虹重磅推出首款治愈系 AI TV、客餐厅 PRO 共享空调,以及面向低空经济领域的通信模组等一系列创新产品。这一举动充分展现了长虹在家电领域全面推进 AI 化的坚定决心,以及为低空经济等新兴产业提供有力科技支撑的硬核实力 。“首发” 新品,领航用户价值
    华尔街科技眼 2025-03-21 21:13 54浏览
  • 在智能终端设备快速普及的当下,语音交互已成为提升用户体验的关键功能。广州唯创电子推出的WT3000T8语音合成芯片,凭借其卓越的语音处理能力、灵活的控制模式及超低功耗设计,成为工业控制、商业终端、公共服务等领域的理想选择。本文将从技术特性、场景适配及成本优势三方面,解析其如何助力行业智能化转型。一、核心技术优势:精准、稳定、易集成1. 高品质语音输出,适配复杂环境音频性能:支持8kbps~320kbps宽范围比特率,兼容MP3/WAV格式,音质清晰自然,无机械感。大容量存储:内置Flash最大支
    广州唯创电子 2025-03-24 09:08 163浏览
  • 今年全国两会期间,“体重管理”和“育儿”整体配套政策引发了持久广泛关注。从“吃”到“养”,都围绕着国人最为关心的话题:健康。大家常说“病从口入”,在吃这件事上,过去大家可能更多是为了填饱肚子,如今,消费者从挑选食材到厨电都贯彻着健康的宗旨,吃得少了更要吃得好了。这也意味着在新消费趋势下,谁能抓住众人的心头好,就能带起众人的购买欲望,才能在新一轮竞争中脱颖而出。作为家电行业的风向标,在2025年中国家电及消费电子博览会(AWE)上,这两个话题也被媒体和公众频繁提及。深耕中国厨房三十余年的苏泊尔再次
    华尔街科技眼 2025-03-22 11:42 64浏览
  • 人形机器人产业节奏预估:2024年原型机元年,2025年小规模量产元年。当宇树科技H1人形机器人以灵动的手部动作在春晚舞台上演创意融合舞蹈《秧Bot》,舞出"中国智造"时,电视机前十几亿观众第一次深刻意识到:那个需要仰望波士顿动力的时代正在落幕。*图源:宇树科技短短数周后,宇树G1机器人又用一段丝滑的街舞在网络收割亿级播放量,钢铁之躯跳出赛博朋克的浪漫。2月11日,宇树科技在其京东官方旗舰店上架了两款人形机器人产品,型号分别为Unitree H1和G1。2月12日,9.9万元的G1人形机器人首批
    艾迈斯欧司朗 2025-03-22 21:05 119浏览
  • 文/Leon编辑/cc孙聪颖‍去年,百度公关部副总裁璩静的争议言论闹得沸沸扬扬,最终以道歉离职收场。时隔一年,百度的高管又出事了。近日,“百度副总裁谢广军女儿开盒孕妇”事件登上热搜,持续发酵,引起网友对百度数据安全性的怀疑。3月19日晚间,百度正式发布声明,表示坚决谴责窃取和公开他人隐私的网络暴力行为,同时强调,百度内部实施匿名化、假名化处理,经查验,泄露数据并非来自百度,而是海外的社工库,“当事人承认家长给她数据库”为不实信息,针对相关谣言百度已经向公安机关报案。然而,并非所有网友都对这份声明
    华尔街科技眼 2025-03-21 21:21 98浏览
  • 无论你是刚步入职场的新人,还是已经有几年经验的职场老手,培养领导力都是职业发展中一个至关重要的环节。拥有良好的领导能力不仅能让你从人群中脱颖而出,也能让你在团队中成为一个值得信赖、富有影响力的核心成员。什么是领导力?领导力并不仅仅意味着“当老板”或者“发号施令”。它更多地是一种能够影响他人、激发团队潜能,并带领大家实现目标的能力。一位优秀的领导者需要具备清晰的沟通能力、解决问题的能力,以及对人心的深刻理解。他们知道如何激励人心,如何在压力下保持冷静,并能在关键时刻做出正确的决策。如何培养领导力?
    优思学院 2025-03-23 12:24 77浏览
我要评论
0
2
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦