嵌入式C/C++进阶-进程/线程通信《信号,一文搞定》
本文摘要: 信号是Linux/Unix系统中进程间异步通信的核心机制,本质是内核向进程发送的软中断通知。POSIX标准扩展了SystemV的基础功能,支持信号处理函数注册、屏蔽和定时器。信号分为不可靠信号(1-31)和实时信号(SIGRTMIN-MAX),其中SIGKILL/SIGSTOP不可捕获。典型应用场景包括进程控制、异常处理、定时任务等。开发建议使用sigaction替代老旧signal接
目录
1.5 Linux 常用信号总表(System V + POSIX)
1 信号(Signal)
1.1 本质
信号是一种异步通知机制,用于在进程之间或内核与进程之间传递事件。
✅ 一句话本质: 让操作系统向进程“发送一个事件通知”,告诉它“发生了某件事”,比如中断、异常、退出、用户操作等。
1.2 原理
信号的原理可以类比为软中断(Software Interrupt):
-
当某个事件发生(如按下 Ctrl+C、子进程退出、定时器超时、kill 命令等),内核向目标进程发送一个信号。
-
目标进程在运行时被打断,转而执行对应的信号处理函数(Signal Handler)。
-
若进程未注册处理函数,则执行默认动作(如终止、忽略或停止)。
信号传递依赖内核调度,由内核维护信号队列和屏蔽字。
1.3 实现方式
| 标准 | 接口 | 特点 |
|---|---|---|
| System V | kill |
老标准,只提供发送信号功能,兼容性好 |
| POSIX | kill, signal, sigaction, sigprocmask, sigpending, sigsuspend, alarm, setitimer |
新标准,接口全面,支持注册处理函数、阻塞/等待信号、定时器功能,更现代可靠 |
在纯 System V 标准中,只能发送信号(kill),而无法定义自定义处理函数。接收方只能执行系统默认行为(终止、忽略、暂停等)。只有在 POSIX 引入信号处理机制之后,才支持进程自定义处理函数。
1.4 特点总结
⚡ 异步:信号随时可能到达并中断进程执行。
📢 事件驱动:通常用于通知某个状态或事件发生。
🔒 不可携带复杂数据:信号只传递类型(及少量附加信息)。
🧠 内核直接支持:无需额外内存或管道。
🚫 不适合频繁通信:更适用于“控制通知”而非“数据交换”。
1.5 Linux 常用信号总表(System V + POSIX)
| 编号 | 宏名称 | 触发来源 | 含义(用途说明) | 默认行为 |
|---|---|---|---|---|
| 1 | SIGHUP |
终端挂断、会话关闭、或由程序发送 | “挂起”信号,表示控制终端断开或配置需重载(常用于守护进程 reload) | 终止进程 |
| 2 | SIGINT |
用户按下 Ctrl+C 或 kill -2 |
中断进程(Interrupt) | 终止进程 |
| 3 | SIGQUIT |
用户按下 Ctrl+\ 或 kill -3 |
退出并产生 core dump | 终止 + core dump |
| 4 | SIGILL |
CPU 执行非法指令 | 非法指令错误(可能程序损坏) | 终止 + core dump |
| 5 | SIGTRAP |
调试器断点、trap 指令 |
调试用途(gdb 使用) | 终止 + core dump |
| 6 | SIGABRT |
调用 abort() 函数 |
异常终止 | 终止 + core dump |
| 7 | SIGBUS |
非法内存访问(对齐错误或无效总线访问) | 硬件访问错误 | 终止 + core dump |
| 8 | SIGFPE |
浮点异常(除零等) | 算术错误 | 终止 + core dump |
| 9 | SIGKILL |
kill -9 或系统强制杀死 |
无法被捕获/忽略的终止信号 | 立即终止(不可捕获) |
| 10 | SIGUSR1 |
用户自定义信号(1) | 应用自定义事件 | 终止进程(可捕获) |
| 11 | SIGSEGV |
无效内存访问(Segmentation Fault) | 访问非法内存区域 | 终止 + core dump |
| 12 | SIGUSR2 |
用户自定义信号(2) | 应用自定义事件 | 终止进程(可捕获) |
| 13 | SIGPIPE |
向已关闭的管道/Socket 写入 | 写入失败时产生 | 终止进程(默认),常被忽略 |
| 14 | SIGALRM |
调用 alarm() / setitimer() |
定时器到期信号 | 终止进程 |
| 15 | SIGTERM |
默认的 kill 信号 (kill -15) |
请求进程正常退出 | 终止进程 |
| 16 | SIGSTKFLT |
协处理器栈错误(历史) | 已废弃(罕见) | 终止进程 |
| 17 | SIGCHLD |
子进程停止或结束时发送给父进程 | 子进程退出通知 | 忽略(默认不终止) |
| 18 | SIGCONT |
恢复被暂停的进程(与 SIGSTOP 对应) | kill -CONT 或系统恢复 |
继续运行 |
| 19 | SIGSTOP |
强制暂停进程(无法捕获) | kill -STOP 或 Ctrl+Z |
暂停(不可捕获/忽略) |
| 20 | SIGTSTP |
用户按下 Ctrl+Z |
暂停进程(可捕获) | 暂停(可捕获) |
| 21 | SIGTTIN |
后台进程尝试读终端 | Shell 作业控制 | 暂停 |
| 22 | SIGTTOU |
后台进程尝试写终端 | Shell 作业控制 | 暂停 |
| 23 | SIGURG |
Socket 有“带外数据”(Out-of-band data) | 网络异步事件 | 忽略 |
| 24 | SIGXCPU |
超出 CPU 时间限制(setrlimit) |
系统资源保护 | 终止 + core dump |
| 25 | SIGXFSZ |
文件超过最大限制 | 文件系统限制 | 终止 + core dump |
| 26 | SIGVTALRM |
setitimer(ITIMER_VIRTUAL) 超时 |
虚拟时间定时器 | 终止进程 |
| 27 | SIGPROF |
setitimer(ITIMER_PROF) 超时 |
统计/性能分析用 | 终止进程 |
| 28 | SIGWINCH |
终端窗口大小变化 | shell/tui 程序响应窗口变化 | 忽略 |
| 29 | SIGIO / SIGPOLL |
文件描述符可读/可写 | 异步 IO 通知 | 终止进程 |
| 30 | SIGPWR |
电源故障、系统关机事件 | UPS / 电源通知 | 终止进程 |
| 31 | SIGSYS |
非法系统调用 | 系统调用参数错误 | 终止 + core dump |
⚙️ 实时信号(POSIX.1b Real-time Signals)
| 宏名称 | 含义 | 触发来源 | 默认行为 |
|---|---|---|---|
SIGRTMIN ~ SIGRTMAX |
实时信号区间(数量由系统决定,通常为 32~64) | 用户自定义、POSIX 定时器、异步 I/O、线程通信 | 终止进程(可捕获) |
特点:
-
支持信号队列(不会合并)
-
可携带参数(
sigqueue()) -
用于线程、实时通信、异步事件(如 timer_create)
🚦 信号的触发来源分类总结
| 来源 | 说明 | 示例 |
|---|---|---|
| 内核自动触发 | 程序运行中出现硬件/系统异常 | SIGSEGV, SIGFPE, SIGILL, SIGBUS |
| 用户主动发送 | 调用 kill(pid, sig)、raise(sig)、pthread_kill() |
SIGUSR1, SIGUSR2, SIGTERM |
| 终端/控制台产生 | 用户键盘组合(Ctrl+C/Z/\)或 shell 控制 | SIGINT, SIGTSTP, SIGQUIT, SIGHUP |
| 进程关系触发 | 子进程结束、作业控制等 | SIGCHLD, SIGCONT, SIGSTOP |
| 定时器触发 | alarm(), setitimer(), timer_create() |
SIGALRM, SIGVTALRM, SIGPROF |
| 系统资源限制 | 超出 ulimit 或资源控制(rlimit) |
SIGXCPU, SIGXFSZ |
| 文件/网络事件 | 异步 I/O、Socket 带外数据 | SIGIO, SIGURG |
| 系统事件 | 电源、窗口、系统调用错误 | SIGPWR, SIGWINCH, SIGSYS |
💡常见编程习惯
| 场景 | 常用信号 | 处理方式 |
|---|---|---|
| 进程优雅退出 | SIGTERM, SIGINT |
注册 handler 释放资源再退出 |
| 守护进程配置重载 | SIGHUP |
handler 中重新加载配置文件 |
| 子进程退出回收 | SIGCHLD |
handler 中 waitpid() 回收 |
| 防止管道崩溃 | SIGPIPE |
signal(SIGPIPE, SIG_IGN); |
| 定时任务 | SIGALRM |
在 handler 中执行周期任务 |
| 用户自定义控制 | SIGUSR1, SIGUSR2 |
应用层自由定义行为 |
1.6 信号宏
| 宏 | 含义 | 典型用法(示例) | 备注 |
|---|---|---|---|
SIG_DFL |
恢复 默认处理(Default action) | signal(SIGTERM, SIG_DFL); |
POSIX 标准常用值,表示使用内核默认动作(如终止、忽略、core dump 等) |
SIG_IGN |
忽略 信号(Ignore) | signal(SIGPIPE, SIG_IGN); |
POSIX 标准常用,接收到信号时被丢弃 |
SIG_ERR |
错误返回标志(signal/sigaction 调用失败时返回) | if (signal(SIGINT, h) == SIG_ERR) perror("signal"); |
POSIX 定义。不是 handler,不会被调用 |
SIG_HOLD |
将信号放入“保持/阻塞”状态(历史/实现) | (部分实现用于旧的 sigset API) |
非 POSIX 标准,历史遗留(旧 System V 接口中出现) |
SIG_GET |
(历史/实现) 表示“获取当前 handler” | 仅在少数旧实现中见到 | 非标准;历史用途,几乎不用 |
SIG_SGE |
(历史/实现) 内部/扩展标志 | — | 非标准,厂商/历史遗留 |
SIG_ACK |
(历史/实现) 确认/应答相关 | — | 非标准,厂商/历史遗留 |
SIG_BLOCK |
sigprocmask 的 how 参数:阻塞集合中的信号 |
sigprocmask(SIG_BLOCK, &set, NULL); |
POSIX 标准,用于临界区阻塞信号 |
SIG_UNBLOCK |
sigprocmask 的 how 参数:解除阻塞 |
sigprocmask(SIG_UNBLOCK, &set, NULL); |
POSIX 标准 |
SIG_SETMASK |
sigprocmask 的 how 参数:设置屏蔽字为集合 |
sigprocmask(SIG_SETMASK, &set, &old); |
POSIX 标准 |
SA_RESTART |
sigaction 的 sa_flags:被信号打断的系统调用自动重启 |
act.sa_flags = SA_RESTART; |
POSIX 推荐选项,避免 EINTR 在部分系统调用中出现 |
SA_RESETHAND(或 SA_ONESHOT) |
处理一次后恢复默认动作(一次性 handler) | `act.sa_flags | = SA_RESETHAND;` |
SA_SIGINFO |
使 handler 使用 sa_sigaction(带更多信息) |
act.sa_flags = SA_SIGINFO; |
handler 原型为 void (*sa_sigaction)(int, siginfo_t*, void*) |
SA_NODEFER(或 SA_NOMASK) |
在信号处理期间不自动屏蔽该信号 | `act.sa_flags | = SA_NODEFER;` |
SA_NOCLDSTOP |
对子进程停止/继续不发送 SIGCHLD |
act.sa_flags = SA_NOCLDSTOP; |
与 SIGCHLD 行为相关 |
SA_NOCLDWAIT |
子进程退出时,不在父进程产生僵尸(内核自动回收) | act.sa_flags = SA_NOCLDWAIT; |
可避免 wait(),但在不同内核行为上需注意 |
SA_ONSTACK |
在备用信号栈上执行 handler(配合 sigaltstack) |
`act.sa_flags | = SA_ONSTACK;` |
ITIMER_REAL |
setitimer 的定时器类型:以真实时间(会产生 SIGALRM) |
setitimer(ITIMER_REAL, &tv, NULL); |
POSIX/Unix 常用 |
ITIMER_VIRTUAL |
以进程用户态 CPU 时间为基准(会产生 SIGVTALRM) |
setitimer(ITIMER_VIRTUAL, ...) |
POSIX/Unix |
ITIMER_PROF |
包含系统 + 用户时间(会产生 SIGPROF) |
setitimer(ITIMER_PROF, ...) |
POSIX/Unix |
NSIG / SIGRTMIN / SIGRTMAX |
信号数量或实时信号范围 | for (i=1;i<NSIG;i++) ... 或 使用 SIGRTMIN |
SIGRT* 为 POSIX 实时信号(可携带数据) |
SIGEV_SIGNAL / SIGEV_THREAD |
sigevent 的通知类型(定时器/异步IO) |
sev.sigev_notify = SIGEV_SIGNAL; |
与 POSIX 定时器/异步事件相关(sigevent) |
额外说明(要点归纳)
-
必须区分“handler 值”与“控制/标志宏”
-
SIG_DFL/SIG_IGN/(历史)SIG_GET等是作为signal()的第二个参数使用的“特殊值”,或作为signal()返回值(SIG_ERR)。 -
SIG_BLOCK/SIG_UNBLOCK/SIG_SETMASK是传给sigprocmask的操作类型。 -
SA_*系列是sigaction的标志位(sa_flags),用于控制处理行为。
-
-
现代编程实践
-
优先使用
sigaction()+sigprocmask()(POSIX),而非老旧signal()。 -
常用的宏你只需记住:
SIG_DFL、SIG_IGN、SIG_ERR(与signal()相关),SIG_BLOCK/SIG_UNBLOCK/SIG_SETMASK(与sigprocmask()相关),以及常见SA_标志(如SA_RESTART、SA_SIGINFO、SA_RESETHAND、SA_NOCLDWAIT)。
-
-
历史兼容性
-
SIG_GET、SIG_SGE、SIG_ACK、SIG_HOLD等多为 历史/厂商扩展,现代 POSIX 程序通常不使用,若需要移植性请用条件编译或查文档。
-
-
实时信号
-
POSIX 实时信号 (
SIGRTMIN..SIGRTMAX) 支持队列化和附带数据,适用于需要较丰富异步通信的场景。
-
⚠️ 重要的例外
需要特别注意,有两个信号是无法被捕获、阻塞或忽略的终极武器,其余信号均可以重载,但是异常重载后如未终止程序,会在未定义的不稳定状态下运行:
-
SIGKILL(信号 9) -
SIGSTOP(信号 19)
1.7 对比
| 特性 | 信号(Signal) | 管道(Pipe) | 消息队列 | 共享内存 |
|---|---|---|---|---|
| 通信内容 | 控制/事件 | 数据流 | 结构化消息 | 任意数据 |
| 是否异步 | ✅ 是 | ❌ 否 | ❌ 否 | ❌ 否 |
| 数据量 | 极少(仅类型) | 小 | 中等 | 大 |
| 速度 | 快(仅事件) | 中 | 中 | 快(零拷贝) |
| 典型用途 | 通知 / 中断 / 进程控制 | 父子进程通信 | 异步消息交换 | 大数据共享 |
2 C语言实现
2.1 System V 程序实现
2.1.1 接口讲解
System V 信号接口非常简洁:
| 接口 | 作用 | 参数说明 |
|---|---|---|
kill(pid_t pid, int sig) |
向进程或进程组发送信号 | pid:目标进程ID;sig:信号编号 |
⚡ 特点:System V 只提供发送信号的功能,不提供注册信号处理函数或阻塞信号的机制。
2.1.2 使用示例
System V 本身没有注册处理函数的标准接口,所以通常只是发送信号给其他进程:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
int main() {
pid_t target_pid;
printf("请输入要发送信号的进程 PID: ");
scanf("%d", &target_pid);
// 向目标进程发送 SIGTERM 信号
if (kill(target_pid, SIGTERM) == -1) {
perror("kill");
return 1;
}
printf("已向进程 %d 发送 SIGTERM 信号\n", target_pid);
return 0;
}
System V 仅负责发送信号,不关心接收进程如何处理。
2.2 POSIX 程序实现
2.2.1 接口讲解
POSIX 信号接口更全面,支持注册处理函数、阻塞/等待信号以及定时器功能:
| 接口 | 作用 | 参数说明 |
|---|---|---|
kill(pid, sig) |
向进程或进程组发送信号 | 同 System V |
signal(signum, handler) |
注册信号处理函数(旧接口) | signum:信号编号;handler:函数指针, |
sigaction(signum, &act, NULL) |
注册信号处理函数(推荐接口) | act:新处理行为;oldact:保存旧行为(可选) |
sigprocmask(how, set, oldset) |
阻塞/解除阻塞信号 | how:SIG_BLOCK/SIG_UNBLOCK/SIG_SETMASK;set:信号集合 |
sigpending(set) |
查询当前被阻塞的信号集合 | set:信号集合 |
sigsuspend(mask) |
原子挂起进程,等待未屏蔽信号 | mask:临时屏蔽字 |
alarm(seconds) |
设置定时器,到期发送 SIGALRM |
seconds:秒数 |
setitimer(which, &new_value, &old_value) |
精细定时器接口,可微秒级 | which:定时器类型;new_value:新定时值 |
2.2.2 使用示例
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
// 信号处理函数
void handle_sigusr1(int sig) {
printf("收到 SIGUSR1 信号,sig=%d\n", sig);
}
int main() {
struct sigaction sa;
sa.sa_handler = handle_sigusr1; // 注册处理函数
sigemptyset(&sa.sa_mask); // 处理信号时屏蔽的信号集合
sa.sa_flags = 0; // 默认标志
if (sigaction(SIGUSR1, &sa, NULL) == -1) {
perror("sigaction");
return 1;
}
printf("进程 PID=%d,等待 SIGUSR1 信号...\n", getpid());
while (1) {
pause(); // 阻塞等待信号
}
return 0;
}
POSIX 信号接口可以可靠注册处理函数,支持信号屏蔽和定时器,比 System V 更现代和稳健。
3 面试常见问题
1. 什么是信号?在C/C++中,信号的主要作用是什么?
信号是进程间通信的一种异步通知机制,用于告知进程某个特定事件已发生。主要作用包括:处理硬件异常(如段错误SIGSEGV)、响应终端指令(如Ctrl+C的SIGINT)、进程间简单通知、定时器超时处理等。
2. 信号作为一种IPC机制,最适合哪些场景?
信号适合异步通知和简单事件处理场景,不适用于需要传递大量数据或复杂同步的场景。典型应用包括:优雅终止进程、处理程序异常、定时任务提醒、子进程状态变化通知等。
3. 描述信号的生命周期
-
产生:由硬件异常、终端指令、kill命令或软件条件触发
-
注册:信号被添加到目标进程的待处理信号集中
-
注销:信号被处理前从待处理集中移除
-
处理:执行默认动作、忽略或调用用户自定义处理函数
4. 信号有哪几种基本的处理方式?
-
默认处理:执行系统预定义行为(终止、忽略、核心转储等)
-
忽略信号:明确指示系统忽略该信号(SIGKILL和SIGSTOP除外)
-
自定义处理:注册用户定义的信号处理函数
5. 如何捕获一个信号并执行自定义函数?
#include <signal.h>
void signal_handler(int sig) {
// 处理逻辑
}
int main() {
// 使用signal函数(简单但不推荐用于复杂程序)
signal(SIGINT, signal_handler);
// 或使用更健壮的sigaction
struct sigaction sa;
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);
}
6. 哪些信号不能被捕获或忽略?
SIGKILL(信号9)和SIGSTOP(信号19)是终极控制信号,任何进程都无法捕获、阻塞或忽略,确保系统总能终止或暂停失控进程。
7. 信号处理函数中有哪些安全注意事项?
信号处理函数应:
-
只调用异步信号安全函数(如write、_exit)
-
避免使用非安全函数(如printf、malloc)
-
执行时间尽量短,避免阻塞
-
使用volatile sig_atomic_t类型保护共享变量
8. 什么是可靠信号与不可靠信号?
早期Unix信号(编号1-31)可能丢失,为不可靠信号;现代Linux通过实时信号(SIGRTMIN-SIGRTMAX)和sigqueue()提供可靠信号支持,保证信号不丢失、可排队、可携带额外数据。
9. 信号和信号量(Semaphore)有什么区别?
信号是异步事件通知机制,用于进程间简单通知。信号量是同步原语,用于控制多进程/线程对共享资源的访问,本质是一个计数器配合等待队列。
10. 在多线程程序中使用信号需要注意什么?
-
信号是发送给整个进程的,但可由特定线程处理
-
使用pthread_sigmask设置线程信号掩码
-
建议创建专用信号处理线程,使用sigwait同步处理信号
-
避免在主工作线程中处理异步信号
11. 为什么说信号不适合作为线程间通信的主要方式?
线程间通信应使用更高效、可控的机制(互斥锁、条件变量、消息队列等)。信号的异步性会引入竞态条件,且无法精确控制哪个线程接收信号,增加了调试复杂度。
12. 如何模拟"信号量"的功能?
虽然C++标准库没有直接提供信号量,但可以通过mutex和condition_variable实现:
#include <mutex>
#include <condition_variable>
class CountingSemaphore {
int count_;
std::mutex mutex_;
std::condition_variable cv_;
public:
CountingSemaphore(int initial = 0) : count_(initial) {}
void signal() {
std::lock_guard<std::mutex> lock(mutex_);
++count_;
cv_.notify_one();
}
void wait() {
std::unique_lock<std::mutex> lock(mutex_);
cv_.wait(lock, [this]{ return count_ > 0; });
--count_;
}
};
13. 在信号处理函数中如何安全地修改一个全局标志?
#include <signal.h>
#include <stdatomic.h>
// C11方式(推荐)
_Atomic int graceful_shutdown = 0;
// 或传统方式
volatile sig_atomic_t shutdown_requested = 0;
void handler(int sig) {
// 简单赋值是原子的
graceful_shutdown = 1;
// 或:shutdown_requested = 1;
}
14. 什么是信号掩码(信号集)?
信号掩码定义当前被阻塞(暂时不交付)的信号集合。使用sigset_t类型和以下函数操作:
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT); // 添加SIGINT到集合
sigprocmask(SIG_BLOCK, &set, NULL); // 阻塞这些信号
15. sigaction相比signal函数有哪些优势?
-
行为一致性:在各Unix系统上行为一致
-
更多控制:可指定信号处理时的行为标志
-
信号屏蔽:可设置处理函数执行期间自动阻塞的信号
-
额外信息:处理函数可获得更多信号相关信息
16. 如何让一个进程在后台运行时不受终端关闭的影响?
创建守护进程的关键步骤:
pid_t pid = fork();
if (pid > 0) exit(0); // 父进程退出
setsid(); // 创建新会话,脱离终端
chdir("/"); // 改变工作目录
umask(0); // 重设文件权限掩码
// 关闭标准文件描述符
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
17. 如果信号处理函数执行时间过长,可能会导致什么问题?
-
延迟对其他信号的响应
-
可能错过重要的时序要求
-
同种信号会被自动阻塞,导致信号丢失
-
可能影响程序整体响应性能
18. 除了kill命令,还有哪些方式可以给进程发送信号?
-
终端按键:Ctrl+C(SIGINT)、Ctrl+Z(SIGTSTP)、Ctrl+(SIGQUIT)
-
硬件异常:段错误(SIGSEGV)、除零(SIGFPE)、非法指令(SIGILL)
-
软件条件:alarm(SIGALRM)、子进程退出(SIGCHLD)
-
系统调用:kill()、sigqueue()、pthread_kill()
19. 在C++的RAII风格代码中,信号可能带来什么挑战?
信号可能中断正在执行的代码,如果在资源持有期间(如锁未释放、文件未关闭)发生信号,并在处理函数中尝试操作相同资源,可能导致:
-
死锁(如重复加锁)
-
资源泄漏
-
状态不一致
20. 信号处理函数执行期间,如果再次收到同一信号会怎样?
默认情况下,信号处理函数执行期间,同种信号会被自动加入进程信号掩码(阻塞)。这意味着:
-
不会发生嵌套调用
-
后续信号会被标记为未决,待当前处理完成后才交付
-
可使用sa_flags的SA_NODEFER标志改变此行为
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐

所有评论(0)