Master Linux Pipe
Master Linux Pipe
May 25, 2025
file descriptor
A file descriptor, or FD, is a positive integer that refers to an input/output source. 指向 I/O 源,不关心具体的源是什么, 这就是抽象
所谓 I/O redirection 不过是对指定的 FD(指针) 复制而已
sh
系统调用追踪
shell pipeline 流程
complete lifecycle of a shell pipeline (cat /etc/passwd | wc -l
) executed via sh
.
- ✅
pipe2()
(pipe creation, kernel managed buffer, pipe[0] read pipe[1] write) - ✅
clone()
(Linux process creation, replacesfork()
) - ✅
dup2()
(redirects stdin/stdout) - ✅
execve()
(runscat
andwc -l
) - ✅
wait4()
(sh
wait process status change)
strace -f -tt -s 1000 -o pipe.log -e trace=pipe2,clone,execve,dup2,close,wait4 sh -c 'cat /etc/passwd | wc -l'
[shell] 43141 11:44:13.141393 execve("/usr/bin/sh", ["sh", "-c", "cat /etc/passwd | wc -l"], 0x7ffc18a3a610 /* 66 vars */) = 0
[shell] 43141 11:44:13.151773 close(3) = 0
[shell] 43141 11:44:13.164758 close(3) = 0 # shell 关闭 pipe read 端
[shell] 43141 11:44:13.198842 pipe2([3, 4], 0) = 0 # sh 准备 pipe[read,write]
[shell] 43141 11:44:13.199757 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7bf8e1d19a10) = 43142 # sh 新创建的进程将执行 cat
[shell] 43141 11:44:13.203158 close(4) = 0 # shell 关闭 pipe write 端
[ cat ] 43142 11:44:13.204173 close(3) = 0
[ cat ] 43142 11:44:13.206577 dup2(4, 1) = 1 # 将 stdout 指向 pipe 的 write 端
[ cat ] 43142 11:44:13.210697 close(4) = 0
[ cat ] 43142 11:44:13.214337 execve("/usr/bin/cat", ["cat", "/etc/passwd"], 0x587549212128 /* 66 vars */) = 0 # 执行 cat
[shell] 43141 11:44:13.221827 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7bf8e1d19a10) = 43143 # sh 新创建的进程将执行 wc
[shell] 43141 11:44:13.224486 close(3 <unfinished ...>
[ wc ] 43143 11:44:13.225520 dup2(3, 0 <unfinished ...> # 将 stdin 指向 pipe 的 read 端
[shell] 43141 11:44:13.225697 <... close resumed>) = 0
[shell] 43141 11:44:13.226745 close(-1 <unfinished ...>
[ wc ] 43143 11:44:13.227157 <... dup2 resumed>) = 0
[shell] 43141 11:44:13.229491 <... close resumed>) = -1 EBADF (Bad file descriptor)
[ wc ] 43143 11:44:13.231492 close(3 <unfinished ...>
[shell] 43141 11:44:13.232277 wait4(-1, <unfinished ...>
[ wc ] 43143 11:44:13.232353 <... close resumed>) = 0
[ wc ] 43143 11:44:13.233515 execve("/usr/bin/wc", ["wc", "-l"], 0x587549212158 /* 66 vars */) = 0 # 执行 wc
[ cat ] 43142 11:44:13.237591 close(3) = 0
[ wc ] 43143 11:44:13.247690 close(3) = 0
[ cat ] 43142 11:44:13.252126 close(3) = 0
[ wc ] 43143 11:44:13.261880 close(3) = 0
[ cat ] 43142 11:44:13.273608 close(3) = 0
[ wc ] 43143 11:44:13.280205 close(3) = 0
[ cat ] 43142 11:44:13.285117 close(3 <unfinished ...>
[ wc ] 43143 11:44:13.285306 close(3) = 0
[ cat ] 43142 11:44:13.286279 <... close resumed>) = 0
[ cat ] 43142 11:44:13.287263 close(1) = 0
[ cat ] 43142 11:44:13.288559 close(2) = 0
[ cat ] 43142 11:44:13.290401 +++ exited with 0 +++
[shell] 43141 11:44:13.290534 <... wait4 resumed>[{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 43142 # sh 等待 cat
[shell] 43141 11:44:13.291286 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=43142, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
[shell] 43141 11:44:13.293119 wait4(-1, <unfinished ...>
[ wc ] 43143 11:44:13.295141 close(3) = 0
[ wc ] 43143 11:44:13.301529 close(0) = 0
[ wc ] 43143 11:44:13.302466 close(1) = 0
[ wc ] 43143 11:44:13.303521 close(2) = 0
[ wc ] 43143 11:44:13.309383 +++ exited with 0 +++
[shell] 43141 11:44:13.311084 <... wait4 resumed>[{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 43143 # sh 等待 wc
[shell] 43141 11:44:13.311821 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=43143, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
[shell] 43141 11:44:13.314447 wait4(-1, 0x7fffd5a3124c, WNOHANG, NULL) = -1 ECHILD (No child processes)
[shell] 43141 11:44:13.318626 +++ exited with 0 +++
By default, all open file descriptors remain open across an execve call unless explicitly marked with the
FD_CLOEXEC
flag.
数据流向
Pipe Setup (
shell
):pipe2([3,4])
creates a communication channel:3
: Read end (used bywc
) read port(读口)4
: Write end (used bycat
) write port(写口)
Child 1 (
cat
):dup2(4, 1)
→ Redirectscat
’s stdout to the pipe 写口.execve("cat", ...)
→cat
writes/etc/passwd
to the pipe.
Child 2 (
wc
):dup2(3, 0)
→ Redirectswc
’s stdin to the pipe 读口.execve("wc", ...)
→wc
reads from the pipe and counts lines.
Parent Cleanup:
- Closes both ends of the pipe (
close(3)
andclose(4)
) to prevent resource leaks.
- Closes both ends of the pipe (
数据流可视化
syscalls 可视化
+-------------------+
| Parent Shell |
| (PID 43141) |
+-------------------+
|
| 🔵 pipe2([3,4], 0)
▼
+-------------------+
| Pipe: |
| Read: FD 3 |
| Write: FD 4 |
+-------------------+
|
| 🟢 clone(SIGCHLD) → PID 43142
| 🟢 clone(SIGCHLD) → PID 43143
▼
+-------------------+ +-------------------+
| Child 43142 (cat) | | Child 43143 (wc) |
+-------------------+ +-------------------+
| 🟡 dup2(4, 1) | | 🟡 dup2(3, 0) |
| 🔴 close(4) | | 🔴 close(3) |
| 🟠 execve("cat") | | 🟠 execve("wc") |
| → Writes to pipe | | → Reads from pipe |
+-------------------+ +-------------------+
| ↑
| |
+--------------------------+
Data flow via pipe
pipeline 流程详细分析
✅ Step 1: Pipe Creation
43141 11:44:13.198842 pipe2([3, 4], 0) = 0
- The parent
shell
creates a pipe with two file descriptors:3
: Read end (forwc
)4
: Write end (forcat
)
pipe2()
is a modern variant ofpipe()
that supports flags. Here, no flags are set (0
), so it behaves likepipe()
.
✅ Step 2: Forking Child Processes
First Child (PID 43142
) – runs cat
43141 11:44:13.199757 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7bf8e1d19a10) = 43142
- The parent shell uses
clone()
to create a child process. - This is how
fork()
is implemented in modernglibc
— viaclone()
withSIGCHLD
.
Second Child (PID 43143
) – runs wc -l
43141 11:44:13.221827 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7bf8e1d19a10) = 43143
- The parent shell creates a second child process.
- Again, this is a
fork()
under the hood.
✅ Step 3: Redirecting File Descriptors
First Child (PID 43142
) – cat
43142 11:44:13.206577 dup2(4, 1) = 1 # 将 stdout 指向 pipe 的 write 端
43142 11:44:13.210697 close(4) = 0
- Redirects stdout (
FD 1
) to the write end of the pipe (FD 4
). - Closes the redundant
FD 4
after duplication.
Second Child (PID 43143
) – wc
43143 11:44:13.225520 dup2(3, 0 <unfinished ...>
43143 11:44:13.227157 <... dup2 resumed>) = 0
43143 11:44:13.231492 close(3 <unfinished ...>
- Redirects stdin (
FD 0
) to the read end of the pipe (FD 3
). - Closes the redundant
FD 3
after duplication.
✅ Step 4: Executing Commands
execve() 到底做了什么?
execve() replaces the current process with a new program: It replaces the process’s code, data, heap, and stack.
But it preserves :
- Open file descriptors (unless FD_CLOEXEC is set). 子进程提前重定向 fd,execve 后并不失效
- Signal handlers (unless changed in the new program).
- Current working directory.
- Umask.
- User/group IDs, etc.
cat
Process (PID 43142
)
27641 13:23:49.476204 execve("/usr/bin/cat", ["cat", "/etc/passwd"], ...) = 0
- Replaces child process with
cat /etc/passwd
. - Now
cat
writes its output to the pipe.
wc
Process (PID 43143
)
27642 13:23:49.483297 execve("/usr/bin/wc", ["wc", "-l"], ...) = 0
- Replaces child process with
wc -l
. - Now
wc
reads input from the pipe and counts lines.
✅ Step 5: Parent Shell Cleanup
43141 11:44:13.164758 close(3) = 0
43141 11:44:13.203158 close(4) = 0
- The parent shell closes both ends of the pipe.
- This ensures the reader (
wc
) knows when the writer (cat
) has finished (when all writers have closed the pipe).
✅ Step 6: Process Exit
Both child processes exit cleanly:
43142 11:44:13.290401 +++ exited with 0 +++
43143 11:44:13.309383 +++ exited with 0 +++
And finally, the parent shell exits:
43141 11:44:13.318626 +++ exited with 0 +++
✅ Step 7: shell wait subProcess Exit
43141 11:44:13.290534 <... wait4 resumed>[{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 43142 #cat
43141 11:44:13.311084 <... wait4 resumed>[{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 43143 #wc
And finally, the parent shell exits:
43141 11:44:13.318626 +++ exited with 0 +++
涉及系统调用总结
PID | Action | Description |
---|---|---|
43141 | pipe2([3, 4], 0) | Creates pipe by shell process |
43141 | clone(...) | Forks first child (PID 27641) |
43141 | clone(...) | Forks second child (PID 27642) |
43142 | dup2(4, 1) | Redirects stdout to pipe write end |
43143 | dup2(3, 0) | Redirects stdin to pipe read end |
43142 | execve("cat", ...) | Replaces child with cat |
43143 | execve("wc", ...) | Replaces child with wc -l |
43141 | wait4() | Waits for both children to finish |
References
Last updated on