Re:Linux系统篇(三十六):进程间通信Chapter3:从匿名管道到命名管道:Linux 进程间通信实战(FIFO 深度解析)


观众老爷们大家好 这里是邪修KING的独家频道 本文属于系列Linux系统篇 ------动静态库 一起学Linux的小伙伴可订阅专栏: Linux系统篇


前言

匿名管道有一个天生的局限:只能用于具有亲缘关系的进程(父子、兄弟)之间通信。原因很简单 ------ 它没有名字,只能靠 fork 继承文件描述符来传递管道入口,两个毫不相干的进程根本找不到同一条管道。

那如果想让两个完全独立的进程通信呢?比如一个服务端进程和一个客户端进程,它们既不是父子,也没有共同祖先。

这就是我们今天的主角:命名管道(Named Pipe),也叫 FIFO。

和上一篇一样,我们依然会从「先描述,再组织」这个内核核心思想出发,讲透原理、讲清规则、写透代码。本文只聚焦命名管道,文末会预告下一篇 System V 共享内存的内容。


0. 再谈「先描述,再组织」:命名管道也逃不开这个套路

在上一篇里我们反复强调:Linux 内核管理任何资源,都是先用结构体描述清楚「它是什么」,再用数据结构组织好「它们怎么放」。

  • 进程:task_struct(PCB)描述 + 链表 / 哈希表组织
  • 匿名管道:struct pipe_inode_info 描述缓冲区 + 文件描述符表组织入口
  • 文件:struct inode 描述属性 + 目录树组织层级

到了命名管道,这个思路依然成立,而且体现得更明显:

0.1 描述:内核里还是那套管道结构

很多人以为命名管道和匿名管道是两种完全不同的东西,其实不是。在内核层面,它们共用同一套缓冲区结构 struct pipe_inode_info,读写逻辑、环形缓冲区、等待队列全都是一样的。

换句话说:命名管道 = 匿名管道 + 一个文件系统中的名字。

0.2 组织:从「fd 继承」变成「文件系统索引」

匿名管道的组织方式是「靠文件描述符表 + fork 继承」,只有亲缘进程能拿到;

命名管道的组织方式是「靠文件系统的目录树」,在磁盘上有一个真实的文件名,任何进程只要知道路径,就能通过 open 找到同一条管道。

划重点:变的是「入口组织方式」,不变的是「内核描述结构」。 这就是为什么我们说「创建和打开方式不同,但语义完全一致」。

理解了这一点,你学命名管道就会非常轻松 ------ 读写规则、原子性、半双工、SIGPIPE 这些特性,和匿名管道一模一样,你只需要学「怎么创建、怎么打开、有什么特殊规则」就够了。


1. 为什么需要命名管道?匿名管道的天花板

在讲命名管道之前,我们先把匿名管道的局限说透,这样你才能明白 FIFO 诞生的意义。

1.1 匿名管道的核心限制:只能亲缘进程通信

匿名管道没有名字,它的入口就是两个文件描述符。一个进程创建了管道之后,只有它 fork 出来的子进程能继承到这两个 fd,才能访问同一条管道。

复制代码
  父进程创建管道
      │
     fork → 子进程继承 fd → 父子通信
      ↘
     fork → 子进程继承 fd → 兄弟通信

两个完全独立的进程,比如你手动启动的 server 和 client,既没有 fork 关系,也没有共同祖先,根本没法拿到对方管道的文件描述符,匿名管道就用不了。

1.2 解决方案:给管道起个名字

既然问题出在「没有全局唯一标识」,那解决方案就很直接:给管道在文件系统里起个名字,让所有进程都能通过文件名找到它。

这个「有名字的管道」,就是命名管道,也叫 FIFO(First In First Out,先进先出)。

1.3 命名管道的直观形象

你可以把匿名管道想象成「家里的内线电话」,只有一家人(亲缘进程)能用,外人接不上;

而命名管道就是「公共电话号码」,任何人(任何进程)只要拨通这个号码(打开这个文件),就能和另一端通话。


2. 命名管道是什么:有名字的内核缓冲区

2.1 定义

命名管道是一种特殊类型的文件,它在文件系统中以文件名的形式存在,但数据并不存储在磁盘上,而是存放在内核缓冲区中。进程通过操作这个特殊文件,实现跨进程的数据传输。

