【Linux】进程等待与资源回收:父进程的责任

文章目录

    • 进程等待与资源回收:父进程的责任
    • 一、进程终止方式回顾与深化
      • [1.1 回顾:为什么需要进程等待](#1.1 回顾:为什么需要进程等待)
      • [1.2 进程退出的三种方式](#1.2 进程退出的三种方式)
        • [1.2.1 return退出](#1.2.1 return退出)
        • [1.2.2 exit()函数](#1.2.2 exit()函数)
        • [1.2.3 _exit()函数](#1.2.3 _exit()函数)
      • [1.3 三种方式的关键区别:缓冲区刷新](#1.3 三种方式的关键区别:缓冲区刷新)
      • [1.4 退出码的含义](#1.4 退出码的含义)
    • 二、进程等待机制
      • [2.1 进程等待的必要性](#2.1 进程等待的必要性)
      • [2.2 wait函数详解](#2.2 wait函数详解)
      • [2.3 waitpid函数详解](#2.3 waitpid函数详解)
      • [2.4 status参数的位图解析](#2.4 status参数的位图解析)
      • [2.5 阻塞等待 vs 非阻塞等待](#2.5 阻塞等待 vs 非阻塞等待)
        • [2.5.1 阻塞等待](#2.5.1 阻塞等待)
        • [2.5.2 非阻塞等待](#2.5.2 非阻塞等待)
    • 三、实战案例
      • [3.1 非阻塞等待:一边等待一边工作](#3.1 非阻塞等待:一边等待一边工作)
      • [3.2 完整的进程回收示例](#3.2 完整的进程回收示例)
    • 四、总结与展望

进程等待与资源回收:父进程的责任

💬 欢迎讨论 :这是Linux系统编程系列的第五篇文章。在第二篇中,我们学习了僵尸进程的产生和危害------当子进程退出而父进程不回收时,子进程就会变成僵尸。那么,父进程如何正确地回收子进程呢?这就是本篇要深入讲解的进程等待机制。如果有任何疑问,欢迎在评论区交流!

👍 点赞、收藏与分享:这篇文章包含了大量实战代码和原理分析,如果对你有帮助,请点赞、收藏并分享给更多的朋友!

🚀 承上启下:建议先阅读本系列前四篇文章,理解进程的创建、状态、调度和虚拟内存,这样学习进程等待会更轻松。


一、进程终止方式回顾与深化

在深入学习进程等待之前,我们先来系统地理解进程的退出方式。这将帮助我们更好地理解wait/waitpid获取的退出信息。

1.1 回顾:为什么需要进程等待

在第二篇文章中,我们详细学习了僵尸进程的概念。让我们快速回顾一下核心要点:

僵尸进程的产生条件:

  1. 子进程已经执行结束
  2. 父进程仍在运行
  3. 父进程没有调用wait()或waitpid()读取子进程的退出状态

僵尸进程的危害:

  • 占用内核内存(PCB无法释放)
  • 占用进程号(PID资源耗尽)
  • 大量僵尸进程会导致无法创建新进程

因此,父进程必须通过进程等待来回收子进程,避免产生僵尸进程。

1.2 进程退出的三种方式

进程正常退出有三种方式,它们看起来相似,但有重要的区别。

1.2.1 return退出

这是最常见的退出方式:

cpp 复制代码
int main()
{
    printf("hello world\n");
    return 0;  // 返回退出码0
}

当main函数执行return n时,等同于调用exit(n)。这是因为调用main函数的运行时库返回值作为exit的参数。

1.2.2 exit()函数
cpp 复制代码
#include <stdlib.h>

void exit(int status);

exit是C标准库函数,它在终止进程前会做一些清理工作:

  1. 调用用户通过atexit()或on_exit()注册的清理函数
  2. 刷新所有标准I/O流的缓冲区
  3. 关闭所有打开的 标准 I/O 流(FILE)
  4. 删除tmpfile()创建的临时文件
  5. 最后调用_exit()
1.2.3 _exit()函数
cpp 复制代码
#include <unistd.h>

void _exit(int status);

_exit是系统调用,它会立即终止进程,

  • _exit 不做用户态清理(不刷新 stdio 缓冲、不调用 atexit 回调等);进程结束后内核会回收资源并关闭 FD。

1.3 三种方式的关键区别:缓冲区刷新

让我们通过实验来观察三种退出方式的区别。

实验1:使用exit()

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

int main()
{
    printf("hello");  // 注意:没有\n
    exit(0);
}

编译运行:

bash 复制代码
gcc test.c -o test
./test

输出:

bash 复制代码
hello

可以看到hello被正常输出了。

实验2:使用_exit()

cpp 复制代码
#include <stdio.h>
#include <unistd.h>

int main()
{
    printf("hello");  // 注意:没有\n
    _exit(0);
}

运行结果:

bash 复制代码
./test
# 什么都没有输出!

为什么会这样?

这涉及到C标准库的缓冲机制:

bash 复制代码
printf("hello") → 数据进入缓冲区 → 等待刷新

exit(0)   → 刷新缓冲区 → 输出到终端 → 进程退出
_exit(0)  → 直接退出 → 缓冲区数据丢失

缓冲区的刷新时机:

  1. 缓冲区满了
  2. 遇到换行符\n
  3. 程序正常结束(调用exit或return)
  4. 手动调用fflush()

让我们验证一下:

cpp 复制代码
#include <stdio.h>
#include <unistd.h>

int main()
{
    printf("hello\n");  // 加上\n
    _exit(0);
}

这次会输出hello,因为\n触发了缓冲区刷新。

注意:

当 stdout 连接到终端时,stdout 通常是行缓冲,\n 会触发刷新,所以能看到输出。

当 stdout 重定向到文件/管道时,stdout 往往变成全缓冲,\n 不一定立刻刷新,这时 _exit() 可能仍然导致输出丢失。

或者手动刷新:

cpp 复制代码
#include <stdio.h>
#include <unistd.h>

int main()
{
    printf("hello");
    fflush(stdout);  // 手动刷新
    _exit(0);
}

1.4 退出码的含义

无论使用哪种退出方式,都需要传递一个退出码(exit code)

cpp 复制代码
exit(0);    // 退出码为0
exit(1);    // 退出码为1
_exit(10);  // 退出码为10
return 5;   // 退出码为5
  • 0表示成功:程序正常执行完毕,没有错误
  • 非0表示失败 :不同的非0值可以表示不同的错误类型
    注意 :虽然退出码是int类型,但只有低8位有效。所以退出码的范围是0-255。
cpp 复制代码
exit(256);  // 实际退出码是0(256 % 256 = 0)
exit(257);  // 实际退出码是1(257 % 256 = 1)

在shell中可以通过$?查看上一个程序的退出码:

bash 复制代码
./test
echo $?  # 查看test的退出码

常见的退出码:

  • 0:成功
  • 1:通用错误
  • 2:误用shell命令
  • 126:命令不可执行
  • 127:命令未找到
  • 130:通过Ctrl+C终止(信号2)
  • 143:通过kill终止(信号15)

可以使用strerror()函数获取错误码的描述:

cpp 复制代码
#include <stdio.h>
#include <string.h>
#include <errno.h>

int main()
{
    for(int i = 0; i < 10; i++) {
        printf("错误码%d: %s\n", i, strerror(i));
    }
    return 0;
}

输出:

bash 复制代码
错误码0: Success
错误码1: Operation not permitted
错误码2: No such file or directory
错误码3: No such process
...

二、进程等待机制

理解了进程的退出方式后,我们来学习父进程如何获取子进程的退出信息。

2.1 进程等待的必要性

父进程需要进行进程等待,主要有三个原因:

1. 回收子进程资源

子进程退出后,虽然用户空间内存被释放了,但PCB(task_struct)仍然占用内核内存。父进程通过wait/waitpid来释放这部分资源。

2. 获取子进程的退出信息

父进程往往需要知道子进程的执行结果:

  • 子进程是否正常退出?
  • 退出码是多少?
  • 如果异常退出,是被哪个信号终止的?

3. 避免僵尸进程堆积

如果父进程不回收子进程,系统中会堆积大量僵尸进程,最终导致:

  • 内核内存耗尽
  • PID资源耗尽
  • 无法创建新进程

2.2 wait函数详解

wait是最简单的进程等待函数:

cpp 复制代码
#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *status);

返回值:

  • 成功:返回被回收的子进程的PID
  • 失败:返回-1

参数status:

  • 输出型参数,用于获取子进程的退出状态
  • 如果不关心退出状态,可以传NULL

wait的行为:

  1. 如果所有子进程都还在运行,wait会阻塞等待
  2. 如果有子进程已经退出变成僵尸,wait立即返回并回收
  3. 如果没有子进程,wait返回-1

让我们看一个简单的例子:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main()
{
    pid_t id = fork();
    
    if(id < 0) {
        perror("fork");
        return 1;
    }
    else if(id == 0) {
        // 子进程
        printf("子进程[%d]开始运行\n", getpid());
        sleep(3);
        printf("子进程[%d]即将退出,退出码=10\n", getpid());
        exit(10);
    }
    else {
        // 父进程
        printf("父进程[%d]等待子进程[%d]\n", getpid(), id);
        
        int status = 0;
        pid_t ret = wait(&status);  // 阻塞等待
        
        if(ret > 0) {
            printf("等待成功!回收了进程%d\n", ret);
            printf("子进程的退出码: %d\n", (status >> 8) & 0xFF);
        }
    }
    
    return 0;
}

运行结果:

bash 复制代码
父进程[12800]等待子进程[12801]
子进程[12801]开始运行
子进程[12801]即将退出,退出码=10
等待成功!回收了进程12801
子进程的退出码: 10

2.3 waitpid函数详解

waitpid是wait的增强版,提供了更多的控制选项:

cpp 复制代码
#include <sys/types.h>
#include <sys/wait.h>

pid_t waitpid(pid_t pid, int *status, int options);

参数pid:

  • pid > 0:等待进程ID为pid的子进程
  • pid = -1:等待任意子进程(与wait相同)
  • pid = 0:等待同一进程组的任意子进程
  • pid < -1:等待进程组ID为|pid|的任意子进程

参数status:

  • 与wait相同,输出型参数
  • 传NULL表示不关心退出状态

参数options:

  • 0:阻塞等待(默认行为)
  • WNOHANG:非阻塞等待,如果没有子进程退出则立即返回0

返回值:

  • 成功回收子进程:返回子进程的PID
  • 使用WNOHANG且没有子进程退出:返回0
  • 出错:返回-1

waitpid相比wait的优势:

  1. 可以等待指定的子进程
  2. 支持非阻塞等待
  3. 更灵活的控制

2.4 status参数的位图解析

status参数不是简单的整数,而是一个位图,包含了丰富的信息。我们需要理解它的结构:

bash 复制代码
status (32位整数)
┌─────────┬─────────┬──────────┬──────────┐
│  高16位  │ 退出码   │ core dump│  信号编号 │
│ (不关心) │  8位    │   1位    │   7位    │
└─────────┴─────────┴──────────┴──────────┘
           15-8位      第7位      6-0位

低16位的含义:

  • 0-6位:导致进程终止的信号编号(如果是信号终止)
  • 第7位:core dump标志(1表示产生了core文件)
  • 8-15位:进程的退出码(如果是正常退出)

不要依赖固定位布局,因为不同平台实现可能不一样,务必使用 wait.h 提供的宏解析。

Linux提供了一组宏来解析status:

1. WIFEXITED(status)

检查进程是否正常退出(调用exit/_exit/return)

cpp 复制代码
if(WIFEXITED(status)) {
    printf("进程正常退出\n");
}

2. WEXITSTATUS(status)

获取退出码(仅当WIFEXITED为真时有效)

cpp 复制代码
if(WIFEXITED(status)) {
    int code = WEXITSTATUS(status);
    printf("退出码: %d\n", code);
}

3. WIFSIGNALED(status)

检查进程是否被信号终止

cpp 复制代码
if(WIFSIGNALED(status)) {
    printf("进程被信号终止\n");
}

4. WTERMSIG(status)

获取终止信号编号(仅当WIFSIGNALED为真时有效)

cpp 复制代码
if(WIFSIGNALED(status)) {
    int sig = WTERMSIG(status);
    printf("终止信号: %d\n", sig);
}

让我们写一个完整的例子来演示:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main()
{
    pid_t id = fork();
    
    if(id < 0) {
        perror("fork");
        return 1;
    }
    else if(id == 0) {
        // 子进程:运行20秒后退出
        printf("子进程[%d]开始运行\n", getpid());
        sleep(20);
        exit(10);
    }
    else {
        // 父进程:等待子进程
        printf("父进程[%d]等待子进程[%d]\n", getpid(), id);
        printf("你可以在20秒内kill掉子进程来观察信号终止\n");
        
        int status = 0;
        pid_t ret = waitpid(id, &status, 0);
        
        if(ret > 0) {
            if(WIFEXITED(status)) {
                // 正常退出
                printf("子进程正常退出,退出码: %d\n", 
                       WEXITSTATUS(status));
            }
            else if(WIFSIGNALED(status)) {
                // 信号终止
                printf("子进程被信号%d终止\n", 
                       WTERMSIG(status));
            }
        }
    }
    
    return 0;
}

测试场景1:让子进程正常退出

bash 复制代码
./test
# 等待20秒

输出:

bash 复制代码
父进程[12900]等待子进程[12901]
子进程[12901]开始运行
你可以在20秒内kill掉子进程来观察信号终止
子进程正常退出,退出码: 10

测试场景2:在另一个终端kill子进程

bash 复制代码
# 终端1
./test

# 终端2
ps aux | grep test  # 找到子进程PID
kill -9 12901       # 发送SIGKILL信号

终端1输出:

bash 复制代码
父进程[12900]等待子进程[12901]
子进程[12901]开始运行
你可以在20秒内kill掉子进程来观察信号终止
子进程被信号9终止

2.5 阻塞等待 vs 非阻塞等待

wait和waitpid默认都是阻塞等待,但waitpid支持非阻塞模式。

2.5.1 阻塞等待

阻塞等待的特点:

cpp 复制代码
pid_t ret = waitpid(-1, &status, 0);  // options=0,阻塞模式
  • 如果子进程还在运行,父进程会一直等待,无法执行其他代码
  • 直到子进程退出,waitpid才返回

示例:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main()
{
    pid_t id = fork();
    
    if(id < 0) {
        perror("fork");
        return 1;
    }
    else if(id == 0) {
        // 子进程:运行5秒
        printf("子进程[%d]开始运行\n", getpid());
        sleep(5);
        printf("子进程[%d]退出\n", getpid());
        exit(0);
    }
    else {
        // 父进程:阻塞等待
        printf("父进程开始等待...\n");
        
        int status = 0;
        pid_t ret = waitpid(id, &status, 0);  // 阻塞在这里
        
        printf("等待结束!回收了进程%d\n", ret);
    }
    
    return 0;
}

运行结果:

bash 复制代码
父进程开始等待...
子进程[13000]开始运行
子进程[13000]退出
等待结束!回收了进程13000

可以看到,父进程在waitpid处阻塞了5秒,期间无法做任何事情。

2.5.2 非阻塞等待

非阻塞等待允许父进程在等待的同时做其他事情:

cpp 复制代码
pid_t ret = waitpid(-1, &status, WNOHANG);  // WNOHANG:非阻塞
  • 如果子进程还在运行,waitpid立即返回0
  • 如果子进程已经退出,waitpid返回子进程PID并回收
  • 父进程可以在循环中检查子进程状态,同时执行其他任务

这在实际应用中非常有用。让我们来写一个更实用的例子:


三、实战案例

3.1 非阻塞等待:一边等待一边工作

假设父进程需要等待子进程完成任务,但在等待期间还有自己的工作要做。这时就需要非阻塞等待:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <vector>
#include <time.h>


typedef void (*handler_t)();  // 函数指针类型
std::vector<handler_t> tasks;  // 任务队列

// 模拟父进程的任务
void task_one() {
    printf(">>> 执行任务1:检查系统日志\n");
}

void task_two() {
    printf(">>> 执行任务2:更新配置文件\n");
}

void task_three() {
    printf(">>> 执行任务3:发送心跳包\n");
}

// 加载任务
void load_tasks() {
    if(tasks.empty()) {
        tasks.push_back(task_one);
        tasks.push_back(task_two);
        tasks.push_back(task_three);
    }
}

// 执行所有任务
void do_tasks() {
    load_tasks();
    for(auto task : tasks) {
        task();
    }
}

int main()
{
    pid_t id = fork();
    
    if(id < 0) {
        perror("fork");
        return 1;
    }
    else if(id == 0) {
        // 子进程:模拟长时间任务
        printf("子进程[%d]开始工作...\n", getpid());
        sleep(10);
        printf("子进程[%d]完成工作\n", getpid());
        exit(0);
    }
    else {
        // 父进程:非阻塞等待 + 处理自己的任务
        printf("父进程[%d]创建了子进程[%d]\n", getpid(), id);
        printf("父进程在等待的同时会处理自己的任务\n\n");
        
        int status = 0;
        pid_t ret = 0;
        
        // 轮询检查子进程状态
        do {
            ret = waitpid(id, &status, WNOHANG);  // 非阻塞
            
            if(ret == 0) {
                // 子进程还在运行,父进程可以做自己的事
                printf("[时间: %d秒] 子进程还在运行中...\n", 
                       (int)time(NULL) % 100);
                do_tasks();  // 执行父进程的任务
                printf("\n");
                sleep(2);  // 每2秒检查一次
            }
        } while(ret == 0);
        
        // 子进程退出了
        if(ret > 0) {
            printf("===================================\n");
            printf("子进程已退出!\n");
            if(WIFEXITED(status)) {
                printf("正常退出,退出码: %d\n", 
                       WEXITSTATUS(status));
            }
        }
    }
    
    return 0;
}

运行结果:

bash 复制代码
父进程[13100]创建了子进程[13101]
父进程在等待的同时会处理自己的任务

子进程[13101]开始工作...
[时间: 45秒] 子进程还在运行中...
>>> 执行任务1:检查系统日志
>>> 执行任务2:更新配置文件
>>> 执行任务3:发送心跳包

[时间: 47秒] 子进程还在运行中...
>>> 执行任务1:检查系统日志
>>> 执行任务2:更新配置文件
>>> 执行任务3:发送心跳包

[时间: 49秒] 子进程还在运行中...
>>> 执行任务1:检查系统日志
>>> 执行任务2:更新配置文件
>>> 执行任务3:发送心跳包

子进程[13101]完成工作
===================================
子进程已退出!
正常退出,退出码: 0

可以看到,父进程在等待子进程的同时,每2秒就执行一次自己的任务,实现了并发处理

3.2 完整的进程回收示例

让我们写一个综合示例,演示进程等待的各种场景:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>

// 打印status的详细信息
void print_status(int status)
{
    printf("status = %d (0x%X)\n", status, status);
    printf("低7位(信号): %d\n", status & 0x7F);
    printf("第7位(core): %d\n", (status >> 7) & 1);
    printf("高8位(退出码): %d\n", (status >> 8) & 0xFF);
    
    if(WIFEXITED(status)) {
        printf("→ 进程正常退出,退出码=%d\n", WEXITSTATUS(status));
    }
    else if(WIFSIGNALED(status)) {
        printf("→ 进程被信号%d终止\n", WTERMSIG(status));
    }
}

int main()
{
    printf("====== 测试1:正常退出 ======\n");
    pid_t id1 = fork();
    if(id1 == 0) {
        printf("子进程1[%d]退出,退出码=5\n", getpid());
        exit(5);
    }
    else {
        int status = 0;
        waitpid(id1, &status, 0);
        print_status(status);
    }
    
    printf("\n====== 测试2:异常退出(除0) ======\n");
    //这是演示用,除 0 属于未定义行为,不建议在工程代码中依赖它触发信号。
    pid_t id2 = fork();
    if(id2 == 0) {
        printf("子进程2[%d]执行除0操作\n", getpid());
        int a = 1 / 0;  // 触发SIGFPE信号
        (void)a;
    }
    else {
        int status = 0;
        waitpid(id2, &status, 0);
        print_status(status);
    }
    
    printf("\n====== 测试3:使用WNOHANG ======\n");
    pid_t id3 = fork();
    if(id3 == 0) {
        printf("子进程3[%d]将运行5秒\n", getpid());
        sleep(5);
        exit(0);
    }
    else {
        int count = 0;
        int status = 0;
        pid_t ret;
        
        while(1) {
            ret = waitpid(id3, &status, WNOHANG);
            if(ret == 0) {
                printf("第%d次检查:子进程还在运行\n", ++count);
                sleep(1);
            }
            else if(ret > 0) {
                printf("第%d次检查:子进程退出了\n", ++count);
                print_status(status);
                break;
            }
            else {
                perror("waitpid");
                break;
            }
        }
    }
    
    return 0;
}

运行结果:

bash 复制代码
====== 测试1:正常退出 ======
子进程1[13200]退出,退出码=5
status = 1280 (0x500)
低7位(信号): 0
第7位(core): 0
高8位(退出码): 5
→ 进程正常退出,退出码=5

====== 测试2:异常退出(除0) ======
子进程2[13201]执行除0操作
status = 8 (0x8)
低7位(信号): 8
第7位(core): 0
高8位(退出码): 0
→ 进程被信号8终止

====== 测试3:使用WNOHANG ======
子进程3[13202]将运行5秒
第1次检查:子进程还在运行
第2次检查:子进程还在运行
第3次检查:子进程还在运行
第4次检查:子进程还在运行
第5次检查:子进程还在运行
第6次检查:子进程退出了
status = 0 (0x0)
低7位(信号): 0
第7位(core): 0
高8位(退出码): 0
→ 进程正常退出,退出码=0

这个示例完整演示了:

  1. 正常退出的status解析
  2. 信号终止的status解析
  3. 非阻塞等待的使用方式

四、总结与展望

通过本篇文章,我们系统地学习了进程等待与资源回收的核心知识:

进程退出方式:

  • 理解了return、exit()、_exit()三种退出方式
  • 掌握了它们的关键区别:缓冲区刷新
  • 学会了退出码的使用和含义

进程等待机制:

  • 掌握了wait()和waitpid()的使用
  • 理解了阻塞等待与非阻塞等待的区别
  • 学会了解析status参数获取子进程退出信息
  • 实现了"一边等待一边工作"的并发处理模式

核心要点回顾:

  1. 父进程必须回收子进程,否则产生僵尸进程
  2. status是位图,包含退出码和信号信息
  3. WNOHANG实现非阻塞等待,提高程序并发能力
  4. 使用宏解析status:WIFEXITED、WEXITSTATUS等

在下一篇文章中,我们将学习进程程序替换(exec函数族)。我们会理解:为什么fork出的子进程只能执行父进程的代码?如何让子进程执行一个全新的程序?以及如何结合fork+exec+wait实现一个完整的命令行解释器(mini-shell)。

💡 思考题

  1. 为什么_exit()不刷新缓冲区,而exit()要刷新?这样设计的目的是什么?
  2. 如果父进程创建了5个子进程,如何确保所有子进程都被回收?
  3. 在什么场景下应该使用阻塞等待?什么场景下应该使用非阻塞等待?
  4. 如果子进程的退出码是256,父进程通过WEXITSTATUS获取到的值是多少?为什么?

以上就是关于进程等待与资源回收的内容,下一篇我们将揭开程序替换的神秘面纱,实现属于自己的shell!

相关推荐
木子欢儿6 小时前
Ubuntu24.04 安装rime中文输入法
linux·运维·服务器
gf13211116 小时前
python_基于主视频删减片段并插入镜头视频
linux·python·音视频
liuwei2000006 小时前
Ubuntu 22.04 安装 ROS 2 Humble
linux·运维·ubuntu
YJlio6 小时前
Active Directory 工具学习笔记(10.14):第十章·实战脚本包——AdExplorer/AdInsight/AdRestore 一键化落地
服务器·笔记·学习
Logic1016 小时前
《数据库运维》 郭文明 实验4 数据库备份与恢复实验核心操作与思路解析
运维·数据库·sql·mysql·学习笔记·形考作业·国家开放大学
物联网心球6 小时前
从ext4文件系统到Linux文件树
linux·linux内核·文件系统
winner88816 小时前
深入理解 find 与 grep 路径参数位置差异:Unix 哲学下的设计逻辑
服务器·unix
ℳ₯㎕ddzོꦿ࿐6 小时前
先立后破:Linux 下“新建管理员 → 验证 → 禁用 root 远程 SSH”的零翻车笔记
linux·笔记·ssh
郁大锤7 小时前
解决Ubuntu/Linux/Gnome 打开文件慢,使用chrome打开文件更慢/卡死问题
linux·ubuntu·卡顿