文件系统

文件索引节点:inode

两部分 纯数据区

  • 文件真正的数据存储区、基本存储单位为block

元数据区

  • 文件属性:磁盘中的存储位置、文件长度等信息
  • 时间戳:atime(访问)、mtime(内容修改)、ctime(inode 状态变更);ctime 不是创建时间,创建时间 btime 是否可用取决于文件系统和接口
  • 读写权限:通常在 open 等获取访问权限时检查文件权限;后续 read/write 还受文件描述符访问模式等约束
  • 所属组、所有者
  • 链接数

挂载

​ 挂载是一个非常重要的功能,使用非常频繁。它指将一个设备(通常是存储设备,可以挂载光盘、硬盘、磁带、光盘镜像文件等)挂接到一个已存在的目录上,那么,你访问这个目录就是访问这个设备(这个目录可以不为空,但挂载后这个目录下以前的内容将不可用) ,一个目录一旦成为"挂载点",该目录下看到的内容,来自"被挂载的文件系统", 与父目录的存储介质完全无关。

挂载需要有 挂载源(设备) 和 挂载点(目录)

/var             → /dev/mtdblock5  (JFFS2 flash)
/var/run         → tmpfs           (RAM)
/var/run/hdd0    → /dev/sda2       (HDD / ext2)

进程间通信

image-20250622223836271

  • 通过文件

  • 通过内核

  • 共享内存

匿名管道

image-20250622230944399

Linux 匿名管道提供单向、基于内核缓冲的字节流通信,常通过 fork 继承描述符使用。读写只在特定条件下阻塞,也可设置 O_NONBLOCK;应关闭不需要的描述符以正确触发 EOF 或 SIGPIPE。

匿名管道的基本用法

  1. 创建匿名管道
int pipe_fd[2];
pipe(pipe_fd);   // pipe_fd[0] = 读端, pipe_fd[1] = 写端
  1. fork 之后由父子进程各关闭不需要的端口
  • 写数据的进程:关闭读端
  • 读数据的进程:关闭写端

否则 read/write 行为会受到影响(例如 EOF 不会出现)

  1. 父子通过 read / write 进行通信
  • write(pipefd[1], buf, n)
  • read(pipefd[0], buf, n)

匿名管道的阻塞行为(重点)

Write 可能阻塞的情况:

  • 管道缓冲区已满时,write 阻塞,只有当读进程读走数据后,write 才能继续

Read 可能阻塞的情况:

  • 管道缓冲区为空 → read 阻塞,直到写进程写入更多数据
  • 写端全部关闭且缓冲区数据已读完时,read 才返回 0(EOF);剩余数据仍可先被读出

匿名管道的注意事项

  1. 必须正确关闭端口,否则会出现以下情况:
    1. 读端不关闭写端 ⇢ 读不到 EOF,一直阻塞
    2. 写端进程保留多余读端 ⇢ 真正读者退出后仍不触发 SIGPIPE/EPIPE,写满后可能一直阻塞
  2. 管道容量有限;很多 Linux 配置默认 16 页,但可能受内核版本和资源限制影响,可用 F_GETPIPE_SZ 查询,不能硬编码为 64 KiB
  3. 匿名管道没有用于重新 open 的路径;但可通过 Unix 域 socket 的 SCM_RIGHTS 把描述符传给非亲缘进程,因此不是绝对不能用于非亲缘进程
  4. 多个写端写入时是原子化的?
    1. 不超过 PIPE_BUF(Linux 为 4096 字节)的单次写入保证不与其他写者交错;非阻塞且空间不足时会返回 EAGAIN
    2. 大写可能导致数据交错
  5. 管道是流式无格式通信,没有消息边界,需要自己定义协议格式(分包、长度)

问:如果读端进程已经关闭(close(pipefd[0])),写进程继续 write 会发生什么?

答 write 失败,收到 SIGPIPE

因为匿名管道有一个特殊规则:

  • 规则:当所有读端被关闭时:
    • write 不再阻塞
    • 向调用进程产生 SIGPIPE 信号
    • 若该信号被忽略、阻塞或处理后返回,write 才有机会返回 -1 并设置 errno 为 EPIPE
      • → 如果进程没有处理该信号,会被终止!
    • 这就是“Broken Pipe”错误的来源。

写端写管道时,如果所有读端都关闭,write 不会阻塞,而是立即失败,并收到 SIGPIPE。

例如:

每个进程各关闭一端,我们强制建立了一个明确的单向通信流:子进程 -> 父进程。

  • 对于读者(父进程): 只有当所有指向管道写端的文件描述符都被关闭时,读者在读取管道时才会接收到 EOF(文件结束符)。如果父进程不关闭自己的 fds[1],那么即使子进程退出了(关闭了它的 fds[1]),管道的写端依然被父进程自己持有着。这样,父进程在读取 fds[0] 时永远不会拿到 EOF,可能会导致程序死锁,一直在等待永远不会到来的数据。
  • 对于写者(子进程): 如果读者(父进程)关闭了读端,子进程再尝试写入数据时,操作系统会向子进程发送 SIGPIPE 信号,表明管道已断开。