两个关键词:

  • 特殊文件 :类型是 p(pipe),不是普通文件,也不是目录、套接字
  • 内核缓冲区:数据只在内存里,不落地,和匿名管道本质一样

2.2 一个最直观的验证

我们可以先在命令行感受一下:

复制代码
mkfifo mypipe
ls -l mypipe

你会看到类似这样的输出:

复制代码
prw-r--r-- 1 root root 0 10月 22 10:00 mypipe

注意第一个字母 p,这就代表它是管道文件(pipe)。大小是 0,也印证了它不存数据,只是一个入口标识。

2.3 通信模型

复制代码
  进程A                           进程B
  open("mypipe", 写)              open("mypipe", 读)
      │                               │
      └──────────► 内核缓冲区 ◄────────┘
                (通过文件名关联)

两个进程通过同一个文件名,找到内核中同一块管道缓冲区,一个写一个读,完成通信。这就是命名管道的全部秘密。


3. 命名管道的创建:命令行 + 系统调用

命名管道的创建有两种方式:命令行创建,和代码里用函数创建。

3.1 命令行创建:mkfifo

最简单的方式,直接用 mkfifo 命令:

复制代码
mkfifo 文件名

比如:

复制代码
mkfifo test_fifo

就会在当前目录下创建一个名为 test_fifo 的命名管道文件。

3.2 代码创建:mkfifo 函数

头文件和原型:

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

int mkfifo(const char *pathname, mode_t mode);

参数说明:

  • pathname:要创建的命名管道的路径名
  • mode:文件权限,和 open 的 mode 一样,比如 0644,最终权限会受 umask 影响

返回值:

  • 成功返回 0
  • 失败返回 -1,并设置 errno

3.3 最简单的创建代码

复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    // 创建一个名为 p2 的命名管道,权限 0644
    int ret = mkfifo("p2", 0644);
    if (ret == -1) {
        perror("mkfifo");
        exit(1);
    }
    printf("命名管道创建成功\n");
    return 0;
}

3.4 一个常见疑问:文件存在了怎么办?

如果路径对应的文件已经存在,mkfifo 会失败,errno 设为 EEXIST。这和 open 的 O_CREAT | O_EXCL 语义类似。

所以工程上通常的写法是:如果文件已存在且是管道文件,就直接用;如果创建失败且不是 EEXIST,才报错退出。


4. 命名管道的打开规则:阻塞与非阻塞的四种场景

命名管道最核心、最容易考、也最容易踩坑的,就是打开规则。这也是它和匿名管道最大的不同点之一 ------ 匿名管道创建的时候读写端就都有了,而命名管道需要两边分别 open。

打开规则分两个维度:打开目的(读 / 写) 和 是否非阻塞(O_NONBLOCK),组合起来一共四种情况。

4.1 前提:为什么打开会阻塞?

管道是用来通信的,只有读端没有写端,或者只有写端没有读端,都没有意义。所以内核默认会让 open 阻塞,直到另一端也打开,通信链路建立完成。

这是内核提供的同步机制,保证双方都就绪之后再开始通信。

4.2 四种场景详细说明

场景 1:阻塞模式下,以读方式打开(O_RDONLY)
  • 行为:阻塞等待,直到有其他进程以写方式打开这个 FIFO
  • 类比:就像你接起电话等着,对方不拨号过来,你就一直等
场景 2:阻塞模式下,以写方式打开(O_WRONLY)
  • 行为:阻塞等待,直到有其他进程以读方式打开这个 FIFO
  • 类比:你拨了号,对方不接,你就一直等接通

这是默认行为,也是最常用的模式。两个进程分别打开,都阻塞,等双方都 open 了,一起往下走,天然同步。

场景 3:非阻塞模式下,以读方式打开(O_RDONLY | O_NONBLOCK)
  • 行为:立刻返回成功,不等待写端
  • 注意:此时如果没有写端,后续 read 会直接返回 0,而不是阻塞
场景 4:非阻塞模式下,以写方式打开(O_WRONLY | O_NONBLOCK)
  • 行为:立刻返回失败 ,errno 设置为 ENXIO
  • 原因:如果没有读端,写端打开也没用,内核直接报错返回

4.3 规则速查表

表格

