同一个 st_mode 里,高位说类型、低 9 位说权限——普通文件 33206 就是 0o100666,目录 16895 就是 0o40777。Linux 一共七种文件,ls 的首字母就是类型的缩写。
7.1 到 7.8 讲的全是【普通文件】,而 Linux 里【文件】这个词覆盖面大得多。这一节先认全七种,后面两节各看一类。
一、mode 里塞了两层信息
一个 st_mode 是 16 位,高 4 位说【这是什么类型】,低 12 位说【权限】。
实测:普通文件 33206,八进制是 0o100666;目录 16895,八进制是 0o40777。
八进制一眼就看清了:0o100000 是类型位(普通文件),666 是权限位(读写人人有)。0o040000 是类型位(目录),777 是权限位。
所以判断类型和判断权限是两件互不干扰的事:stat.S_ISREG() 看高位,mode & 0o777 看低位。
这个设计很省:一个整数同时回答两个问题,而所有系统调用(open、chmod、mkdir)都用同一个数。
二、七种文件
ls -l 的第一列那个字母,就是类型。
- 普通文件(0o100000):绝大多数文件。
d 目录(0o040000):内容是一张名字对照表。
l 符号链接(0o120000):内容是【另一个路径】。它自己不存数据,readlink 才读得到目标。
c 字符设备(0o020000):按字节流走,不能跳。/dev/null、/dev/tty 都是。
b 块设备(0o060000):按块访问,能随机跳。/dev/sda 这种。
p 命名管道(0o010000):两个进程通过它传字节,先进先出,不落盘。
s 套接字(0o140000):进程间通信用的端点,网络服务与本地服务都靠它。
记住一条:这七种东西都能 open,但后面能做的事不一样。能用同一套接口开始,就是它们被归成一类的原因。
三、目录为什么也是文件
因为目录真的符合文件的所有定义:有 inode、有块、有大小、有权限。它的内容是一串目录项,每项是【名字 + inode 号】。
所以对目录读,读出来的是目录项——但普通 read 会返回 EISDIR,要专门的 getdents 系统调用才行。
ls 就是封装了 getdents 的一个程序。它先拿到一串(inode 号,名字),再对每个 inode 号 stat 一次拿属性——这就是为什么 ls -l 比 ls 慢。7.14 会算这一笔在网络盘上有多贵。
目录能 seek(有 seekdir),因为它确实有【位置】的概念——位置就是【读到第几个目录项】。
这也解释了目录的一个限制:不能对目录做硬链接,因为那会让目录树可能成环,遍历永远走不完。
四、能 read 不能 seek 的那一类
设备和管道能 read,却不能 seek。因为它们背后没有【位置】这个概念。
键盘的下一个字节,不在磁盘的某个偏移上——它甚至还没发生。管道里的下一个字节,也取决于写的那头什么时候写。
所以对它们 lseek 会返回 ESPIPE。这个错误码的意思是【这不是一个可以跳的东西】。
这条区别在实际中很有用:任何【想先跳回去再读一遍】的代码,在管道上都会失败——所以 gzip、tar 这类要回退的程序,在管道里就得靠别的手段(临时文件或缓冲)。
而块设备能 seek,因为它确实有【第几块】这个概念——这也正是它能装文件系统的原因(7.11)。
五、动手 Demo:文件的种类——Linux 里一共有几种文件
这一节先看这台机器给出的 st_mode,再把它拆成【类型】与【权限】两半,最后把 Linux 的七种文件列全。
import os
import stat
import tempfile
d = tempfile.mkdtemp(prefix="ch29c7_")
p = os.path.join(d, "a.txt")
open(p, "wb").write(b"x")
sub = os.path.join(d, "sub")
os.makedirs(sub)
for label, path in (("普通文件", p), ("目录", sub)):
st = os.stat(path)
print(label, st.st_mode, oct(st.st_mode),
stat.S_ISREG(st.st_mode), stat.S_ISDIR(st.st_mode))
实跑输出:
--------------------------------------------------------------------
文件的种类——Linux 里一共有几种文件
--------------------------------------------------------------------
① 先看这台机器给出的 mode
东西 st_mode 八进制 是普通文件吗 是目录吗
------------------------------------------------------------------
普通文件 33206 0o100666 是 不是
目录 16895 0o40777 不是 是
**八进制看着就清楚了**:
**0o100666 的高位 0o100000 是【类型】,低 9 位 666 是【权限】。**
**判断类型看高位,判断权限看低位——两件事互不干扰。**
② Linux 的七种文件
种类 mode 高位 ls 首字母 能 read 吗 能 seek 吗
----------------------------------------------------------------------
普通文件 0o100000 - 能 能
目录 0o040000 d 能 getdents 能 seekdir
符号链接 0o120000 l 不能 不能
字符设备 0o020000 c 能 不能
块设备 0o060000 b 能 能
命名管道 0o010000 p 能 不能
套接字 0o140000 s 能 不能
**这张表里最反直觉的是【目录也是文件】。**
**它有自己的 inode、自己的块,内容是一张名字对照表。**
**所以对一个目录调 read,读出来的是目录项**——
**Linux 上要用 `getdents`,普通 read 会返回 EISDIR。**
**而【设备和管道】能 read 不能 seek**——**它们背后没有【位置】这个概念**:
**键盘的下一个字节,不在磁盘的某个偏移上。**
③ 【一切皆文件】的真实含义
**不是【所有东西都是磁盘上的文件】。**
**而是【所有东西都能用同一套 open/read/write/close 去用】。**
**键盘、屏幕、磁盘、网络套接字、进程信息,接口长一个样。**
**所以 shell 里的 `<` `>` `|` 才能对任意东西生效。**
**这一层统一,就是 7.15 要讲的虚拟文件系统。**
0o100666 里,0o100000 是类型、666 是权限。0o40777 里,0o040000 是类型、777 是权限。
判断类型看高位,判断权限看低位——两件事互不干扰。
注意这台机器是 Windows,所以目录的权限位是 777,Linux 上通常是 755 或 700。类型位与权限位分开这一点,两个系统是一样的。
最容易反直觉的是【目录也是文件】:它有自己的 inode、自己的块,内容是一张名字对照表。所以对目录 read 读出来的是目录项——Linux 上要用 getdents,普通 read 返回 EISDIR。
📋 Linux 上验证:ls -l /dev/null /dev/sda /tmp,看第一列是不是 c、b、d。
📋 ls -l /proc/self/fd/0,看你当前终端的类型。
30 秒实验
ls -l /dev | head -20,数一数有几行首字母是c或b。stat -c "%A %F %n" /etc/passwd /tmp /dev/null,一次看全三个。mkfifo /tmp/myfifo,然后ls -l看它是不是p。
常见误区
- 【符号链接就是快捷方式,内容指向目标】。它内容确实是那个路径字符串,但你自己
read它是读不到的——要用readlink。 - 【目录不是文件】。目录有 inode、有块、有权限、能
seek,只是它的内容格式是【名字对照表】而不是随便的字节。 - 【管道和套接字是【虚拟】的,不算文件】。它们确实在文件系统里有名字、有 inode,只是背后没有存储介质。
自测三问(点开对答案)
1. 0o100644 这个数里,哪几位说类型、哪几位说权限? —— **高 4 位(0o100000)说类型**——**这是普通文件**;**低 9 位(644)说权限**——**属主读写、其他只读。** 2. 为什么 `ls -l` 比 `ls` 慢? —— **因为 `ls -l` 要对每个目录项再 `stat` 一次拿属性**;**`ls` 只要名字就够了**。**在网络盘上这个差别会被放大到几百倍。** 3. 管道为什么不能 `seek`? —— **因为它背后没有【位置】这个概念**——**下一个字节还取决于写的那头什么时候写。****所以 `lseek` 会返回 `ESPIPE`。**- ☐ 7.10 字符设备
相关笔记
- 数据与元数据 — inode 里存了什么
- 字符设备 — 第一种非普通文件
- 块设备 — 第二种非普通文件
- 🐧 Linux是怎样工作的 · 索引