命名管道 FIFO

与匿名管道不同,有文件名,FIFO最大的特性就是每个FIFO都有一个路径名与之相关联,从而允许无亲缘关系的任意两个进程间通过FIFO进行通信。

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

特点

  1. 使用方式像文件,但不能 seek,你可以通过:
  • open()
  • read()
  • write()
  • close()
  • 但不能lseek,因为 FIFO 是流式文件,没有偏移量。
  1. 进入 FIFO 的数据顺序,就是读出的顺序。
  2. 管道一样,FIFO仅提供半双工的数据通信

open 行为会阻塞

以下讨论阻塞模式。加 O_NONBLOCK 时,只读 open 可立即成功;只写 open 在没有读端时失败并返回 ENXIO。

  1. 打开 FIFO 的读端,如果没有写端打开 → 阻塞
int fd = open("fifo", O_RDONLY);  // 没有写端

会阻塞,直到有进程打开写端为止。

  1. 打开 FIFO 的写端,如果没有读端打开 → 阻塞
int fd = open("fifo", O_WRONLY);  // 没有人读

也会阻塞,直到读端出现。

⚠️ 这是命名管道区别于匿名管道的重要特性!!!

匿名管道是在父子进程中自然同时存在读写端,而 FIFO 是“双方需要主动 open”,因此更容易阻塞。

PIPE 与 FIFO 的共同点

  1. 内核实现层面:本质是同一套机制
    • 内核维护的一块 环形缓冲区
    • 使用内存页保存管道数据;不能等同于普通文件的 Page Cache,数据不会写入 FIFO 路径对应的存储文件
    • 通过 等待队列 + 锁 实现同步与阻塞
  2. 都是“流式文件”(Stream)
    • 不支持 lseek
    • 只能顺序读 / 顺序写
  3. 都是 FIFO(先进先出)
    • 先写入的数据,先被读出
    • 没有消息边界(除非你自己定义协议)

共享内存

D-BUS

D-BUS(Desktop Bus)是一种“消息总线式”的进程间通信(IPC)机制。用于让不同进程互相通信

特点:

  • 多个进程共享一个“软件总线”
  • 广播消息
  • 点对点 :A 进程直接发给 B 进程

System Bus(系统总线) +

UDS

UNIX domain socket:允许位于同一主机系统上的进程之间相互通信的

struct sockaddr_un {
    sa_family_t sun_family;     /* Address family (AF_UNIX) */
    char        sun_path[108];   /* Path name */
};
#define SOCKET_PATH "/tmp/uds_socket"
#define BUFFER_SIZE 1024
int server_fd;
struct sockaddr_un server_addr;
char buffer[BUFFER_SIZE];

// 创建 Unix 域套接字
server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (server_fd == -1) {
    perror("socket");
    exit(EXIT_FAILURE);
}

// 初始化服务器地址结构
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sun_family = AF_UNIX;
strncpy(server_addr.sun_path, SOCKET_PATH, sizeof(server_addr.sun_path) - 1);
// 绑定套接字到地址
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
    perror("bind");
    exit(EXIT_FAILURE);
}

时间

设置时区

下面是该开发板所用 libc/根文件系统约定的 /etc/TZ -> /tmp/TZ 配置,不是所有 Linux 通用。glibc 系统通常读取 TZ 环境变量或 /etc/localtime,不能只创建 /etc/TZ 就假定生效。

[root@Ingenic-uc1_1:~]# ls -l /etc/TZ
lrwxrwxrwx    1 root     root             9 Aug 15 11:20 /etc/TZ -> ../tmp/TZ
[root@Ingenic-uc1_1:~]# cat /tmp/TZ
GMT-08:00

需要注意的是,POSIX TZ 的正负号和我们日常说的 UTC 偏移方向是反的。

查看时间

date

  • -u 查看的是UTC 时间
[root@Ingenic-uc1_1:~]# date
Fri Sep 11 10:07:01 GMT 2026
[root@Ingenic-uc1_1:~]# date -u
Fri Sep 11 02:07:25 UTC 2026

获取时间

如果通过 TZ 配置了时区,比如东八区,那么按标准 Linux/POSIX 的用法:

  • 获取 UTC 时间:用 gmtime() / gmtime_r()
  • 获取 东八区本地时间:用 localtime() / localtime_r()
time_t now = time(NULL);

struct tm utc_tm;
struct tm local_tm;

gmtime_r(&now, &utc_tm);        // UTC+0
localtime_r(&now, &local_tm);   // 根据 TZ 转成本地时间

参考:pipe(7);unix(7);inode(7);tzset(3)。