打开方式 阻塞模式(默认) 非阻塞模式(O_NONBLOCK)
只读打开(O_RDONLY) 阻塞,直到有写端打开 立即返回成功
只写打开(O_WRONLY) 阻塞,直到有读端打开 立即返回失败,errno=ENXIO

4.4 为什么非阻塞写打开会失败?

这是一个经典面试题。简单说:管道是半双工的单向通道,如果连读端都没有,写端打开之后往哪儿写呢?内核认为这是一个无意义的操作,所以直接返回错误。

而非阻塞读打开之所以允许成功,是因为读端可以先等着,等写端来写数据,语义上是说得通的。


5. 实战一:用命名管道实现跨进程文件拷贝

光说不练假把式。我们来写第一个完整案例:用命名管道实现两个独立进程之间的文件拷贝。

5.1 整体流程

复制代码
  写端进程(send)                    读端进程(recv)
  打开源文件 abc                      打开目标文件 abc.bak
  打开命名管道 tp(写)                打开命名管道 tp(读)
  循环读文件 → 写管道                  循环读管道 → 写文件
  关闭文件、关闭管道                   关闭文件、关闭管道
                                    删除管道文件

两个进程完全独立,分别编译、分别运行,靠命名管道 tp 传递数据。

5.2 写端代码:读文件,写管道

文件名:send.c

c 复制代码
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

// 错误处理宏:打印错误并退出
#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

int main(int argc, char *argv[])
{
    // 1. 创建命名管道 tp,如果已存在则继续使用
    // 这里我们选择直接创建,失败且不是已存在则报错
    int ret = mkfifo("tp", 0644);
    if (ret == -1 && errno != EEXIST) {
        ERR_EXIT("mkfifo");
    }

    // 2. 打开源文件(只读)
    int infd = open("abc", O_RDONLY);
    if (infd == -1)
        ERR_EXIT("open source file");

    // 3. 打开命名管道(只写)
    // 阻塞模式:会等到读端也打开才返回
    int outfd = open("tp", O_WRONLY);
    if (outfd == -1)
        ERR_EXIT("open fifo write");

    // 4. 循环拷贝:读源文件 → 写管道
    char buf[1024];
    int n;
    while ((n = read(infd, buf, 1024)) > 0) {
        write(outfd, buf, n);
    }

    // 5. 关闭文件描述符
    close(infd);
    close(outfd);

    printf("文件发送完成\n");
    return 0;
}

5.3 读端代码:读管道,写目标文件

文件名:recv.c

c 复制代码
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

