CSAPP Shell Lab 满分攻略:进程组、信号与作业控制避坑指南
简介这份资源是CSAPP计算机系统基础Shell Lab的满分实现参考面向正在修读北大与CMU联合课程、或自学《深入理解计算机系统》系统级编程章节的学生帮助解决Shell实验无从下手、难以拿满分的问题。压缩包内共1个文件为一份C语言源码整体约7KB核心围绕Shell脚本基础、文件与目录操作、管道与重定向、函数与别名、错误处理与调试、环境变量与脚本参数、脚本设计与测试等关键知识点展开并覆盖理解任务要求、设计解决方案、编写与测试脚本、优化调试、文档编写直至提交评估的完整流程。目前已有2709人学习下载可作为对照参考帮助读者理清实验目标、把握评分要点、排查边界条件与异常场景进而深化对操作系统与系统级编程的理解。仅供学习参考请勿抄袭。1. 从零实现一个带作业控制的迷你 ShellCSAPP Shell Lab 到底在考什么很多人第一次打开 CSAPP 的 Shell Lab 时会觉得这不过是写个命令行解释器fork 一下、exec 一下、wait 一下能跑就行。真正动手才发现tsh 的评分脚本会拿几十个用例反复折磨你Ctrl-C 能不能只杀掉前台进程、Ctrl-Z 之后进程有没有真的挂起、fg和bg能不能把作业状态切回来、后台进程退出后有没有僵尸残留。这些行为背后是进程组、信号、终端控制、作业控制四件事的交叉任何一环没对齐用例就红。这篇笔记面向正在做这个实验、或者想自己从零写一个带作业控制的 shell 的开发者。我会把「进程组与会话」「信号处理与竞态」「作业状态机」「回收与阻塞」这几条线拆开讲给出可以直接抄的代码骨架和参数说明也会把当年让我卡了整整两晚的坑标出来。标题里提到的满分思路核心不是堆代码而是把「谁在什么时候收到什么信号」这件事想清楚。2. 进程组、会话与终端作业控制的三根支柱2.1 为什么 fork 之后第一件事是 setpgid在普通程序里fork 出来的子进程和父进程同属一个进程组终端发出的 Ctrl-C 会以 SIGINT 形式发给整个前台进程组。如果 shell 和它 fork 出来的子进程在同一个组里用户按一次 Ctrl-Cshell 自己也会被干掉这显然不是我们要的。作业控制的本质就是让每个作业job独占一个进程组shell 通过切换「前台进程组」来决定键盘信号发给谁。所以子进程 fork 之后、exec 之前必须调用setpgid(0, 0)把自己变成新进程组的组长父进程也要调用setpgid(pid, pid)防止子进程还没来得及执行就被信号打断。这两处都写是因为父子之间存在竞态谁先跑到不确定两边都设一次才能保证无论调度顺序如何进程组一定建好。/* 在 fork 之后父进程和子进程都要执行 */ if (pid 0) { /* 子进程把自己放进新进程组pgid 自己的 pid */ if (setpgid(0, 0) 0) { perror(setpgid child); exit(1); } /* 之后才 exec保证 exec 前进程组已就绪 */ execve(argv[0], argv, environ); /* exec 失败才会走到这里 */ perror(execve); exit(1); } else { /* 父进程同样设置消除竞态窗口 */ if (setpgid(pid, pid) 0) { /* 子进程可能已经 exec 并退出忽略 EACCES 之外的错误 */ if (errno ! EACCES) perror(setpgid parent); } }参数上setpgid(0, 0)的第一个 0 表示「当前进程」第二个 0 表示「以当前进程 pid 作为新组 id」。父进程里setpgid(pid, pid)则是显式指定子进程 pid 和组 id。注意父进程这次调用可能失败因为子进程可能已经 exec 甚至退出此时 errno 常见是 EACCES 或 ESRCH属于正常竞态不要当成致命错误。2.2 tcsetpgrp 把前台交给谁光有进程组还不够终端需要知道「当前哪个进程组是前台」。这靠tcsetpgrp(fd, pgid)完成fd 是终端文件描述符一般是 STDIN_FILENOpgid 是要设为前台的进程组。shell 在启动一个前台作业时要先把该作业的进程组设为前台等它结束后再把自己抢回来。这里有个血泪经验shell 自己启动时可能并不在前台进程组里需要先调用tcgetpgrp检查如果发现自己不是前台就主动把自己设为前台否则后续所有终端操作都会收到 SIGTTOU 而被挂起。很多同学的程序在评分脚本里一跑就卡死就是漏了这一步。/* shell 初始化时确保自己在前台 */ pid_t shell_pgid getpid(); /* 把自己设为进程组组长 */ setpgid(shell_pgid, shell_pgid); /* 抢回终端前台控制权 */ tcsetpgrp(STDIN_FILENO, shell_pgid);tcsetpgrp的第二个参数必须是已存在的进程组 id否则会返回 ENOTTY 或 EINVAL。调用时机上前台作业启动前设一次作业回收后再设回 shell 自己顺序不能反否则会出现「作业还没结束shell 就把终端抢回来」导致信号发错对象的玄学问题。2.3 信号屏蔽与 sigprocmask 的取舍信号处理里最容易被忽略的是竞态父进程刚 fork 完还没来得及把子进程加入作业表子进程就退出并发出 SIGCHLD此时 handler 去作业表里找找不到对应记录于是这个子进程变成僵尸永远回收不掉。解决办法是在 fork 之前用sigprocmask屏蔽 SIGCHLD把子进程加入作业表之后再解除屏蔽让挂起的 SIGCHLD 在安全时机被处理。sigset_t mask, prev; sigemptyset(mask); sigaddset(mask, SIGCHLD); /* 进入临界区屏蔽 SIGCHLD */ sigprocmask(SIG_BLOCK, mask, prev); pid_t pid fork(); if (pid 0) { /* 子进程恢复信号屏蔽字否则 exec 后仍被屏蔽 */ sigprocmask(SIG_SETMASK, prev, NULL); setpgid(0, 0); execve(argv[0], argv, environ); exit(1); } /* 父进程加入作业表 */ add_job(pid, ...); /* 离开临界区恢复原屏蔽字挂起的 SIGCHLD 此刻触发 */ sigprocmask(SIG_SETMASK, prev, NULL);注意子进程里也要恢复屏蔽字否则 exec 之后新程序会继承被屏蔽的 SIGCHLD导致它自己的子进程无法正常回收。这个细节在评分脚本里往往通过「shell 里再跑一个 shell」的用例来暴露不写就等着翻车。3. 信号处理函数怎么写才不翻车3.1 SIGCHLD handler 里只能调异步信号安全函数信号处理函数运行在不可预测的时机它可能打断 malloc、printf 这类非可重入函数造成死锁或内存错乱。所以 handler 里能做的事非常有限只能调用waitpid、write、_exit这类异步信号安全函数。想打印调试信息用write(STDOUT_FILENO, ...)而不是 printf。一个稳妥的 SIGCHLD handler 长这样循环调用waitpid(-1, status, WNOHANG | WUNTRACED)把已退出或已停止的子进程全部收掉然后根据 status 更新作业表状态。用循环是因为一次 SIGCHLD 可能对应多个子进程状态变化只 wait 一次会漏。void sigchld_handler(int sig) { int olderrno errno; /* 保存 errnohandler 返回后要恢复 */ pid_t pid; int status; /* 循环回收WNOHANG 不阻塞WUNTRACED 也报告停止的子进程 */ while ((pid waitpid(-1, status, WNOHANG | WUNTRACED)) 0) { if (WIFEXITED(status) || WIFSIGNALED(status)) { /* 进程已终止从作业表删除 */ delete_job(pid); } else if (WIFSTOPPED(status)) { /* 进程被停止Ctrl-Z标记为 STOPPED */ mark_job_stopped(pid); } } errno olderrno; /* 恢复 errno避免影响主流程 */ }参数说明WNOHANG让 waitpid 在没有可回收子进程时立即返回 0避免 handler 阻塞WUNTRACED让被停止的子进程也能被报告否则 Ctrl-Z 之后作业状态无法更新。olderrno的保存与恢复是硬性要求因为 handler 可能打断主流程中依赖 errno 的判断。3.2 SIGINT 和 SIGTSTP 只转发给前台进程组Ctrl-C 和 Ctrl-Z 在终端里默认会发给前台进程组但 shell 自己如果也在那个组里就会一起中招。正确做法是 shell 捕获这两个信号在 handler 里用kill(-fg_pgid, SIGINT)把信号转发给当前前台作业的进程组负号表示「发给整个进程组」。void sigint_handler(int sig) { int olderrno errno; pid_t fg fg_pgid; /* 全局变量记录当前前台进程组 */ if (fg 0) { kill(-fg, SIGINT); /* 负号发给整个进程组 */ } errno olderrno; }这里的关键是kill的 pid 参数为负时表示进程组 id。如果写成正的 fg只会杀掉组长进程组内其他进程收不到信号用例里「管道中多个进程都要被中断」的场景就会失败。另外 fg_pgid 这个全局变量在主流程里更新时要考虑信号屏蔽避免 handler 读到半更新的值。3.3 用 sigsuspend 等待前台作业结束前台作业启动后shell 需要等它结束或被停止。最朴素的做法是waitpid(pid, status, 0)但这会阻塞期间 SIGCHLD handler 无法及时更新作业表容易出竞态。更稳的模式是用sigsuspend先屏蔽 SIGCHLD检查作业是否还在运行若在运行则用 sigsuspend 挂起并等待信号被唤醒后重新检查。/* 等待前台作业 pid 结束或停止 */ while (job_is_alive(pid)) { sigset_t mask, prev; sigemptyset(mask); sigaddset(mask, SIGCHLD); sigprocmask(SIG_BLOCK, mask, prev); /* 若作业仍存活挂起等待 SIGCHLD */ if (job_is_alive(pid)) { sigsuspend(prev); /* 原子地解除屏蔽并挂起 */ } sigprocmask(SIG_SETMASK, prev, NULL); }sigsuspend(prev)的语义是临时把屏蔽字设为 prev即解除 SIGCHLD 屏蔽挂起进程直到收到信号且 handler 返回后屏蔽字恢复为调用前的值。这样既不会错过 SIGCHLD也不会在检查与挂起之间留下竞态窗口。这个模式是 Shell Lab 里区分「能跑」和「满分」的分水岭。4. 作业表与状态机builtin、fg、bg 怎么落地4.1 作业表的数据结构设计作业表是 shell 的核心状态每个作业记录 pid、pgid、状态RUNNING / STOPPED / DONE、命令行字符串。状态机只有三种迁移RUNNING 收到 SIGTSTP 变 STOPPEDSTOPPED 收到 SIGCONT 变 RUNNINGRUNNING 或 STOPPED 收到终止信号变 DONE。所有信号 handler 只做状态标记真正的清理和打印放在主循环里做避免在 handler 里操作复杂数据结构。typedef enum { RUNNING, STOPPED, DONE } job_state; typedef struct job { pid_t pid; /* 组长进程 pid */ pid_t pgid; /* 进程组 id */ job_state state; char cmdline[MAXLINE]; /* 原始命令行用于 jobs 输出 */ struct job *next; } job_t; static job_t *job_list NULL; /* 全局作业表头 */用链表而不是数组是因为作业数量不确定且删除操作频繁。每个作业只记录组长 pid 即可因为同一作业内所有进程共享 pgid信号按组发。cmdline 要保存原始输入jobs命令输出时需要原样打印。4.2 builtin 命令必须在父进程执行cd、exit、jobs、fg、bg这些 builtin 不能 fork 出去执行因为它们要改变 shell 自身的状态。cd改的是 shell 的工作目录fork 出去改的是子进程的父进程不受影响exit要终止 shell 本身fg和bg要操作 shell 维护的作业表。所以解析出命令后先判断是不是 builtin是就直接在父进程调用对应函数。/* 在主循环里fork 之前先判断 builtin */ if (!strcmp(argv[0], cd)) { if (chdir(argv[1]) 0) perror(cd); return; /* 不 fork直接返回主循环 */ } if (!strcmp(argv[0], jobs)) { list_jobs(); return; } if (!strcmp(argv[0], fg)) { do_fg(argv[1]); /* 把指定作业切到前台并等待 */ return; } if (!strcmp(argv[0], bg)) { do_bg(argv[1]); /* 给指定作业发 SIGCONT后台继续 */ return; } /* 非 builtin走 fork exec 流程 */fg的实现要点先用kill(-pgid, SIGCONT)唤醒被停止的作业再tcsetpgrp把终端交给它然后等待它结束或再次被停止。bg则只发 SIGCONT不抢终端。两者都要更新作业状态且fg等待结束后要把终端抢回 shell。4.3 回收作业与避免僵尸僵尸进程是评分脚本重点检查的对象。每个子进程终止后父进程必须 waitpid 回收否则进程表项一直占用。SIGCHLD handler 里的循环 waitpid 就是干这个的。但要注意handler 里回收后只是标记 DONE真正的删除和打印放在主循环因为 handler 里不能安全地操作链表和调用 printf。/* 主循环里每次读命令前先清理 DONE 的作业 */ void reap_done_jobs(void) { job_t **pp job_list; while (*pp) { job_t *j *pp; if (j-state DONE) { *pp j-next; /* 从链表摘除 */ free(j); } else { pp j-next; } } }用二级指针遍历链表删除节点时不需要额外的 prev 指针代码更简洁。清理时机放在每次打印提示符之前保证jobs输出里不会出现已完成的作业。注意 DONE 状态的作业在被jobs引用后就可以删但如果用户可能马上要fg它就得保留到下一次清理具体策略看用例要求。5. 避坑与排查那些让评分脚本变红的细节5.1 现象Ctrl-C 之后 shell 自己也退出了原因shell 没有把自己移出前台进程组或者没有捕获 SIGINT导致终端把 SIGINT 同时发给了 shell。解决启动时用setpgid把自己设为独立进程组捕获 SIGINT 并在 handler 里只转发给前台作业的 pgid不对自己发。检查tcgetpgrp确认 shell 是否在前台。5.2 现象后台进程结束后变成僵尸jobs 里还在原因SIGCHLD handler 没有循环 waitpid或者 waitpid 的 flags 没带 WNOHANG 导致阻塞或者 handler 里只 wait 了一个子进程。解决handler 里用while ((pid waitpid(-1, status, WNOHANG | WUNTRACED)) 0)循环回收确保一次信号处理掉所有状态变化。5.3 现象fg 之后终端输入没反应原因fg没有调用tcsetpgrp把终端交给作业进程组或者作业被 SIGCONT 唤醒后没有真正拿到前台。解决fg里先kill(-pgid, SIGCONT)再tcsetpgrp(STDIN_FILENO, pgid)然后等待作业结束结束后再tcsetpgrp抢回 shell。顺序不能反。5.4 现象管道命令只杀掉了第一个进程原因kill的 pid 参数用了正的组长 pid只发给单个进程。解决改用负的 pgidkill(-pgid, SIGINT)负号表示发给整个进程组。同时确认 fork 时每个子进程都调用了setpgid加入同一组。5.5 现象评分脚本报「waitpid returned -1 ECHILD」原因handler 里 waitpid 被调用时子进程已经被主流程的 waitpid 回收了或者 SIGCHLD 被屏蔽期间子进程退出解除屏蔽后 handler 找不到子进程。解决统一回收入口只在 SIGCHLD handler 里 waitpid主流程等待前台作业时用 sigsuspend 而不是直接 waitpid避免两处竞争。6. 进阶技巧用 trace 和最小用例把行为钉死做到这里功能基本齐了但离满分还差一步验证。评分脚本不会告诉你哪个用例挂了只会给个总分。我的习惯是自己写一组最小用例配合strace -f -e tracesignal,process观察信号和进程系统调用把每个行为钉死。先准备一个能打印自身进程组和收到信号的测试程序比如一个循环打印 pid、pgid 并在收到 SIGINT 时输出一行再退出的脚本。然后针对每个场景单独跑# 场景一前台作业被 Ctrl-C 中断 ./tsh tsh ./loop # 后台启动 tsh fg %1 # 切到前台 # 此时按 Ctrl-C观察 loop 是否退出、tsh 是否存活用strace -f -e tracesignal,process ./tsh启动可以看到每次 fork、setpgid、kill、waitpid 的调用顺序和返回值。重点看三件事fork 后 setpgid 是否父子都调了、kill 的 pid 是否为负、waitpid 是否在 SIGCHLD 后立即被调用。任何一处顺序不对strace 里都能看出来。再准备一个「作业状态迁移」的检查表每实现一个功能就勾一项场景预期行为检查点Ctrl-C 前台作业作业终止shell 存活kill 参数为负shell 未收 SIGINTCtrl-Z 前台作业作业停止jobs 显示 StoppedWUNTRACED 生效状态更新fg 恢复停止作业作业继续运行并占前台SIGCONT tcsetpgrp 顺序bg 恢复停止作业作业后台运行shell 可输入只发 SIGCONT不抢终端后台作业结束无僵尸jobs 不再显示handler 循环 waitpid管道作业 Ctrl-C组内所有进程终止负 pgid 广播最后说一个我自己的教训当年为了图快把作业表操作直接写进 SIGCHLD handler结果在「一次退出多个后台进程」的用例里链表被并发修改程序随机崩溃查了两天才定位到是 handler 里调了非可重入的 free。后来改成 handler 只标记、主循环清理再没出过问题。信号处理里能少做就少做能延后就延后这条习惯帮我省了无数个调试的夜晚。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

