新手阶段我啃《APUE》信号那一章啃了三遍才敢说自己入门了。但真正让我对信号机制“开窍”的不是书本上的定义而是线上一次诡异的服务“假死”事故——进程还在CPU 占用为 0就是什么活都不干。后来 strace 一挂发现进程阻塞在一个被信号打断后没有重试的系统调用上就这么安安静静地“躺”了大半天。那一刻我才明白信号这东西日常里安静得像风但处理不好真的会让整个服务在风中凌乱。这篇文章聊的就是 Linux 信号处理的门道——从原理到实操到填坑一次性讲透。1. 信号是什么先搞清楚这个“异步通知”到底在通知什么1.1 从一个“CtrlC”讲起信号的日常体验你在终端里跑着一个耗时的命令比如tar解压一个大文件等得不耐烦了按下CtrlC命令立刻终止提示符回来了。这个过程中发生了什么很多人只知道“按 CtrlC 能中断程序”但背后的机制就是信号终端驱动把按键解释为一个中断信号SIGINT然后把这个信号发送给前台进程组里的每一个进程。进程收到 SIGINT 后默认行为是终止运行。这件稀松平常的小事把信号的几个核心特征全暴露出来了信号是异步的你不知道进程正在执行哪条指令按下按键的那一刻进程可能在做解压、可能在写日志、可能在等待网络数据包信号随时可能到达。信号是通知不是指令内核只是告诉进程“你该中断了”至于进程是立刻终止、还是先保存现场再退出、甚至完全忽略这个消息取决于进程自己设置的“态度”。信号有默认行为如果进程没有专门处理 SIGINT内核就按默认规则办——终止进程。理解这一点信号就不再神秘了。它本质上是操作系统给进程递小纸条“注意发生了一件事你自己看着办。”1.2 信号在内核里的出生和送达从按键到进程的完整链路既然要讲“艺术和实践”就不能只停留在应用层。我试着把信号从产生到被处理的完整链路捋一遍你会发现它其实是一个很精巧的状态机。一个信号的生命周期大致分为四个阶段产生Generation事件发生的瞬间。引发信号的事件很多键盘输入SIGINT、SIGQUIT、硬件异常段错误产生 SIGSEGV、非法指令产生 SIGILL、软件条件kill()系统调用、alarm()定时器到期、子进程状态变化产生 SIGCHLD、终端挂断SIGHUP等。未决Pending信号已经生成但还没有被目标进程接收。如果信号被进程阻塞block它会一直停留在未决队列里直到解除阻塞。递送Delivery信号被进程接收的时刻。对于未被阻塞的信号递送几乎是即时的。处理Handling进程对信号作出反应。三种方式执行默认动作通常是终止或暂停、忽略、执行自定义的信号处理函数。这里有个关键点普通信号非实时信号是不排队的。意思是如果进程还没来得及处理一个信号同样的信号又来了几个内核只记录“有一个这样的信号待处理”不会为每个信号建立一个队列条目。这就是很多新手踩的第一个坑——以为发了 N 次信号处理函数就会被调用 N 次实际上可能只调用一次。这个不排队的特性放在后面的“常见问题”里细说这里先记住结论。1.3 信号与门铃的类比为什么异步是个好东西理解异步这件事最有用的一个类比是门铃。想象你在房间里专心写代码快递员到了楼下按门铃。门铃响了信号产生你听到声音递送放下手头工作去开门处理。在这个模型里你不会每写一行代码就去楼下看一眼快递到了没有那是轮询polling。门铃响的时机你无法预测可能在你写第一行时就响也可能写完整整一章才响异步。你可以选择不理会门铃忽略信号也可以选择贴个纸条“放门口就好”改变默认处理方式但你不能阻止快递员按门铃除非你拔掉门铃线——那就是阻塞信号让信号无法递送。对比一下轮询模式如果没有信号这种异步通知机制进程想知道“有没有新数据到了”“子进程退出了没有”“定时器到期了没有”就只能不停地主动去查。查得频繁浪费 CPU查得不频繁响应延迟。信号机制把“何时去查”的判断交给内核进程只在信号到达时才做出反应CPU 利用率和响应及时性都得到保障。这就是信号的底层价值它是对“主动查询”的一种替代方案让进程在等待外部事件时不必空转。你可以回忆一下日常排查问题时top里看到的那些 sleep 状态进程——它们中的很多就是在等待某个信号来唤醒而不是在忙等。2. 认识信号家族常用信号逐个拆解2.1 先看看清单那些天天打照面的信号Linux 上执行kill -l就能列出全部信号。传统 Unix 信号编号 1~31POSIX 实时信号编号 32~64日常打交道最多的是前 31 个。我把最常用的整理成一张表默认行为列也一并标注信号编号触发场景默认行为SIGHUP1终端挂断或控制进程退出终止进程SIGINT2键盘中断CtrlC终止进程SIGQUIT3键盘退出Ctrl\终止并生成 core 转储SIGKILL9kill -9强制杀死终止不可捕获、不可阻塞SIGSEGV11段错误非法内存访问终止并生成 core 转储SIGPIPE13写管道但读端已关闭终止进程SIGALRM14alarm()或setitimer()定时器到期终止进程SIGTERM15kill默认发送的终止信号终止进程SIGCHLD17子进程停止或退出忽略SIGCONT18让暂停的进程继续运行继续运行SIGSTOP19暂停进程CtrlZ 的底层机制暂停不可捕获、不可阻塞SIGTSTP20终端挂起CtrlZ暂停进程这张表里我标了两个“不可捕获、不可阻塞”的异类SIGKILL 和 SIGSTOP。这是内核的刻意设计——如果所有信号都能被进程屏蔽那系统管理员就永远没办法强制终止一个失控进程了这相当于给操作系统留了一把“物理钥匙”。2.2 几个容易混淆的信号SIGTERM vs SIGKILL、SIGHUP 的经典用途新手最常问的一个问题是“kill和kill -9有什么区别”这里的区别本质上就是 SIGTERM 和 SIGKILL 的区别。kill命令默认发送 SIGTERM编号 15。SIGTERM 是“有礼貌的请求”内核通知进程“请你退出”进程可以捕获这个信号在退出前完成清理工作——释放数据库连接、把缓冲区的日志刷到磁盘、删除临时文件、通知上游服务“我要下线了”。这也是为什么大多数服务管理工具systemd 的Stop、Docker 的docker stop默认发送的都是 SIGTERM。而 SIGKILL 是“物理消灭”内核直接终止进程不给任何处理的机会。进程连“临终遗言”都来不及说所有未刷盘的缓冲区数据直接丢失任何自定义清理代码都不执行。所以一个成熟的运维流程里kill -9永远应该是最后手段——先用 SIGTERM 给进程一个优雅退出的机会等几秒钟还赖着不走再上 SIGKILL。再说 SIGHUP这是一个很有故事的信号。它诞生于终端时代用户挂在终端上的进程终端断线后内核发送 SIGHUPHang Up挂断给该终端下的进程组。默认行为是终止——想想也是合理的用户都走了你还跑给谁看后来守护进程daemon发扬了 SIGHUP 的另一个用途重新读取配置文件。因为守护进程没有终端系统管理员要让它重新加载配置总不能重启进程重启有服务中断的风险于是约定俗成给守护进程发 SIGHUP让它重新读配置。Nginx、sshd 都有这个习惯——kill -HUP $(cat /var/run/nginx.pid)就是经典的平滑重载配置操作。2.3 信号的“生命周期”产生、排队、递送、处理前面我把生命周期分成四个阶段这里再展开说一下“排队”这个细节因为它和实时/非实时信号的差异直接相关。对于标准信号编号 1~31内核在每个进程里维护一个pending 位图每个信号占一位这一位一旦置 1就表示“这个信号处于未决状态”。如果进程正在处理 SIGINT此时又来了一个 SIGINT内核发现 pending 位图上 SIGINT 这一位本来已经是 1 了就直接丢弃这个新信号——这就是“不排队”的底层原因。位图只有一位无法记录“来了多少次”只能记录“有没有来过”。POSIX 实时信号编号 32~64则不同内核为它们维护的是队列每个信号都可以排队可以附带数据借助sigqueue()发送到达的顺序也有保证。这就是为什么实时信号在一些对可靠性要求极高的场景比如实时工业控制中会用到——但在日常业务开发里99% 的场合标准信号就够用了。这个机制直接导出一个实战经验不能依赖发送信号的次数来传递计数信息。比如某个脚本连发 5 次 SIGUSR1希望触发 5 次处理逻辑——这完全是错误预期。正确的做法是收到一次信号后去读取文件、共享内存、消息队列里的“任务清单”有多少处理多少。3. 两种处理方式signal() 与 sigaction() 的选型解析3.1 signal()老接口的便利与陷阱写 C 语言的信号处理第一反应往往是signal()函数#include stdio.h #include signal.h #include unistd.h void handler(int sig) { write(STDOUT_FILENO, catch SIGINT\n, 13); } int main() { signal(SIGINT, handler); for (;;) { sleep(1); } return 0; }这段代码能跑也很简洁。但我要泼一盆冷水在实际项目里我几乎不用signal()而是用sigaction()。原因有几个。第一signal()在不同 Unix 系统上的语义有历史差异。早期的 System V 版本里信号处理函数执行完一次后信号处置方式会被重置回默认值SIG_DFL这意味着处理函数执行期间再次到达的同种信号可能会按默认行为终止进程。虽然 Linux 的signal()实现是 BSD 语义不会被重置但如果你写跨平台代码这个差异就是个雷。第二signal()无法精细控制信号处理的行为。它不能指定“在执行处理函数期间阻塞哪些其他信号”也不能获取当前的信号处置方式。第三signal()不支持实时信号的排队和带参传递。想用sigqueue()传数据signal()无能为力必须上sigaction()。因此结论很直接新写的代码一律用sigaction()。signal()适合在快速写个小测试程序、验证信号机制时用。3.2 sigaction()结构体里的门道sigaction()的函数签名如下#include signal.h int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);核心是struct sigaction这个结构体struct sigaction { void (*sa_handler)(int); // 信号处理函数 sigset_t sa_mask; // 处理期间要阻塞的信号集 int sa_flags; // 行为标志 void (*sa_sigaction)(int, siginfo_t *, void *); // 配合 SA_SIGINFO 使用 };注意这是一个联合体我为了直观省略了 union 的写法sa_handler和sa_sigaction共用一块内存。如果你想用带siginfo_t的处理函数能拿到发送者 PID、信号编号、附加数据等需要设置sa_flags里的SA_SIGINFO标志位并把函数指针赋给sa_sigaction字段。sa_mask是容易被忽略却很重要的一个字段它指定了一个信号集当当前信号的处理函数正在执行时这个信号集里的信号会被自动阻塞。举个例子你在处理 SIGINT 时不希望又收到 SIGTERM 把事情搞乱就把 SIGTERM 放进sa_mask这样 SIGTERM 会等到 SIGINT 处理函数返回后才被递送。注意被处理的信号本身也会在它自己的处理函数执行期间被阻塞防止同一信号递归打断这个行为是内核自动保证的不需要手动往sa_mask里加自己。sa_flags里还有几个常用的SA_RESTART让被信号打断的系统调用自动重启这个太关键了后面专门讲 EINTR。SA_SIGINFO使用sa_sigaction作为处理函数可以获得更多细节。SA_RESETHAND处理完一次后恢复默认行为System V 语义。SA_NOCLDWAIT作用于 SIGCHLD 时子进程退出后不变成僵尸进程但是慎用会丢失子进程的退出状态。3.3 实操代码完整实现一个优雅退出程序理论讲完上实操。这里我给出一个“优雅退出”的完整示例它做了这么几件事捕获 SIGINT 和 SIGTERM收到后设置一个全局标志位让主循环自然退出。捕获 SIGUSR1用于触发一次配置重载这里用日志打印模拟。在信号处理函数里只做异步安全的最简操作后面会讲为什么。#include stdio.h #include signal.h #include string.h #include unistd.h #include stdlib.h static volatile sig_atomic_t g_running 1; static volatile sig_atomic_t g_reload 0; static void handle_signal(int sig) { if (sig SIGINT || sig SIGTERM) { g_running 0; } else if (sig SIGUSR1) { g_reload 1; } } static void setup_signal(int sig) { struct sigaction sa; memset(sa, 0, sizeof(sa)); sa.sa_handler handle_signal; sigemptyset(sa.sa_mask); sa.sa_flags SA_RESTART; if (sigaction(sig, sa, NULL) -1) { perror(sigaction); exit(1); } } int main() { setup_signal(SIGINT); setup_signal(SIGTERM); setup_signal(SIGUSR1); printf(process started, pid%d\n, getpid()); while (g_running) { if (g_reload) { g_reload 0; printf(reload config...\n); // 这里做真正的配置读取逻辑 } // 模拟业务逻辑 sleep(1); } printf(process exiting gracefully\n); return 0; }几个细节你可能注意到了g_running和g_reload声明成volatile sig_atomic_t而不是普通的int。sig_atomic_t是保证对信号处理函数中的读写是原子的整数类型避免读到半更新状态。volatile是防止编译器把变量优化到寄存器里——信号处理函数和主循环之间共享这个变量如果编译器自作聪明地缓存了副本主循环可能永远看不到更新。信号处理函数里我没有调用printf而是直接把标志位置位由主循环去响应。这是一个原则性问题下一节详细解释。编译运行一下gcc -o graceful graceful.c ./graceful另开一个终端执行kill -USR1 pid程序输出 reload config执行kill -TERM pid程序打印 exiting 信息后退出。对比一下直接kill -9退出时没有清理日志——这就是优雅退出和强杀的区别。4. 进阶实践定时器信号、子进程协作与 Shell 层级的信号处理4.1 用 SIGALRM 实现定时任务alarm()是每个 C 程序员最早接触的定时器接口之一#include stdio.h #include signal.h #include unistd.h void on_alarm(int sig) { printf(tick\n); alarm(1); // 再次设定期时器实现周期性 } int main() { signal(SIGALRM, on_alarm); alarm(1); for (;;) { pause(); // 挂起直到收到信号 } return 0; }这个程序每秒打印一次 “tick”。原理是alarm()让内核在指定秒数后向当前进程发送 SIGALRM默认行为是终止进程我们把它捕获并重新设定下一次闹钟。alarm()的粒度是秒级只支持一个定时器。更精细的场景用setitimer()或timer_create()setitimer()支持微秒级精度且支持三种计时模式ITIMER_REAL、ITIMER_VIRTUAL、ITIMER_PROF。ITIMER_REAL 到期发送 SIGALRMITIMER_VIRTUAL 只在进程用户态执行时计时到期发送 SIGVTALRMITIMER_PROF 统计用户态内核态耗时到期发送 SIGPROF。timer_create()是 POSIX 定时器支持高精度、多个独立定时器甚至可以用实时信号携带超时值。不过在 Linux 上实现高精度的定时任务调度我的个人建议是优先考虑timerfd这个话题稍微超纲这里留个引子timerfd_create()把定时器事件绑定到一个文件描述符上配合epoll比信号定时器干净得多因为你完全绕开了信号处理函数里的种种限制。4.2 SIGCHLD 与子进程回收fork()之后子进程退出时内核会向父进程发送 SIGCHLD 信号。如果父进程不对这个信号做任何处理子进程退出后就会变成僵尸进程zombie占用一个进程表项直到父进程调用wait()/waitpid()来回收。我见过不少生产事故父进程跑成了守护进程fork()出的子进程意外退出但父进程没wait()于是每个失败的子进程都变成僵尸。僵尸越积越多进程号枯竭最终整个服务不可用。正确的做法之一就是在父进程里捕获 SIGCHLD并在处理函数里调用waitpid()#include stdio.h #include stdlib.h #include signal.h #include sys/wait.h #include unistd.h #include string.h static void on_sigchld(int sig) { int status; pid_t pid; // WNOHANG 保证如果没有子进程退出就立即返回不会阻塞 while ((pid waitpid(-1, status, WNOHANG)) 0) { printf(child %d exit, status%d\n, pid, WEXITSTATUS(status)); } } int main() { struct sigaction sa; memset(sa, 0, sizeof(sa)); sa.sa_handler on_sigchld; sigemptyset(sa.sa_mask); sa.sa_flags SA_RESTART | SA_NOCLDSTOP; // 只在子进程退出时通知停止不通知 sigaction(SIGCHLD, sa, NULL); pid_t pid fork(); if (pid 0) { // 子进程 exit(42); } // 父进程持续运行 for (;;) { sleep(1); } return 0; }注意这里用了while循环配合WNOHANG反复调用waitpid()原因仍然是信号不排队的问题如果瞬间多个子进程退出可能只触发一次 SIGCHLD如果处理函数里只waitpid()一次就可能漏收僵尸。循环回收直到返回 0没有更多退出的子进程才是稳妥做法。SA_NOCLDSTOP标志的意义是只关心子进程退出不关心子进程因信号被暂停SIGSTOP/SIGTSTP的事件避免无谓的唤醒。4.3 Shell 脚本中的 trap 陷阱命令信号处理不只是 C 语言的专利。写 Shell 脚本时trap命令是你最好的朋友。它的基本用法是trap echo catch signal INT TERM意思是当脚本收到 SIGINT 或 SIGTERM 时执行echo。更常用的场景是清理临时文件#!/bin/bash tmpfile$(mktemp) trap rm -f $tmpfile; echo cleanup done; exit 1 INT TERM EXIT echo tmpfile: $tmpfile while true; do sleep 1 done这里我把EXIT也放进了 trap 列表。EXIT是 Bash 扩展的伪信号脚本无论正常结束还是异常退出都会触发该 trap。这样就能保证临时文件一定会被清理不会残留在 /tmp 里。有几个 trap 的细节值得强调如果脚本里启动了后台子进程kill脚本主进程不会自动传递信号给子进程。要妥善处理可以在 trap 里显式kill后台任务的 PID。如果希望忽略某个信号trap 目标可以写空字符串trap INT此时进程收到 SIGINT 不做任何事。这在写一些“不可中断”的安装脚本时很有用。trap 字符串中的变量展开时机很微妙。用单引号包裹时变量在 trap 执行时才展开用双引号包裹时变量在 trap 设置时就展开。初学者在这个细节上容易踩坑建议统一用单引号。5. 常见问题与排查技巧实录5.1 信号丢失非实时信号的排队问题这是最经典的“幽灵问题”。现象是程序明明收到多次信号处理函数只执行了一两次。原因我在信号生命周期里说过了——标准信号用位图维护 pending 状态相同信号不排队。但这背后还有一个进阶场景处理函数还没执行完新信号又来了。此时如果新信号被阻塞比如正在处理该信号它会停留在 pending 位图上处理函数返回后这个信号就会被递送。如果期间又来了好几个同种信号位图还是只记录“有一个”处理函数只会再执行一次。所以凡是想通过“信号次数”来驱动逻辑的设计都要重新审视。我的经验是信号只当“敲门砖”用真正的信息装在文件、管道或共享内存里。收到信号后去读数据数据有多少就处理多少不依赖信号本身的次数。5.2 EINTR被信号打断的系统调用这是一个让很多人挠头的错误。你写了一个阻塞式read()等待客户端发送数据然后程序收到一个信号处理函数执行完后read()返回了 -1errno被设置为EINTR。为什么因为系统调用在等待期间被信号中断内核会先切换到信号处理函数等处理函数返回后系统调用是否继续由 SA_RESTART 标志决定。如果设置了这个标志内核会自动重新启动系统调用对应用无感知如果没有设置系统调用返回EINTR必须由应用自己决定要不要重试。这种错误在 Java 里体现为InterruptedException在 C 里就是EINTR。解决方式在sigaction()里设置sa_flags | SA_RESTART。大多数情况下这能让read()、write()、wait()这类系统调用自动重启。对于某些不支持自动重启的系统调用如poll()、epoll_wait()、nanosleep()即便设置了 SA_RESTART 也可能返回 EINTR。这时必须手动重试do { ret epoll_wait(epfd, events, maxevents, timeout); } while (ret 0 errno EINTR);我在生产环境里排查过一起“偶发请求超时”最后定位就是epoll_wait()被周期性信号中断返回 EINTR 后代码直接 break 出循环导致这一轮的连接没有被及时处理。修复方式就是加了一个 EINTR 重试循环超时问题彻底消失。5.3 死锁与重入在信号处理函数里别乱调函数这条经验我愿称之为“信号处理的铁律”。信号处理函数执行时主流程可能正处于一个随机的位置——可能刚给某个互斥锁加了锁可能正在malloc()内部操作堆的内存可能正在调用某个库函数的中间步骤。如果处理函数里也调用了printf()内部可能加锁、malloc()、free()、pthread_mutex_lock()这类非异步安全函数就可能发生死锁处理函数想拿主流程已经拿住的锁而主流程被信号打断永远不释放。堆损坏malloc()是线程不安全的且有内部锁处理函数里再调malloc()极可能破坏堆元数据产生随机崩溃。POSIX 明确规定在信号处理函数里可以安全调用的函数是有限的统称“异步安全函数”async-signal-safe。常见的有write()、read()、open()、close()_exit()注意不是exit()kill()、getpid()、sigaction()waitpid()配合 WNOHANG而不安全的包括printf()、malloc()、free()、pthread_*、syslog()等。结合我自己的实践最稳妥的模式是处理函数里只设置标志位sig_atomic_t主循环里再执行真正的业务逻辑。 如果非要立即响应也只用write()往管道里写一个字节主循环用poll()监听管道可读再去做重活。5.4 排查信号问题的实用工具最后给出一套我常用的信号问题排查工具链遇到“疑似信号问题”时按顺序排查kill -l列出系统支持的全部信号及编号。记不住编号时的查表神器。/proc/pid/status查看进程的信号相关信息。关键字段SigPnd挂起的信号、ShdPnd进程级挂起、SigBlk阻塞的信号、SigIgn忽略的信号、SigCgt捕获的信号以十六进制位图显示。我曾经用这个确认过一个进程“是不是漏了 SIGTERM 的捕获”——一看SigCgt位图就真相大白。strace -p pid跟踪系统调用能直观看到信号打断系统调用的现场。-e tracesignal可以只追踪信号相关调用。gdb调试时用handle SIGSEGV stop设置断点排查段错误信号。一次排查“进程收不到 kill 信号”的过程我很推荐新手复现一下起一个 sleep 进程手动编辑/proc/pid/status是做不到的权限不允许但如果进程阻塞了信号SigBlk对应位会是 1。你会发现kill一个阻塞了 SIGINT 的进程进程确实不会终止——不是信号丢了而是它被阻塞在 pending 队列里了。写在最后的一点个人体会开头提到的那个线上“假死”事故最后修复其实只改了三行代码给sigaction加上SA_RESTART给epoll_wait加上 EINTR 重试。但这三行代码背后的理解成本是好几本 Linux 编程书和无数次踩坑攒下来的。信号处理就是这样——平时静默无声像风一样让人感觉不到存在可一旦你在不合适的地方做了不合适的事它就能让整个系统在“风中凌乱”。我个人最大的心得是对信号保持敬畏但不必恐惧。把“信号只做通知、不做业务”这个原则焊死在脑子里再熟练运用sigaction的几个关键标志位绝大多数信号相关的问题都可以在设计阶段就规避掉。如果你正在做一些网络服务、守护进程或者容器相关的工作强烈建议亲手写一遍这篇文章里的小例子——不是背接口而是把信号在你脑子里的“运行模型”搭起来。模型通了代码就通了。
企业数字化 ERP 产品动态
相关推荐
校园文件管理系统源代码实战:部署、权限控制与二次开发指南 简介:一套面向校园网环境的文件管理系统源代码,覆盖学校班级文件共享、课件资源管理、内容发布与存储备份等典型场景。系统由桃源企业文件管理系统V2.4演进而来,在通用文件功能的基础上强化了文件发布、教育课件管理和权限控制能力࿰… · 2026/9/26 17:11:19
三网合一话费余额查询API源码部署:通道选型与接口设计 简介:这是一份2024年三网合一话费余额查询接口系统源码包,基于ThinkPHP6.0框架开发,面向需要搭建话费查询服务的PHP开发者、站长或通信行业从业者,覆盖移动、联通、电信三网余额查询场景,主要解决用户中心在线查余额、… · 2026/9/26 17:11:19
ghcr.io镜像拉不下来?亲测有效的六种加速与搬运方案 1. 为什么每次 docker pull ghcr.io 都卡在半路:一次拉取背后的完整链路群里又有人喊 ghcr.io 镜像拉不动了。这种事我一年能碰上好几次:docker pull ghcr.io/xxx/yyy:latest敲下去,进度条长时间停在 0%,等几分钟直接弹i/o timeou… · 2026/9/26 17:11:19
2026硬核测评:5款主流AI简历匹配工具横评,TaoToken统一Key接入ATS关键词暴击实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 17:37:38
FLUX 3 Action开源7B世界动作模型,刷新RoboLab-120 SOTA 1. 先把标题拆开看:FLUX 3 Action到底开源了什么东西1.1 "7B WAM RoboLab-120 SOTA"这三个词放在一起有多罕见说实话,刚看到这条消息的时候,我的第一反应是有点不太信。原因很简单:在过去两年里,"开源… · 2026/9/26 17:37:31
数据手套如何破解物理AI手部数据稀缺难题? 前阵子帮一个做轮式机器人的团队调试抓取管线,对方反馈最大的问题不是视觉识别,而是机械臂在拿到物体之后的那几百毫秒——手该合多紧、手指怎么跟随物体轮廓变形。他们手里有大把的RGB-D数据,但缺的恰恰是手部本身的高精度运动数据。后来我们… · 2026/9/26 17:37:31
Codex响应接口local proxy failed故障排查指南 1. 这不是“报错”,是 Codex 与 CC Switch 协同链路中的一次典型握手失败 最近两周,我在三个不同客户现场、两个内部开发组、以及五个技术交流群的高频提问里,反复看到这句日志: cc switch local proxy failed while handling co… · 2026/9/26 17:37:31
Self-Speculative Decoding:文档OCR的范式跃迁 1. 这不是“更快的OCR”,而是一次文档理解范式的迁移 你有没有遇到过这样的场景:扫描一份手写会议纪要,Tesseract跑完要8秒,结果漏掉三行小字批注;用阿里云OCR识别一页带复杂表格的财务报表,API返回后发现“… · 2026/9/26 17:37:23
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍 简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践 一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46