int main(int argc, char *argv[])
{
    // 1. 打开目标文件(只写 | 创建 | 截断)
    // 如果文件存在就清空,不存在就创建
    int outfd = open("abc.bak", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (outfd == -1)
        ERR_EXIT("open dest file");

    // 2. 打开命名管道(只读)
    // 阻塞模式:会等到写端也打开才返回
    int infd = open("tp", O_RDONLY);
    if (infd == -1)
        ERR_EXIT("open fifo read");

    // 3. 循环拷贝:读管道 → 写目标文件
    char buf[1024];
    int n;
    while ((n = read(infd, buf, 1024)) > 0) {
        write(outfd, buf, n);
    }

    // 4. 关闭文件描述符
    close(infd);
    close(outfd);

    // 5. 删除管道文件(收尾工作)
    unlink("tp");

    printf("文件接收完成\n");
    return 0;
}

5.4 编译与运行

复制代码
gcc -o send send.c
gcc -o recv recv.c

# 先准备一个测试文件 abc
echo "hello fifo test" > abc

# 开两个终端
# 终端1:运行读端
./recv

# 终端2:运行写端
./send

现象:

  • 先运行哪一个,哪一个就会阻塞在 open 处,等另一个也启动后,双方同时继续,文件拷贝完成。
  • 拷贝结束后,当前目录下会生成 abc.bak,内容和 abc 完全一致。
  • 管道文件 tp 会被 recv 进程删除。

5.5 关键点解读

  1. 两个进程完全独立 ,没有任何亲缘关系,靠文件名 tp 找到同一条管道
  2. open 的阻塞特性天然实现了同步,不用我们自己写等待逻辑
  3. 数据流式传输,和匿名管道一样,没有消息边界,read 能读多少读多少
  4. 管道文件只是入口,数据全程在内核缓冲区,不写磁盘

6. 实战二:命名管道实现 Server-Client 通信

文件拷贝是单向一次性的,我们再写一个更贴近实际的场景:持续通信的服务端与客户端。客户端发消息,服务端接收并打印。

6.1 架构图

复制代码
  客户端进程                      服务端进程
  (写管道)                      (读管道)
  键盘输入 → 写入 mypipe →  读取并打印消息

这是一个单向通信模型,服务端一直等消息,客户端发一条服务端收一条。

6.2 服务端代码:创建管道,循环读取

文件名:serverPipe.c

c 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

int main()
{
    // 1. 设置 umask,保证我们设置的权限生效
    umask(0);

    // 2. 创建命名管道 mypipe
    if (mkfifo("mypipe", 0644) < 0) {
        // 如果文件已存在,不报错,继续使用
        if (errno != EEXIST) {
            ERR_EXIT("mkfifo");
        }
    }

    printf("服务端启动,等待客户端连接...\n");

    // 3. 以只读方式打开管道(阻塞等待写端)
    int rfd = open("mypipe", O_RDONLY);
    if (rfd < 0) {
        ERR_EXIT("open");
    }
    printf("客户端已连接,开始接收消息\n");

    // 4. 循环读取客户端消息
    char buf[1024];
    while (1) {
        buf[0] = 0; // 清空缓冲区
        printf("等待消息中...\n");

        // 从管道读取数据
        ssize_t s = read(rfd, buf, sizeof(buf) - 1);
        if (s > 0) {
            // 读到数据,去掉末尾的换行符再打印
            buf[s - 1] = 0;
            printf("客户端说# %s\n", buf);
        }
        else if (s == 0) {
            // read 返回 0:所有写端都关闭了,客户端退出
            printf("客户端已退出,服务端关闭\n");
            break;
        }
        else {
            // 读取出错
            ERR_EXIT("read");
        }
    }

    // 5. 关闭文件描述符
    close(rfd);
    return 0;
}

6.3 客户端代码:打开管道,循环发送

文件名:clientPipe.c

c 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

int main()
{
    printf("客户端启动,正在连接服务端...\n");

    // 1. 以只写方式打开命名管道(阻塞等待读端)
    int wfd = open("mypipe", O_WRONLY);
    if (wfd < 0) {
        ERR_EXIT("open");
    }
    printf("连接成功,请输入消息(Ctrl+C 退出)\n");

    // 2. 循环读取键盘输入,写入管道
    char buf[1024];
    while (1) {
        buf[0] = 0;
        printf("请输入# ");
        fflush(stdout); // 手动刷新缓冲区,保证提示立刻打印

        // 从标准输入读取一行
        ssize_t s = read(0, buf, sizeof(buf) - 1);
        if (s > 0) {
            buf[s] = 0;
            // 写入管道发送给服务端
            write(wfd, buf, strlen(buf));
        }
        else if (s <= 0) {
            ERR_EXIT("read stdin");
        }
    }

    // 3. 关闭写端
    close(wfd);
    return 0;
}

6.4 Makefile

复制代码
.PHONY:all
all:clientPipe serverPipe

clientPipe:clientPipe.c
	gcc -o $@ $^

serverPipe:serverPipe.c
	gcc -o $@ $^

.PHONY:clean
clean:
	rm -f clientPipe serverPipe mypipe

6.5 运行效果

复制代码
# 终端1:启动服务端
./serverPipe
服务端启动,等待客户端连接...

# 终端2:启动客户端
./clientPipe
客户端启动,正在连接服务端...
连接成功,请输入消息(Ctrl+C 退出)
请输入# hello world

# 服务端会显示:
客户端已连接,开始接收消息
等待消息中...
客户端说# hello world

当客户端 Ctrl+C 退出时,写端关闭,服务端 read 返回 0,检测到客户端退出,自身也退出。

6.6 退出逻辑的设计思想

这里又用到了管道的经典特性:所有写端关闭,read 返回 0。

客户端退出 → 写端关闭 → 服务端 read 返回 0 → 服务端知道对方走了,自己也优雅退出。

整个过程不需要发特殊的退出指令,也不需要信号,靠管道本身的语义就完成了退出通知。这是管道通信非常优雅的一点。


7. 内核视角:命名管道的本质与「先描述再组织」

代码写了两个,我们再回到内核层面,把命名管道的本质讲透,再次呼应「先描述,再组织」。

7.1 描述层:内核结构和匿名管道完全一样

当你 open 一个命名管道文件后,内核做的事情是:

  1. 通过路径找到对应的 inode
  2. 发现这是一个管道文件,就给这个 inode 关联上 struct pipe_inode_info 结构
  3. 分配环形缓冲区、初始化等待队列

而 struct pipe_inode_info 里面有什么?环形缓冲区指针、读写位置、读者写者计数、等待队列......和匿名管道用的是同一个结构体,一模一样。

所以你会发现,一旦 open 成功之后,read、write、close 的行为,和匿名管道没有任何区别:

  • 管道空了 read 阻塞
  • 管道满了 write 阻塞
  • 写端全关 read 返回 0
  • 读端全关 write 触发 SIGPIPE
  • PIPE_BUF 以内原子写入

全都是一样的规则,因为底层就是同一套实现。

7.2 组织层:入口方式变了

匿名管道的组织方式:

  • 没有文件名,入口就是文件描述符
  • 靠 fork 继承 fd 传递入口
  • 组织在进程的文件描述符表里

命名管道的组织方式:

  • 有文件名,入口是文件系统路径

  • 靠文件名索引到同一个 inode

  • 组织在文件系统的目录树里

    复制代码
    匿名管道的入口组织          命名管道的入口组织
    父进程 fd 表                文件系统目录树
        │                           │
       fork                         /
      子进程 fd 表              mypipe (inode)
                                  /   \
                            进程A fd  进程B fd

7.3 一句话总结

命名管道和匿名管道,内核里是同一个东西,差别只在于「你怎么找到它」。

一个靠 fd 继承找,一个靠文件名找。找到之后,用法、规则、本质,完全一致。

这也完美体现了「先描述再组织」的思想:描述结构(管道缓冲区)是统一的,组织方式(入口索引)可以灵活变化。


8. 匿名管道 vs 命名管道:一张表彻底分清

表格

对比维度 匿名管道(pipe) 命名管道(FIFO)
适用范围 只能亲缘进程(父子、兄弟) 任意进程,只要能访问同一个文件路径
创建方式 pipe() 函数,直接创建并返回两个 fd mkfifo 创建文件,再用 open 打开
标识方式 无名字,靠文件描述符标识 有文件名,在文件系统可见,类型为 p
打开行为 创建即有读写两端,不存在打开阻塞 需要分别 open,默认阻塞等待另一端
内核结构 struct pipe_inode_info 环形缓冲区 完全相同
数据形式 字节流,无消息边界 完全相同
通信方向 半双工,单向;双向需两条管道 完全相同
原子性 ≤PIPE_BUF 保证原子写入 完全相同
同步机制 读写阻塞、SIGPIPE 等 完全相同,额外增加 open 阻塞
生命周期 所有引用关闭后释放,随进程 管道文件随文件系统;缓冲区随最后一个引用关闭
典型场景 父子进程通信、进程池、shell 管道 独立进程 C/S 通信、跨程序数据传递

共同点总结

  1. 本质都是内核缓冲区,遵循先进先出
  2. 都遵循「一切皆文件」,用 read/write 操作
  3. 都是半双工字节流通信
  4. 读写规则、原子性、信号行为完全一致

核心差异总结

一句话:有没有名字,决定了能不能被无关进程找到。 其他的都是这个差异衍生出来的。


9. 常见坑点与最佳实践

9.1 坑点一:单端打开一直卡住

很多新手刚接触会疑惑:「为什么我 open 一个 FIFO 程序就不动了?」

这不是 bug,这是默认的阻塞行为。必须读写两端都有进程打开,open 才会返回。

解决: 要么启动对端进程,要么使用非阻塞模式(但要处理好返回值)。

9.2 坑点二:以为管道文件存数据

命名管道文件大小永远是 0,它只是一个索引节点,数据在内核内存里。就算你重启程序,只要管道文件还在,之前的数据也没了 ------ 因为进程全关了缓冲区就释放了。

9.3 坑点三:用完不删管道文件

mkfifo 创建的文件会一直留在文件系统里,就算所有进程都退出了也不会自动消失。用完记得 unlink 删除,不然下次创建会报 EEXIST。

9.4 坑点四:多进程同时写的原子性问题

和匿名管道一样,单次写入 ≤ PIPE_BUF(Linux 默认 4096 字节)时,内核保证原子性,不会穿插;超过就不保证了。如果多个客户端同时往一个服务端写管道,要注意消息长度。

9.5 坑点五:忘了是半双工

一条命名管道只能单向传数据。想双向通信,必须开两条管道,一条 client→server,一条 server→client。

9.6 最佳实践

  1. 服务端创建管道,客户端直接用:创建的职责交给通信的发起方 / 服务方,避免重复创建冲突
  2. 退出靠关闭写端:不要发特殊消息,利用 read 返回 0 的天然语义,优雅可靠
  3. 小数据量、简单场景优先用 FIFO:比 socket 简单,比共享内存安全,上手成本极低
  4. 用完清理管道文件:养成好习惯,避免残留文件干扰

10. 总结与下篇预告

10.1 全文总结

今天我们沿着匿名管道的脉络,把命名管道(FIFO)从原理到实战完整走了一遍:

  1. 为什么要有命名管道:解决匿名管道只能亲缘进程通信的局限,给管道加个文件名,让任意进程都能找到
  2. 命名管道的本质:内核缓冲区 + 文件系统名字,底层结构和匿名管道完全一样,变的只是入口组织方式
  3. 四大打开规则:阻塞模式下读写打开都等对端;非阻塞读成功、写失败
  4. 两个实战案例:文件拷贝 + Server-Client 通信,覆盖一次性传输和持续通信两种场景
  5. 内核设计思想:依然遵循「先描述,再组织」,描述结构统一,组织方式灵活
  6. 五大坑点与最佳实践:帮你避开绝大多数新手会踩的雷

命名管道是 IPC 里非常实用的一种,它继承了管道简单易用、内核兜底同步的优点,又突破了亲缘进程的限制。在很多轻量级跨进程通信场景里,它比 socket 更简单,比共享内存更安全。

10.2 下篇预告:System V 共享内存

管道虽然好用,但有一个绕不开的短板:数据必须经过内核中转,两次拷贝,性能有限。如果要传输大量数据、追求极致低延迟,管道就不够用了。

下一篇我们会讲 IPC 里速度最快的方式 ------System V 共享内存:

  • 共享内存为什么是最快的 IPC?零拷贝到底是什么意思?
  • ftok、shmget、shmat、shmdt、shmctl 全套函数详解
  • 共享内存的数据结构与内核组织方式
  • 共享内存的致命问题:没有同步互斥怎么办?
  • 管道 + 共享内存的组合实战

感兴趣的同学可以点个关注,我们下篇见。

相关推荐
Rabitebla1 小时前
【Linux】 权限篇(三):深入理解 umask 位运算与粘滞位的设计思想
java·linux·服务器·c++·笔记·学习
Tech探索者1 小时前
重装飞牛 NAS,几 KB 的 .bin 够吗?我的备份与恢复全过程
运维·服务器
java_nnnn1 小时前
Linux 学习第一篇:从零认识 Linux,使用 WSL + Ubuntu 搭建学习环境
linux·运维·学习
讯捷蓝达1 小时前
机房突发断电,5 盘 RAID硬盘全部掉线: 急速上门救援,现场4小时华为2288H V5服务器数据完整恢复
运维·服务器·数据库
ly76892 小时前
性能剖析与压测方法论:JMH、async-profiler 与分层瓶颈定位的可复现实验
服务器·前端·数据库·压测·jmh·性能剖析·async-profiler
风翼靓崽2 小时前
磁盘逻辑卷扩容
linux·运维·服务器
Z21-X2 小时前
云原生运维学习笔记:KVM实战——从环境搭建到热迁移的硬核体验
运维·笔记·学习·云原生
Brilliantwxx2 小时前
【STM32】RTC 完结篇:日期操作、闹钟中断与附录(时间戳 / 闹钟 / NTP)
服务器·c语言·stm32·单片机·嵌入式硬件·实时音视频
zincsweet2 小时前
Linux网络编程入门(二):从 UDP 回显服务器到多人聊天室,三个项目的渐进式实践
linux·服务器·网络