A股情感分析全流程:从数据采集到情绪因子回测

A股情感分析全流程:从数据采集到情绪因子回测

简介:面向金融数据分析、量化投资研究及自然语言处理入门人群,这套Python工程以A股市场为对象,实现完整的股市情感分析流程。项目避开传统财务指标,直接从互联网股票评论中抽取投资者情绪,结合标注语料训练情感分类模型…

2026/10/11 23:24:25 阅读更多 →
基于CelebA与PyTorch的人脸识别:从数据准备到部署完整实践

基于CelebA与PyTorch的人脸识别:从数据准备到部署完整实践

简介:以CelebA为训练数据、基于PyTorch实现的人脸识别神经网络项目包,适合深度学习和计算机视觉学习者快速上手人脸识别模型训练。压缩包共30个文件,以10个py脚本为核心,覆盖网络模型定义、数据加载、训练、测试等完整流程&#x…

2026/10/11 23:24:24 阅读更多 →
impeccable工程标准:边界穷尽、状态可追溯、变更零感知

impeccable工程标准:边界穷尽、状态可追溯、变更零感知

1. “impeccable”不是一句空泛夸奖,而是可拆解、可验证、可复现的专业标准最近在多个技术评审会和设计交付现场,反复听到这个词被高频使用:“这个接口文档写得真impeccable”“UI动效的时序控制达到了impeccable级别”“CI流水线的失败归因逻…

