尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Linux信号机制详解:从异步通知到sigaction实战与面试考点
写Linux系统编程绕不开的一个坎就是信号。你程序跑得好好的终端里按下CtrlC进程就像被刀砍一样直接没了有些老练的服务进程却完全不一样收到终止信号以后会先停掉新请求、把任务队列收一收、记一笔日志再自己退出。这两种行为背后就是同一个机制在起作用——信号signal。信号本质上是内核发给进程的异步通知把“你的进程发生了某件事”这个信息强行递到你面前。这篇笔记我把信号机制的整体设计、核心API、实操过程、踩坑记录和面试考点一次讲透适合写多进程服务的、搞嵌入式Linux项目的、以及准备Linux面试的人慢慢翻。1. 信号机制的整体设计与设计思路1.1 信号不是一个“事件循环”而是异步通知很多刚开始做Linux开发的人会把信号往“事件循环”“消息队列”那个方向上想这是理解信号最容易被带偏的地方。事件循环是你主动去轮询、去取事件消息队列是你把消息放到一个队列里由接收方按顺序取走。而信号是内核主动打断你正在执行的代码把控制权硬生生切到一个处理函数里处理完再切回来。它符合“异步”这三个字最原始的定义我不知道它什么时候来甚至不知道它会不会来但它来了我就得响应。打个比方你正在工位上专心写代码同事有事找你不会先往你桌上放一张纸条等你回头看到而是直接走过来拍你肩膀。你放下手头的工作听完他说话再继续写代码。信号就是这个“拍肩膀”。内核拍、硬件拍、别的进程也能拍。这种设计天然适合用来做“紧急通知”CtrlC想让你尽快收拾现场退出kill -9想让你无条件消失SIGSEGV说明内存访问已经越界了。也正因为它会打断正在执行的任意代码所以信号处理函数里的操作受到严格限制这个后面在第四章展开细说。先记住一个结论信号不是IPC里那种“自己取数据”的机制它是“被通知”的机制重在打断和通知不重在传数据。1.2 一个信号从产生到处理要经过哪些阶段一个信号从源头到你看到它被处理至少要经过三个阶段产生generation、未决pending、递送delivery。产生这个好理解按下CtrlC、程序里调用raise、别的进程用kill函数发信号、系统内部因为除零或段错误触发这些都是产生。未决是信号已经产生但还没有真正交给进程处理的状态。递送则是信号真正到达进程、开始执行对应动作。这里有个值得细品的点未决状态之所以存在是因为信号可能被阻塞。进程可以调用sigprocmask把一个信号加入阻塞集合在阻塞期间如果来了这个信号内核不会立即递送它而是把它标记为未决。等解除阻塞内核再把未决信号递送出去。如果阻塞期间同一个标准信号来了好几次系统只会记录一次——标准信号不排队。这是信号机制的一个大坑也是和实时信号最大的差异。实时信号SIGRTMIN到SIGRTMAX才会排队可以携带更多信息。递送之后进程按三种方式之一响应忽略它、用默认动作处理、或者调用自定义的处理函数。注意“忽略”和“阻塞”不是一回事忽略是在递送那一刻选择不理它阻塞是把递送这个动作推迟到解除阻塞以后。还有一个容易混淆的坑SIGKILL和SIGSTOP这两个信号既不能被忽略也不能被捕获更不能被阻塞。它们是内核留给系统管理员的“最后手段”任何进程都无法抵抗这也是kill -9能杀掉几乎所有僵死进程的原因。1.3 信号在IPC工具箱里的位置什么时候该用它Linux下面进程间通信手段多得很管道、消息队列、共享内存、socket。信号在里面算哪一种某种程度上它根本不算“通信”因为它能携带的信息量非常少标准信号连一个整数参数都带不了只能靠信号编号本身表达“发生了哪类事情”。但它在另一个维度上是其他手段替代不了的实时性。管道和消息队列都得靠接收方主动去读就算用epoll加非阻塞也还需要一个读取动作信号来了以后不管你当前在哪个函数里内核都会替你把控制流打断。所以最适合信号的场景是“侧边栏事件”而不是“业务数据流”。最常见的几个用途通知进程退出或重载配置、通知父进程子进程状态变化、在程序内部实现超时闹钟、处理器之间做简单的握手。你不需要在信号里塞业务数据。谁发的、什么时候发的、信号编号是多少就够组织出很多实用逻辑了。比如服务进程收到SIGHUP就重新读配置收到SIGTERM就优雅退出收到SIGCHLD就清理僵尸子进程。信号做的是“通知调度”数据本身放在配置文件里、放在共享内存里、放在数据库里信号只负责说一声“该干活了”。2. 核心API解析注册、发送、等待、阻塞2.1 一套最常用的信号API全景先过一遍最常用的接口心里有个总览后面的实操环节全都会用到。信号处理的注册靠signal和sigaction发送靠kill、raise、alarm等待和阻塞靠pause、sigsuspend、sigprocmask、sigpending。这几组接口互相配合就已经能覆盖绝大多数开发需求。kill函数是给进程或进程组发信号的主力。pid传正数就是发给指定进程传0是发给当前进程组传-1是发给所有有权限发送的进程。要注意权限问题普通进程只能向和自己同一UID的进程发信号跨用户发信号基本会被拒绝。raise就是给自己发信号在多线程环境下只发给当前线程。alarm闹钟则是内核在指定秒数后向当前进程发送SIGALRM这个经常用来做超时控制。这里有一个很多新手会忽略的点alarm是一次性的。闹钟到期发出SIGALRM以后如果想继续定时必须在信号处理函数里再次调用alarm重新设定。如果当初指望靠这一个调用实现周期定时那程序跑到第二个周期就会停摆。这也侧面说明写信号代码必须自己把“上下文状态”管理清楚。2.2 用sigaction而不是signal这不是洁癖是规避未定义行为信号注册函数有两个远古的signal和现代的sigaction。很多教材为了少讲点内容只讲signal去网上考古也常看到signal(SIGINT, handler)这种写法。但实际项目里我强烈建议只用sigaction。原因很简单signal的语义在POSIX标准里是“未定义”的。不同Unix系统对它解释不一样。有的系统里signal注册的处理函数在处理完一次以后会被重置为默认动作你要想持续监听同一个信号就得在handler里再次调用signal重新注册这中间会留出一个窗口信号来了就按默认动作处理可能导致进程直接退出。Linux的glibc内部虽然把signal封装成了类似BSD语义的行为多数情况下会自动重启被打断的系统调用但这属于发行版实现细节不是标准承诺。你这段代码将来要移植到别的Unix系统行为可能完全变样。再看sigaction它把整套配置放在一个结构体里一次性把你关心的事情全部定清楚。关键配置有三个sa_handler指定普通处理函数sa_sigaction指定带siginfo_t详细信息的处理函数两者必须选一另一个置空sa_mask在调用处理函数期间要额外阻塞哪些信号sa_flags是可选的开关组合。sa_flags里最常用的几个我列个表标志行为什么时候用SA_RESTART被该信号打断的系统调用自动重启希望read、write这类操作不因信号返回EINTRSA_SIGINFO使用sa_sigaction作为处理函数可获取信号来源等细节需要知道信号发自哪里、附带数据SA_NOCLDSTOP子进程停止时不再向父进程发SIGCHLD多进程框架里只想关心子进程退出不关心暂停SA_RESETHAND处理函数执行后重置为默认行为模拟信号的“一次性”处理语义实际开发中最常见的是SA_RESTART。没有它的话一个慢速设备上阻塞着的read会被信号中断并返回EINTR错误。你如果没对EINTR做重试处理程序就以为文件读失败了轻则出错重则提前退出。这一点第四章节会专门讲因为它是线上程序被信号坑得最多的地方。2.3 sigprocmask、sigpending、sigsuspend阻塞与等待的艺术信号处理的注册是“怎么处理”和它配套的还有一套“暂时不想处理”的机制。sigprocmask可以把一组信号加入当前线程的阻塞集合也可以从集合里移除。这个函数接受三个参数how指定操作方式是SIG_BLOCK加入阻塞、SIG_UNBLOCK移除阻塞还是SIG_SETMASK整组替换set是本次要操作的信号集oldset可以拿到旧的阻塞集合用于事后恢复现场。既然信号在阻塞期间不会递送那怎么知道它到底来了没有用sigpending。它返回一个信号集里面装的就是当前已经产生但仍在阻塞的信号。这个函数在排查问题的时候特别有用怀疑某个信号被阻塞了、迟迟没触发处理函数打出来一看就知道。比这两个更高级一点的是sigsuspend。它做的事情很巧妙先把进程的阻塞集合换成参数指定的集合然后立即挂起等待信号到来一旦有信号递送并执行完处理函数它才返回。这个“换阻塞集合”和“挂起等待”是一个原子操作中间不会插入别的步骤。为什么这么设计因为如果你用sigprocmask解除阻塞再调用pause等待这两个操作中间插了一个窗口信号恰恰在你解除阻塞之后、还没进入pause之前到达那它执行完处理函数以后没有任何东西让进程挂住该等的信号白白等掉了。这种问题属于竞态条件非常阴险。用sigsuspend一步到位就把这个窗口消灭了。这也是信号编程和普通编程截然不同的地方很多代码难写不是API不熟练而是“原子性”没想明白。谁能在正确的位置保证操作不会被信号插队谁就能写出靠谱的信号逻辑。3. 实操写一个能优雅重载配置的守护进程3.1 场景设计为什么必须用信号来做这件事我拿一个具体场景来把上面的API串起来。假设你维护一个常驻服务它从配置文件里读监听端口、日志等级这些参数。以前上线以后调整参数都要去改代码、重编译、重启服务。重启服务意味着连接断开、请求失败、内存里的临时状态全部丢失。理想的方式是服务不重启只是重新读一遍配置热生效。在网络服务里这种“重载配置”的触发方式传统上有几种比如定期秒级轮询配置文件修改时间或者提供一个管理接口手工触发。但最轻量、最符合Unix惯例的是信号方式发送SIGHUP给服务进程进程收到以后重新解析配置。为什么用SIGHUP按照Unix惯例SIGHUP本来就代表“控制终端挂断”后来被扩展成了“重新初始化”的语义很多知名服务比如nginx、sshd都是这样约定俗成的。同样SIGTERM代表“请优雅退出”SIGCHLD代表“有一个子进程状态变了”。这几个信号组合起来就是一个完整守护进程的骨架。3.2 关键代码骨架与配置说明我写一个简化的demo父进程负责监听信号收到SIGHUP打印“配置重载”收到SIGTERM标记退出收到SIGCHLD回收子进程防止僵尸进程堆积。这里为了控制长度业务逻辑用打印代替#include stdio.h #include stdlib.h #include string.h #include signal.h #include unistd.h #include sys/wait.h static volatile sig_atomic_t g_quit 0; static volatile sig_atomic_t g_reload 0; static void on_term(int sig) { g_quit 1; } static void on_hup(int sig) { g_reload 1; } static void on_chld(int sig) { int status; while (waitpid(-1, status, WNOHANG) 0) { /* 循环回收所有已退出子进程 */ } } static void setup_handler(int sig, void (*handler)(int), int flags) { struct sigaction sa; memset(sa, 0, sizeof(sa)); sa.sa_handler handler; sa.sa_flags flags; sigemptyset(sa.sa_mask); if (sigaction(sig, sa, NULL) 0) { perror(sigaction); exit(1); } } int main(void) { setup_handler(SIGTERM, on_term, SA_RESTART); setup_handler(SIGHUP, on_hup, 0); setup_handler(SIGCHLD, on_chld, SA_NOCLDSTOP); printf(service started, pid%d\n, getpid()); while (!g_quit) { if (g_reload) { g_reload 0; printf(reload config...\n); } sleep(1); } printf(service exit gracefully\n); return 0; }这里的关键是三个处理函数的职责划分非常干净SIGTERM只负责把退出标志置1SIGHUP只负责把重载标志置1主循环里检查标志再执行真正动作。信号处理函数里不做重活、不调用printf只是改一个volatile sig_atomic_t类型的标志这是书籍和实战经验反复强调的最佳实践。编译运行很简单gcc -Wall -o daemon_demo daemon_demo.c ./daemon_demo kill -HUP $(pgrep -f daemon_demo) kill -TERM $(pgrep -f daemon_demo)运行起来以后你会看到SIGHUP触发时打印了reload configSIGTERM触发时服务在完成当前循环后优雅退出。整个流程非常直观。3.3 复盘SIGCHLD处理里藏着哪些细节这份代码里最容易出问题的其实是on_chld。很多人第一次写的时候会写成一个简单的waitpid不带循环结果服务跑一段时间以后ps一看满屏的defunct僵尸进程。原因在于SIGCHLD信号本身并不排队如果短时间内多个子进程退出内核可能只递送一两次SIGCHLD你一个waitpid只回收了其中一个剩下的子进程就成了僵尸永远没人认领。处理办法就是循环回收只要还有已经退出的子进程就继续waitpid直到返回0为止。这里还要注意waitpid的第三个参数必须用WNOHANG因为信号处理函数会打断当前工作你绝不能在处理函数里阻塞着等一个不知什么时候退出的子进程。WAIT的循环配合WNOHANG是处理SIGCHLD的标准范式但也只是“标准范式”里的基础版本。真正的大型服务框架里往往不在SIGCHLD里做回收而是在主事件循环里统一用信号通知来触发回收这样能把所有逻辑收敛到一个线程里避免信号处理函数的各种限制。另外再看setup_handler里那几行每个信号都是单独调一次sigaction统一封装的好处是不用每个信号都重复写一遍结构体初始化的代码。注意SIGCHLD我用的是SA_NOCLDSTOP而不是SA_RESTART原因也很直白SIGCHLD这种管道型通知本来就不希望被系统自动重启机制干扰我只关心子进程退出不关心子进程暂停继续所以去掉相关的自动恢复语义。4. 常见问题与排查技巧实录4.1 EINTRread被信号打断程序直接“读失败”了这是信号机制里线上出问题最频繁的一类。场景通常是这样的服务进程用epoll或阻塞read监听网络连接同时挂了一个信号处理函数用来处理定时器或退出通知。结果线上日志里开始刷错误一看错误码是EINTR。EINTR的意思是“系统调用被信号打断了”。read、write、accept、recv这类调用如果阻塞等待期间来了一个信号且你没有给这个信号设置SA_RESTART那么内核会让系统调用直接返回错误码设为EINTR。返回以后你的业务代码通常不会自动重试而是判断返回值小于0直接报IO错误甚至关闭连接、退出循环。修复手段有两层。第一层是尽量在sigaction里给不会被破坏语义的信号加SA_RESTART让内核自动帮你重新发起那个系统调用这是最省事的办法。第二层是代码健壮性兜底凡是处理慢速系统调用的返回值遇到EINTR要判断一下是否需要重试。很多成熟的项目里封装一个read、write的辅助函数内部循环处理EINTR就是防御这种中断。记住一点EINTR不是系统调用失败它只是“被插了一脚”需要重新发起千万不要当致命错误处理。4.2 多线程环境里的信号处理函数跑在哪个线程里别乱装单线程时代信号处理比较简单信号来了当前正在执行的代码被打断去跑处理函数。但一旦进入多线程事情就变了。kill函数发送的信号是送给整个进程的这个信号由谁来处理取决于哪个线程的阻塞集合里没有这个信号。默认情况下进程内所有线程共享信号处理函数的注册关系但每个线程都有自己的阻塞信号集合。信号递送到进程时内核会找一个没有被阻塞该信号的线程把处理函数塞进去执行。这意味着你完全无法预测它落在哪个线程上。更麻烦的是如果你在线程A里安装了信号处理函数线程B里什么也没做信号最终可能在主线程里执行也可能在某个随机工作线程里执行取决于当时的调度状态。所以多线程程序的信号处理范式通常是反向操作的不是让信号随便打断某个线程而是在启动其他线程之前用pthread_sigmask把所有希望统一处理的信号全部阻塞掉然后单独创建一个线程在这个线程里调用sigwait或者sigtimedwait等待信号到来。这样不管进程收到多少信号它们都只会被这一个专门的线程接走业务线程永远不被信号打断。这个模式用信号量、网络服务、嵌入式Linux项目里都很常见算是多线程信号编程的标配。4.3 信号处理函数里的“危险动作”为什么不能随便printf另一个高频事故是信号处理函数里动作不干净。我见过有人直接在handler里调printf打印日志调malloc申请内存甚至在函数里加锁。跑起来表面没事但偶尔进程卡死或者数据错乱查几天都查不出来。问题在于信号处理函数可能在任意时刻打断主程序的正常执行。如果主程序正执行到malloc内部信号一来handler里又调malloc这就可能在同一套内存管理数据结构上出现重入冲突数据结构的中间状态被第二个malloc入侵直接内存损坏。printf同理它的内部也涉及全局缓冲区和锁。还有锁操作线程A持锁进入临界区被打断handler所在的线程又去抢同一把锁立刻死锁而且死锁以后没有信号能救因为信号处理还占用着当前线程的执行流。按规定信号处理函数里只能调用async-signal-safe函数这类函数保证在信号处理上下文中可以安全重入。write、read、open、close、_exit这些属于安全名单printf、malloc、free、pthread_mutex_lock都不在名单里。实际项目里最稳妥的做法就是减少handler的思考量阻止标志用volatile sig_atomic_t传递通知用自管道或者signalfd把真正的工作全部挪到信号处理函数的return之后去执行。这句原则值得刻在工位上。4.4 排查信号问题的三个命令级工具遇到信号问题第一反应不应该是猜而是用工具看。kill -l能列出所有信号编号和名称写代码时记不清SIGUSR1到底是几号先敲一下。strace -p 可以跟踪进程的系统调用信号递送时会打印--- SIGTERM ---这样的记录能直接观察信号是什么时候来的、打断在哪个系统调用上。还有一个容易被忽略的好去处/proc/ /status。里面SigBlk、SigIgn、SigCgt几个字段分别显示进程当前阻塞、忽略、捕获的信号集合。如果进程表现异常诡异查看这几个字段往往能迅速定位“哦原来这个信号被忽略了”“原来这个信号被阻塞了”。SigQ字段还能看进程挂起了多少个未决信号。打开proc文件系统读一眼比一遍一遍瞎猜强太多。5. 面试高频考点与工程延伸5.1 8道高频Linux信号面试题你能完整答出几道信号在Linux系统编程面试里几乎是必考题材因为问题短、考察面广、很容易挖深度。我把高频考点整理出8道附上速记思路第一道SIGKILL和SIGSTOP为什么不能捕获答内核把它们定义为“管理员保留信号”必须保证系统在任何情况下都能强制终止或暂停进程否则一个恶意进程只要捕获了终止信号系统就拿它没办法了。第二道标准信号和实时信号的区别答标准信号不排队实时信号可排队实时信号可以携带额外数据标准信号只能靠编号表达信息。第三道signal和sigaction的区别答signal语义因系统而异sigaction语义明确还能设置SA_RESTART、sa_mask等细节项目里用sigaction。第四道信号处理函数里能调用malloc吗答不能malloc不属于async-signal-safe函数可能重入导致内存数据结构损坏安全做法是把标志置位、用write通知事件循环。第五道子进程变成僵尸进程的信号怎么处理答注册SIGCHLD处理函数在handler里循环调用waitpid使用WNOHANG避免阻塞。第六道sigprocmask和sigsuspend有什么区别答sigprocmask只改阻塞集合sigsuspend原子地设置阻塞集合并挂起等待信号避免解除阻塞和挂起之间的竞态窗口。第七道多线程进程里信号是发给进程还是线程答kill发给进程由任意未阻塞该信号的线程接收处理推荐的统一处理方式是在创建线程前阻塞信号专门起线程用sigwait接收。第八道EINTR到底是什么答系统调用被信号打断后返回的错误码是一种“临时失败”正确处理方式是循环重试或者在sigaction里设置SA_RESTART让内核自动重启。面试官最喜欢沿着第三题往下挖你说用sigaction那sa_flags里SA_RESTART解决了什么问题如果不设置会发生什么能接住这一串的候选人通常对信号是真有实操经验。5.2 把信号变成IO事件signalfd和epoll配合的正规玩法传统信号处理函数最大的痛苦就是限制太多printf不行、加锁不行、起线程不行。一个更现代、更符合事件循环架构的替代方案是用signalfd它能把信号变成“文件描述符可读事件”。你先把想要处理的信号统统加入阻塞集合然后用signalfd创建对应的fd再把这个fd交给epoll、select、poll去监听。信号到来后fd变可读你像读普通设备一样读出一个signalfd_siginfo结构体就能知道是哪个信号来了、从哪里来。这样一来信号处理的每一步都发生在正常线程上下文里不再有“打断任意代码”的问题所以可以放心地写复杂的业务逻辑。很多高性能网络服务都是这个路线主循环manbetx网络模型、多线程模型加一个signalfd fd纳入epoll既保留了信号的实时通知能力又规整了编程模型。这个方案唯一的注意事项是得确保目标信号确实被阻塞且不能让信号同时触发传统handler否则两套机制会打架。5.3 给排查信号问题加个“人肉标记”修改进程名的小技巧最后分享一个调试信号问题时的实用小技巧给进程改名。Linux下可以用prctl修改进程名代码里加一行prctl(PR_SET_NAME, my_worker, 0, 0, 0)再用ps -eo pid,comm查看就能看到这个进程变成了my_worker。在排查并发服务时启动十几个同类进程光靠默认的执行文件名字完全分不出谁是父进程谁是子进程给每个角色起不同名字以后你能明确区分信号发给了谁日志精确对位定位效率上一个台阶。要是再配合/proc/pid/status里的SigCgt、SigBlk字段就是一套很完整的信号问题排查工具链了。我个人体会是信号这章内容不亲手跑一遍永远只是纸上谈兵。你找一台Linux机器把上面的demo敲进去开两个终端一边运行一边用kill发各种信号再拿strace盯着看系统调用被打断的过程30分钟就能把那些抽象概念变成身体记忆。踩过几次EINTR和僵尸进程的坑之后你再回来看Linux信号会觉得它不只是一套API而是一整套关于“如何优雅地被打断”的设计哲学。
RELATED

