跳到主要内容Linux 进程控制详解:fork、wait 与退出机制 | 极客日志C
Linux 进程控制详解:fork、wait 与退出机制
综述由AI生成Linux 进程控制涉及进程创建、等待及终止。核心系统调用包括 fork 用于创建子进程,配合写时拷贝优化内存;wait 和 waitpid 用于回收僵尸进程并获取状态;exit、_exit 及 return 控制进程退出流程。文章详细解析了 fork 返回值、COW 机制、阻塞与非阻塞等待、状态宏 WIFEXITED 等概念,以及不同退出方式的区别与应用场景。
未来可期5.2K 浏览 进程创建
再识 fork 函数
在 Linux 系统中,fork 函数是非常重要的系统调用,它从已存在进程中创建一个新进程。创建出来的新进程叫做子进程,而原进程则称为父进程。
在 Linux 参考手册中,fork 函数的原型如下:(man 2 fork 指令查看)
NAME fork - create a child process
SYNOPSIS
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
如上不难看出:
fork 函数的功能是创建一个子进程
- 头文件有
<sys/types.h> 和 <unistd.h>
- 参数为
void ,返回值为 pid_t (实际上是 Linux 内核中 typedef 出来的一个类型)
进程调用 fork,当控制转移到内核中的 fork 代码后,内核做如下几件事:
- 分配新的内存块和内核数据结构给子进程
- 将父进程部分数据结构内容拷贝至子进程
- 添加子进程到系统进程列表当中
- fork 返回,开始调度器调度
当一个进程调用 fork 之后,就有两个二进制代码相同的进程。并且它们都运行到相同的地方。但每个进程都将可以开始属于它们自己的旅程,看如下程序:
int main(void) {
pid_t pid;
printf("Before: pid is %d\n", getpid());
if ((pid = fork()) == -1) {
perror("fork()");
exit(1);
}
printf("After: pid is %d, fork return %d\n", getpid(), pid);
sleep(1);
return 0;
}
输出:
Before: pid is 40176 After: pid , fork After: pid , fork
is
40176
return
40177
is
40177
return
0
这里看到了三行输出,一行 before,两行 after。其中 40176 就是父进程啦,40177 就是子进程。进程 40176 先打印 before 消息,然后它有打印 after。另一个 after 消息是进程 40177 打印的。注意到进程 40177 没有打印 before,为什么呢?
当父进程执行到 fork 创建出子进程时,已经执行了上面的 before 代码,而创建出子进程后,子进程不会去执行父进程已经执行过的代码,而是和父进程一同执行 fork 之后的代码。这就是为什么子进程没有打印 before 的原因。
所以,fork 之前父进程独立执行,fork 之后,父子进程两个执行流分别执行之后的代码。值得注意的是,fork 之后,谁先执行完全由调度器决定,并没有明确的先后关系!
fork 函数返回值
类型定义:fork() 返回 pid_t 类型(通常为 int 通过 typedef 定义),用于表示进程 ID(PID)。
为什么给父进程返回子进程的 pid?
一个父进程可以创建一个或者多个子进程,父进程需要通过返回值获得新创建的子进程的唯一标识符(正整数),从而可以管理创建的多个子进程(如发送信号、等待终止等)。
为什么子进程返回 0?
子进程返回 0,标识自己为子进程,子进程通过返回值 0 确认自己的身份。子进程无需知晓父进程的 PID(实际上可以通过 getppid() 获取)。
- 当系统资源不足(如进程数超限、内存耗尽)时,fork() 失败。
if (pid == -1) {
perror("fork failed");
}
EAGAIN:进程数超过限制(RLIMIT_NPROC)或内存不足。
ENOMEM:内核无法分配必要数据结构所需内存。
写时拷贝 Copy-On-Write
写时拷贝(COW)是 Linux 中 fork() 系统调用的核心优化机制,它使得进程创建变得高效且资源友好。通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。
为什么需要写时拷贝?
在传统的进程创建方式中,fork() 会直接复制父进程的所有内存空间给子进程。这种方式存在明显问题:
- 内存浪费:如果父进程占用 1GB 内存,子进程即使不修改任何数据,也会立即消耗额外 1GB 内存。
- 性能低下:复制大量内存需要时间,尤其是对大型进程而言,fork() 会显著延迟程序运行。
- 推迟实际的内存复制,直到父子进程中某一方尝试修改内存页时,才进行真正的拷贝。在此之前,父子进程共享同一份物理内存。
- fork() 调用时
- 共享内存页:内核仅为子进程创建虚拟内存结构(页表),但物理内存页仍与父进程共享。
- 标记为只读:内核将共享的物理内存页标记为只读(即使父进程原本可写)。
- 进程尝试写入内存
- 触发页错误:当父进程或子进程尝试修改某个共享内存页时,由于页被标记为只读,CPU 会触发页错误(
Page Fault)。
- 内核介入处理:操作系统会由用户态陷入内核态处理异常。
- 分配新的物理内存页,复制原页内容到新页。
- 修改触发写入的进程的页表,使其指向新页。
- 将新页标记为可写,恢复进程执行。
- 后续操作
- 修改后的进程独享新内存页,另一进程仍使用原页。
- 未修改的内存页继续共享,不做复制,操作系统不做任何无意义的事情。
因为有写时拷贝技术的存在,所以父子进程得以彻底分离!完成了进程独立性的技术保证!写时拷贝,是一种延时申请技术,可以提高整机内存的使用率。
进程等待
之前我们在讲进程概念的时候讲过,如果父进程创建出子进程后,如果子进程已经退出,父进程却没有对子进程回收,那么子进程就会变成 '僵尸进程',造成内存泄露等问题。
在 Linux 系统中,进程等待是父进程通过系统调用等待子进程终止并获取其退出状态的过程,主要目的是避免僵尸进程并回收子进程资源。
- 僵尸进程问题: 子进程终止后,其退出状态会保留在进程表中,直到父进程读取。若父进程未处理,子进程将保持僵尸状态(
Zombie),占用系统资源。
- 状态收集: 父进程需知晓子进程的执行结果(成功、错误代码、信号终止等)。
- 资源回收: 内核释放子进程占用的内存、文件描述符等资源。
进程等待的方法
wait
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int* status);
status:输出型参数,用来存储子进程退出状态的指针(可为 NULL,表示不关心状态)。
- 成功:返回终止的子进程 PID。失败:返回 -1(如无子进程)。
waitpid
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int* status, int options);
- 功能:更灵活的等待方式,可指定子进程或非阻塞等待模式。
pid:
>0:等待指定 PID 的子进程。
-1:等待任意子进程(等效于 wait)。
0:等待同一进程组的子进程。
status:同 wait,输出型参数,表明子进程的退出状态。
options:默认为 0,表示阻塞等待。
WNOHANG:非阻塞模式,无子进程终止时立即返回 0。
WUNTRACED:报告已停止的子进程(如被信号暂停)。
- 成功:返回子进程 PID。
WNOHANG 且无子进程终止:返回 0。
- 失败:返回 -1。
- 如果子进程已经退出,调用 wait / waitpid 时,wait / waitpid 会立即返回,并且释放资源,获得子进程退出信息。
- 如果在任意时刻调用 wait / waitpid,子进程存在且正常运行,则进程可能阻塞。如果不存在该子进程,则立即出错返回。
获取子进程 status
wait 和 waitpid,都有一个 status 参数,该参数是一个输出型参数,由操作系统填充。
- 如果传递
NULL,表示不关心子进程的退出状态信息。
- 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
status 不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究 status 低 16 比特位):
- 正常终止
- 高 8 位(第 8 ~ 15 位):保存子进程的退出状态(退出码)(即
exit(code) 或 return code 中的 code 值)。
- 第 7 位:通常为 0,表示正常终止。
- 示例:若子进程调用 exit(5),表明子进程是正常退出,则 status 的高 8 位为 00000101(即十进制 5)。
- 被信号所杀导致终止
- 低 7 位(第 0 ~ 6 位):保存导致子进程终止的信号编号。
- 第 7 位:若为 1,表示子进程在终止时生成了
core dump 文件(用于调试)。
- 第 8 ~ 15 位:未使用(通常为 0)。
- 示例:若子进程因
SIGKILL(信号编号 9)终止,则 status 的低 7 位为 0001001(即十进制 9)。
| 低 16 位结构 | 含义 |
|---|
| [15~8] | 正常终止 → [ 退出状态(高 8 位) ] |
| [7] | 正常终止 → 0;被信号终止 → 1 (Core Dump) |
| [6~0] | 正常终止 → 0;被信号终止 → [ 信号编号 ] |
如何解析 status?
难道真的需要将 status 当作位图,使用位操作来提取子进程的退出信息吗?这么做对我们程序员来说当然小菜一碟,不过有点多余了,没必要。Linux 系统为我们定义了多种宏用来提取 status,方便且专业。
| 宏 | 功能 |
|---|
| WIFEXITED(status) | 若子进程正常终止(exit 或 return)返回真。 |
| WEXITSTATUS(status) | 若 WIFEXITED 为真,返回子进程的退出码(exit 的参数或 return 的值)。 |
| WIFSIGNALED(status) | 若子进程因信号终止返回真。 |
| WTERMSIG(status) | 若 WIFSIGNALED 为真,返回导致终止的信号编号。 |
| WCOREDUMP(status) | 若子进程生成了核心转储文件返回真。 |
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若 WIFEXITED 非零,提取子进程退出码。(查看进程的退出码)
int main() {
pid_t pid = fork();
if (pid == 0) {
printf("子进程运行中... PID=%d\n", getpid());
exit(42);
} else {
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
printf("子进程正常退出,退出码:%d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("子进程被信号终止,信号编号:%d\n", WTERMSIG(status));
}
}
return 0;
}
子进程运行中... PID=56153
子进程正常退出,退出码:42
int main() {
pid_t pid = fork();
if (pid == 0) {
printf("子进程运行中... PID=%d\n", getpid());
int *p = NULL;
*p = 100;
} else {
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
printf("子进程正常退出,退出码:%d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("子进程被信号终止,信号编号:%d\n", WTERMSIG(status));
}
}
return 0;
}
子进程运行中... PID=56203
子进程被信号终止,信号编号:11
阻塞等待与非阻塞等待
在 Unix/Linux 中,父进程通过 wait 或 waitpid 函数等待子进程结束。它们的核心区别在于是否允许父进程在等待子进程时继续执行其他任务。
阻塞等待(Blocking Wait)
父进程调用 waitpid 后,会一直挂起(阻塞),直到目标子进程终止。在阻塞期间,父进程无法执行其他操作,直到子进程退出。
pid_t waitpid(pid_t pid, int* status, 0);
int main() {
int status;
pid_t child_pid = fork();
if (child_pid == 0) {
exit(10);
} else {
waitpid(child_pid, &status, 0);
if (WIFEXITED(status)) {
printf("子进程退出码:%d\n", WEXITSTATUS(status));
}
}
return 0;
}
非阻塞等待(Non-blocking Wait)
父进程调用 waitpid 时,若子进程未结束,则父进程立即返回,而不是挂起。父进程可以继续执行其他任务,同时定期检查子进程状态。需结合循环实现非阻塞式轮询(polling)。
关键选项:宏 WNOHANG(定义在 <sys/wait.h> 中)。
pid_t waitpid(pid_t pid, int* status, WNOHANG);
int main() {
int status;
pid_t child_pid = fork();
if (child_pid == 0) {
sleep(3);
exit(10);
} else {
while (1) {
pid_t ret = waitpid(child_pid, &status, WNOHANG);
if (ret == -1) {
perror("waitpid");
break;
} else if (ret == 0) {
printf("子进程未结束,父进程继续工作...\n");
sleep(1);
} else {
if (WIFEXITED(status)) {
printf("子进程退出码:%d\n", WEXITSTATUS(status));
}
break;
}
}
}
return 0;
}
| 场景 | 阻塞等待 | 非阻塞等待 |
|---|
| 父进程任务优先级 | 必须立即处理子进程结果 | 需同时处理其他任务 |
| 子进程执行时间 | 较短或确定 | 较长或不确定 |
| 资源消耗 | CPU 空闲,无额外开销 | 需轮询,可能占用更多 CPU |
| 典型应用 | 简单脚本、单任务场景 | 多进程管理、事件驱动程序 |
进程终止
进程终止是进程生命周期的最后一个阶段,涉及资源释放、状态通知及父进程回收等关键步骤。进程终止的本质是释放系统资源,就是释放进程申请的相关内核数据结构和对应的代码和数据。
进程退出场景
- 代码运行完毕,结果正确
- 代码运行完毕,结果不正确
- 代码异常终止
- 程序完整执行了所有逻辑,未触发任何错误或异常。
- 输出结果与预期完全一致,符合功能需求或算法目标。
int sum(int a, int b) {
return a + b;
}
int main() {
int result = sum(3, 5);
printf("Result: %d\n", result);
return 0;
}
- 程序正常结束(无崩溃或异常),但输出结果与预期不符。
- 通常由逻辑错误、算法错误或数据处理错误导致。
int factorial(int n) {
int result = 0;
for (int i = 1; i <= n; i++) {
result *= i;
}
return result;
}
int main() {
printf("5! = %d\n", factorial(5));
return 0;
}
- 程序未执行完毕就中途崩溃或被强制终止。
- 通常由运行时错误、资源限制或外部信号触发。
- 比如除零错误,对空指针解引用等异常。
int main() {
int *ptr = NULL;
*ptr = 42;
printf("Value: %d\n", *ptr);
return 0;
}
int main() {
int a = 10;
int b = a / 0;
printf("Value: %d\n", b);
return 0;
}
进程常见退出方法
正常终止(可以通过 echo $? 查看进程退出码)
进程退出码
进程退出码(退出状态)可以告诉我们最后一次执行的命令的状态。在命令结束以后,我们可以知道命令是成功完成的还是以错误结束的。通常是你程序中 main 函数的返回值,其基本思想是,程序返回退出代码 0 时表示执行成功,没有问题。0 以外的任何代码都被视为不成功。
退出码是一个 8 位无符号整数(8-bit unsigned integer),因此取值范围为 2^8=256 个值。
- 退出码 0 表示命令执行有误,这是完成命令的理想状态。
- 退出码 1 我们也可以将其解释为'不被允许的操作'。例如在没有
sudo 权限的情况下使用 yum;
- 130(
SIGINT 或 ^C)和 143(SIGTERM)等终止信号是非常典型的,它们属于 128+n 信号,其中 n 代表信号编号。
这里需要补充一点:
进程退出码和错误码是两个完全不同的概念,不要混为一谈!
错误码
在 Linux 系统中,错误码(Error Codes)是操作系统用于标识程序运行中遇到的各类问题的核心机制。这些错误码通过全局变量 errno(定义在 <errno.h> 头文件中)传递,帮助开发者快速定位和调试问题。
要理解错误码,首先要认识全局变量 error。
例如:fork 函数调用失败后,会立刻返回 -1,并设置全局变量 errno。
- 定义:
errno 是一个线程安全的整型变量,用于存储最近一次系统调用或库函数调用失败的错误码。
- 成功调用不会重置 errno,因此必须在调用后立即检查其值。
- 每个线程有独立的 errno 副本(多线程安全)。
与之对应的是 strerror 函数,该函数可以将对应的错误码转化成字符串描述的错误信息打印出来,方便程序员调试代码。
实际上,我们可以通过 for 循环来打印查看 Linux 系统下所有的错误码以及其错误信息:
int main() {
for (int i = 0; i < 135; ++i) {
printf("%d-> %s\n", i, strerror(i));
}
return 0;
}
不难看出,在 Linux 系统下,一共有 0 ~ 133 总共 134 个错误码,其中 0 表示 success,即程序运行成功,1 ~ 133 则分别对应一个错误信息。
0-> Success
1-> Operation not permitted
2-> No such file or directory
...
134-> Unknown error
int main() {
FILE *fp = fopen("invalid.txt", "r");
if (fp == NULL) {
printf("%d->%s\n", errno, strerror(errno));
return 1;
}
return 0;
}
2->No such file or directory
使用错误码和对应的错误信息可以帮助程序员快速定位错误模块,调试程序,掌握错误码的使用与调试技巧,是提升 Linux 编程效率和系统可靠性的关键。
_exit 函数和 exit 函数
在 Linux 系统中,_exit() 是一个直接终止进程的系统调用,它会立即终止当前进程,并通知操作系统回收资源,但不执行任何用户空间的清理操作。
#include <unistd.h>
void _exit(int status);
- 参数
status:进程的退出状态码,范围是 0~255。父进程可以通过 wait() 或 waitpid() 获取该状态码。
- 返回值:无(进程直接终止,不会返回调用者)。
当前进程调用 _exit() 后,操作系统会立即介入,会从用户态陷入内核态,执行以下操作:
- 关闭所有文件描述符:内核会关闭进程打开的文件、套接字、管道等资源,但不会刷新标准 I/O 库(如
stdio)的缓冲区。
- 释放用户空间内存:回收进程的代码段、数据段、堆、栈等内存资源。
- 发送
SIGCHLD 信号:通知父进程子进程已终止,并传递退出状态码 status。
- 终止进程:进程的状态变为
ZOMBIE(僵尸进程),直到父进程通过 wait() 回收其资源。
本质上,_exit() 最终会调用 Linux 内核的 exit_group 系统调用(sys_exit_group),终止整个进程及其所有线程。其内核处理流程如下:
- 关闭所有文件描述符。
- 释放内存映射(mmap)和虚拟内存区域。
- 解除信号处理程序绑定。
- 将进程状态设为
TASK_DEAD
- 向父进程发送
SIGCHLD 信号。
在 C/C++ 语言中,exit 是一个用于正常终止程序执行的标准库函数。它会执行一系列清理操作后终止进程,并将控制权交还给操作系统。
#include <stdlib.h>
void exit(int status);
#include <cstdlib>
void exit(int status);
- 参数
status:进程的退出状态码,范围 0~255(0 通常表示成功,非零表示异常)。
- 返回值:无(进程终止,不会返回调用者)。
- 调用
atexit 注册的函数:按注册的逆序执行所有通过 atexit 或 at_quick_exit(若使用 quick_exit)注册的函数。
- 刷新所有标准 I/O 缓冲区:清空
stdout、stderr 等流的缓冲区。注意:stderr 默认无缓冲,stdout 在交互式设备上是行缓冲。
- 关闭所有打开的文件流:调用
fclose 关闭所有通过 fopen 打开的文件。注意:不会关闭底层文件描述符(需手动 close)。
- 删除临时文件:删除由
tmpfile 创建的临时文件。
- 终止进程:向操作系统返回状态码
status。父进程可通过 wait 或 waitpid 获取该状态码。
其实本质上,exit 是一个标准库函数,最后也会调用 _exit,但是在这之前,exit 还做了其他的清理工作:
int main() {
printf("hello");
exit(0);
}
[root@localhost linux]# ./a.out hello
[root@localhost linux]#
int main() {
printf("hello");
_exit(0);
}
[root@localhost linux]# ./a.out
[root@localhost linux]#
聪明的同学很快就知道了,我们通过 printf 打印'hello'并没有加上换行符,所以'hello'在缓冲区内没有被立即刷新,所以当我们使用 exit 终止进程时,exit 会帮我们做相应的清理工作,包括刷新 I/O 缓冲区。而调用 _exit 时则不会刷新,进程直接退出。
return 退出
return 是一种更常见的进程退出方法。执行 return n 等同于执行 exit(n),因为调用 main 的运行时函数会将 main 函数的返回值当做 exit 的参数。
状态码传递:
main 函数中的 return 语句返回一个整数值(通常称为退出状态码),表示程序的执行结果:
- 0:表示程序成功执行。
- 非 0:表示程序异常终止(具体数值由程序员定义)。
return 与 exit() 的关系
隐式调用 exit():
- 在 main 函数中使用 return 时,C/C++ 运行时会自动调用 exit() 函数,并将返回值作为参数传递给它。
int main() {
return 42;
}
return 的执行流程
当在 main 函数中执行 return 时,程序会做以下几件事:
- 返回值传递:将返回值传递给运行时环境。
- 清理操作:
- 调用局部对象的析构函数(按照创建顺序的逆序)。
- 调用全局对象的析构函数(同样逆序)。
- 调用 exit():运行时调用 exit(),执行以下操作:
- 刷新所有 I/O 缓冲区(如
std::cout)。
- 关闭通过
fopen 打开的文件流。
- 执行通过
atexit() 注册的函数。
- 终止进程:将控制权交还给操作系统。
值得注意的一点是:在非 main 函数的其他函数中使用 return 仅退出当前函数,返回到调用者,不会终止进程。
_exit、exit 和 return 对比
| 特性 | _exit() (系统调用) | exit() (标准库函数) | return (在 main 中) |
|---|
| 所属标准 | POSIX 系统调用 | C/C++ 标准库函数 | C/C++ 语言关键字 |
| 头文件 | <unistd.h> | <stdlib.h>(C)、<cstdlib>(C++) | 无(语言内置) |
| 执行流程 | 立即终止进程,不执行任何用户空间清理。 | 1. 调用 atexit 注册的函数 2. 刷新 I/O 缓冲区 3. 关闭文件流 | 1. 调用 C++ 局部对象析构函数 2. 隐式调用 exit() 完成后续清理 |
| 清理操作 | 内核自动回收进程资源(内存、文件描述符),不刷新缓冲区、不调用析构函数 | 清理标准库资源(刷新缓冲区、关闭文件流),但不调用 C++ 局部对象析构函数 | 调用 C++ 局部和全局对象析构函数,并触发 exit() 的清理逻辑 |
| 多线程行为 | 立即终止所有线程,可能导致资源泄漏 | 终止整个进程,但可能跳过部分线程资源释放(如线程局部存储) | 同 exit(),但在 C++ 中会正确析构主线程的局部对象 |
| C++ 析构函数调用 | ❌ 不调用任何对象的析构函数(包括全局对象) | ❌ 不调用局部对象析构函数 ✅ 调用全局对象析构函数(C++) | ✅ 调用局部和全局对象析构函数(C++) |
| 缓冲区处理 | ❌ 不刷新 stdio 缓冲区(如 printf 的输出可能丢失) | ✅ 刷新所有 stdio 缓冲区 | ✅ 通过隐式调用 exit() 刷新缓冲区 |
| 适用场景 | 1. 子进程退出(避免重复刷新缓冲区) 2. 需要立即终止进程(绕过清理逻辑) | 1. 非 main 函数的程序终止 2. 需要执行注册的清理函数(如日志收尾) | 1. 在 main 函数中正常退出 2. 需要确保 C++ 对象析构(RAII 资源管理) |
| 错误处理 | 直接传递状态码给操作系统,无错误反馈机制 | 可通过 atexit 注册错误处理函数,但无法捕获局部对象析构异常 | 可通过 C++ 异常机制处理错误(需在 main 中捕获) |
| 信号安全 | ✅ 可在信号处理函数中安全调用(如 SIGINT) | ❌ 不可在信号处理函数中调用(可能死锁) | ❌ 不可在信号处理函数中使用(仅限 main 函数流程) |
| 资源泄漏风险 | 高(临时文件、未释放的手动内存等需内核回收) | 中(未关闭的文件描述符、手动内存需提前处理) | 低(依赖 RAII 自动释放资源) |
| 底层实现 | 直接调用内核的 exit_group 系统调用 | 调用 C 标准库的清理逻辑后,最终调用 _exit() | 编译器生成代码调用析构函数,并跳转到 main 结尾触发 exit() |
_exit():最底层的终止方式,适合需要绕过所有用户空间清理的场景(如子进程退出)。
exit():平衡安全与效率,适合非 main 函数的程序终止,但需注意 C++ 对象析构问题。
return:C++ 中最安全的退出方式,优先在 main 函数中使用,确保资源自动释放。
相关免费在线工具
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
- JSON美化和格式化
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online