2026/10/11 23:24:24 阅读更多 →

最新新闻

绝缘子缺陷检测数据集清洗与工业级训练实战指南

绝缘子缺陷检测数据集清洗与工业级训练实战指南

简介:本资源是面向电力AI研发人员、工业视觉工程师及智能巡检系统开发者的绝缘子缺陷检测专用YOLO格式数据集,解决无人机航拍场景下绝缘子破损、污闪、积雪等9类典型缺陷的精准识别与定位难题。数据集共2139张真实巡检图像(含训练/验证/测试集…

2026/10/12 0:05:01 阅读更多 →
牙科影像龋齿四级像素级分割数据集与临床落地实践

牙科影像龋齿四级像素级分割数据集与临床落地实践

简介:本资源是一套面向医学影像AI研究者与口腔临床算法开发者的专业蛀牙分割数据集,专为U-Net、DeepLab等分割模型训练设计,解决真实场景下多类别蛀牙区域精细识别与程度量化评估难题。数据集含400张高精度口腔内窥镜及X光影像(对…

2026/10/12 0:05:01 阅读更多 →
条形码目标检测数据集实战:从YOLOv8训练到部署

条形码目标检测数据集实战:从YOLOv8训练到部署

简介:这是一份面向目标检测与计算机视觉学习者的条形码识别数据集,涵盖零售、物流、制造等场景下的真实商品条码图像,适合用于训练YOLO系列模型或开展算法实验。数据集共684张图片,按训练集624张、验证集60张划分,采用…

2026/10/12 0:04:01 阅读更多 →
MongoDB复制集扩缩容实战:从rs.add到选主事故复盘

MongoDB复制集扩缩容实战:从rs.add到选主事故复盘

月初帮业务团队扩容一套 MongoDB 复制集,需求描述只有一句话:“加一台新机器进复制集,扛一下读流量。”我反问了一句:“你打算怎么加?”对方很自信:“rs.add() 啊,一行命令的事。”我当场就把计…

2026/10/12 0:04:01 阅读更多 →
Debian新手入门:从部署到日常操作的完整指南

Debian新手入门:从部署到日常操作的完整指南

第一次装完Debian,盯着黑乎乎的终端窗口迷茫好一会儿,这是我至今印象很深的场景。系统能开机、能登录,但下一步该敲什么命令完全没头绪。后来用久了才想明白一件事:Linux的入门难点从来不是"怎么把系统装上"&#xff0c…

2026/10/12 0:04:01 阅读更多 →
多模态大模型入门:从原理到实战,一文搞懂图文音视频一体模型

多模态大模型入门:从原理到实战,一文搞懂图文音视频一体模型

一、什么是多模态大模型? 💡 核心定义:多模态大模型是能够同时处理、理解和生成文本、图像、音频、视频等多种模态信息的人工智能模型。它打破了传统单模态模型(如仅处理文本的GPT-3或仅处理图像的ResNet)的限制&#…

2026/10/12 0:04:00 阅读更多 →

日新闻

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

在数码相机、高清显示屏与现代矢量图形技术高度发达的今天,画面可以做到绝对的锐利、平滑与无瑕。然而,当一张秋日手账插画或拍立得照片过于“平整无瑕”时,往往会散发出一种冰冷生硬的“数码塑料感(Digital Plasticity&#xff0…

2026/10/12 0:00:59 阅读更多 →
活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

在现代网页与移动端设计中,横排(Horizontal Layout)早已经成为了绝对的主流。然而,当我们翻开泛黄的线装古籍、宋版木刻诗集,或是欣赏一张茶道雅集的手写便签时,那种**自上而下纵向书写、自右向左逐列铺展&…

2026/10/12 0:00:59 阅读更多 →
周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

每到周日的晚上八点到十点,很多人心里都会悄悄亮起一盏警示灯。 在心理学上,这种现象有一个专门的称谓——“周日夜晚焦虑症(Sunday Scaries)”。明天又是周一,闹钟又要重新在七点响彻卧房;脑海里仿佛有一个…

2026/10/12 0:00:59 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:53 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:54 阅读更多 →