相关推荐

Go语言实现OAuth2:从授权码到Token校验的完整工程实践

Go语言实现OAuth2:从授权码到Token校验的完整工程实践

开始任何服务端项目之前,我总是先问自己一句:这套接口到底要暴露给谁用?如果是自家前端、自家App,那直接上 Session 或简单的 Token 就行;可一旦涉及到第三方应用接入、多端授权、甚至开放平台,OAuth2 就成…

📅 2026/10/5 4:28:46
前后端分离项目实战:基于SpringBoot+Vue的厨艺交流平台开发部署

前后端分离项目实战:基于SpringBoot+Vue的厨艺交流平台开发部署

前后端分离厨艺交流平台系统做下来,我觉得最值得分享的还不是那一堆CRUD代码,而是这套从需求拆解到技术选型、再到前后端联调和最终部署的完整链路。先说清楚这个项目是什么:它本质上是一个以菜谱分享和用户互动为核心的社区型Web应用&#x…

📅 2026/10/5 4:28:46
INT与gRPC网络遥测:精细化运维实战指南

INT与gRPC网络遥测:精细化运维实战指南

简介:这份PDF文档面向HPC与下一代数据中心网络的运维工程师及架构设计人员,聚焦如何借助Network Telemetry技术打破“网络黑盒”,解决大规模复杂网络中流量精细可视、可控以及端到端秒级故障定位的难题。资源包共1个PDF文件,大小约…

