理论
应用程序需要请求操作系统来做一件事情时,本质上都是要访问“操作系统中的某个对象”。
Unix的哲学是尽可能将这些对象以文件形式暴露出来,比如/proc目录。
文件描述符是这个“一切皆文件”哲学的基础。它的本质是一个可替换的I/O通道编号。
“一切皆文件”更准确的描述是Unix将大量资源抽象为可通过文件描述符访问的对象。
假设没有文件描述符,当程序通过open打开一个文件时,内核需要返回内核里文件的地址(或者其他类似的东西),但这既不安全,也破坏了抽象(程序不应该需要知道磁盘是怎么组织的)。
int fd = open("a.txt", O_RDONLY); // fd = 3这个3是如何与实际文件关联起来的呢?
用户空间:fd = 3
内核空间:+----------------+| fd table |+----------------+|0 | stdin ||1 | stdout ||2 | stderr ||3 | a.txt |+----------------+ | v... 很多层 | vstruct file这个fd table在Linux内核里位于task_struct里的files_struct里面。每个进程都有task_struct。
由于文件描述符只是一个数字索引,可以很方便地统一所有资源。除了file,其他socket、pipe之类的东西也可以用int管理了。
每个Unix进程启动的时候stdin, stdout, stderr默认占三个文件描述符。
文件描述符是Unix管道的核心。
ubuntu@VM-0-9-ubuntu:~$ ls | grep txta.txt下面研究这条命令背后到底发生了什么。
在最开始,shell的fd table如下。
fd:0 stdin1 stdout2 stderrshell进程接收到这条命令以后,发现命令涉及管道,调用pipe这个syscall。
pipe这个syscall会创建一块内核缓冲区,并返回两个fd,一个读端一个写端。
此时shell的fd table如下。
fd:0 stdin1 stdout2 stderr3 pipe读端4 pipe写端现在正式调用fork召唤出ls。
fork会原样复制fd table。
fd:0 stdin1 stdout2 stderr3 pipe读端4 pipe写端此时ls实际上和shell没区别,在这个时候execve的话ls的输出还是会去stdout。
我们想让ls往pipe的写端里写,而不是往stdout里写。通过dup2(4,1)这个syscall把4号fd复制到1号。再close(3), close(4)把无关端口关掉。
fd:0 stdin1 pipe写端2 stderr此时再execve加载ls。ls的代码完全没有改动,但输出时printf里面的write(1, ..)这个系统调用无感知地往pipe的写端里写了。
grep同理,会经历类似上面的过程。
fd:0 pipe读端1 stdout2 stderr总的来说,管道的实现思想是修改程序看到的fd表。
stty -a可以看当前终端的绑定键。
stty -a | claude '这几个快捷键都有什么用'Unix也有类似Windows的任务管理。
Ctrl-z 放后台jobs 就像是Windows的任务管理器fg/bg %n 把第n个job移到前台和后台实验
扫描/proc里每一个数字进程的status,拿ppid。扫描comm拿名字。
建图,打印。
和Linux的实现的区别包括但不限于:Linux的实现还会打印出线程,是用花括号包裹的。同名的默认会被折叠成n*[xxx]的形式。