📅 2026/10/5 4:28:46
MORE NEWS

更多资讯

📰

OpenCADStudio命令行与脚本自动化完整指南:如何用别名和命令脚本批量完成绘图任务

OpenCADStudio命令行与脚本自动化完整指南:如何用别名和命令脚本批量完成绘图任务 【免费下载链接】OpenCADStudio A CAD application built with Rust — 2D/3D drawing, DWG/DXF support, and GPU-accelerated rendering 项目地址: https://gitcode.com/gh_mirr…

📰

AI Agent 权限边界设计:从沙箱逃逸到最小权限实践

1. 从“AI Agent 逃出沙箱”说起:一个被低估的权限边界问题第一次看到“AI Agent 逃出沙箱”这个说法,我脑子里蹦出来的不是科幻电影里的天网,而是一个特别具体的画面:你给一个自动化脚本开了个容器,让它帮你整理文件、…

📰

上下文工程:AI Agent稳定落地的核心技术

1. 这不是“加长版Prompt”,而是AI Agent的呼吸系统你有没有试过给大模型喂一段超长的用户对话历史、三份PDF摘要、五条实时行情数据,再加一个“请综合判断是否下单”,结果模型要么直接截断、要么逻辑混乱、要么开始胡编乱造?这不…

📰

Roo Code 本地模型卡顿优化:从链路分析到参数配置的完整指南

如果你也试过在 Roo Code 里接本地模型,大概率会碰上这样一种体验:第一句话发出去,光标转圈转得人心慌;好不容易开始出字了,又是一个字一个字往外蹦,像在看慢镜头回放。说“卡顿”都算客气了,对…

📰

同一个Beacon为何只该计数一次:wifit3 WiFi审计工具wlan去重模块完全解析

同一个Beacon为何只该计数一次:wifit3 WiFi审计工具wlan去重模块完全解析 【免费下载链接】wifit3 Wifite but USB-only & cross-platform. 项目地址: https://gitcode.com/GitHub_Trending/wi/wifit3 wifit3 是一款跨平台(Linux / Windows /…

📰

RAG客服机器人实战:原理拆解与工程落地避坑指南

1. 为什么客服机器人总爱“一本正经地胡说八道”先说我自己的真实经历。之前团队做了一个客服机器人,接的是某产品的售后知识库,整理了几百篇 Word 和 PDF 文档,喂给大模型做微调。结果上线第一天就翻车了:用户问“保修期多久”&a…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