Chương 44 · Đường ống và FIFO Chapter 44 · Pipes and FIFOs
Một dòng byte chảy từ tiến trình này sang tiến trình khác Một cái ống dẫn dữ liệu từ chương trình này sang chương trình khác A stream of bytes flowing from one process to another
Kerrisk mở chương bằng nhận xét rằng pipe (đường ống) là phương pháp liên lạc liên tiến trình (IPC) lâu đời nhất trên UNIX, xuất hiện từ bản UNIX phiên bản ba (Third Edition) đầu thập niên 1970.1 Ông trình bày: pipe là một byte stream một chiều, dung lượng có hạn, với bảo đảm ghi nguyên tử cho khối ≤ PIPE_BUF; cách tạo pipe bằng lời gọi pipe(); cách nối cha–con và nối các filter bằng dup2(); cách nói chuyện với một lệnh shell qua popen()/pclose(); FIFO (named pipe) tạo bằng mkfifo() với ngữ nghĩa mở đặc biệt; một ứng dụng client–server dùng FIFO; nhập/xuất không chặn O_NONBLOCK; và ngữ nghĩa read()/write() cùng tín hiệu SIGPIPE/lỗi EPIPE.1
Khi bạn gõ ls | wc -l trong shell, dấu gạch đứng ấy chính là một cái ống: đầu ra của lệnh trái chảy thẳng sang đầu vào của lệnh phải, như nước chảy qua ống nước. Chương này mổ xẻ cái ống đó — nó chở dữ liệu ra sao, ai dựng được nó, và người anh em "có tên gọi" của nó là FIFO. Nhớ nhé: pipe là cái ống dẫn dữ liệu từ chương trình này sang chương trình kia.
Kerrisk opens by noting that the pipe is the oldest method of interprocess communication (IPC) on UNIX, having appeared in Third Edition UNIX in the early 1970s.1 He presents: a pipe as a unidirectional, limited-capacity byte stream with an atomicity guarantee for blocks of ≤ PIPE_BUF bytes; creating a pipe with the pipe() system call; connecting parent and child and chaining filters with dup2(); talking to a shell command via popen()/pclose(); FIFOs (named pipes) created with mkfifo() and their peculiar open semantics; a client–server application using FIFOs; nonblocking I/O with O_NONBLOCK; and the semantics of read()/write() with the SIGPIPE signal and EPIPE error.1
Góc nhìn của nhân: một pipe đơn giản là một vùng đệm duy trì trong bộ nhớ nhân, truy cập qua một cặp bộ mô tả tệp. Chính vì sống trong nhân chứ không có tên trên hệ thống tệp, một pipe không tên chỉ nối được những tiến trình họ hàng — những tiến trình cùng thừa kế cặp bộ mô tả từ một tổ tiên chung đã gọi pipe() trước khi fork(). FIFO khắc phục đúng hạn chế đó: nó có một đường dẫn trên hệ thống tệp nên bất kỳ tiến trình nào có quyền cũng mở được.13The kernel's view: a pipe is simply a buffer maintained in kernel memory, reached through a pair of file descriptors. Because it lives in the kernel with no name on the file system, an unnamed pipe can only connect related processes — ones that inherit the descriptor pair from a common ancestor that called pipe() before fork(). A FIFO fixes exactly that limitation: it has a pathname on the file system, so any process with suitable permissions can open it.13
Luận đề của chương: pipe và FIFO là cùng một ý tưởng — một byte stream một chiều, dung lượng có hạn do nhân giữ — chỉ khác ở chỗ FIFO có tên trên hệ thống tệp nên nối được cả những tiến trình không họ hàng. Nắm được ngữ nghĩa đóng bộ mô tả, trần PIPE_BUF, cách mở FIFO có thể chặn, và cặp SIGPIPE/EPIPE — là nắm được kênh IPC đơn giản nhất mà cả shell lẫn vô số chương trình vẫn dùng mỗi ngày.1
Ý chính: pipe và FIFO đều là một cái "ống" một chiều để chở dữ liệu giữa các chương trình. Pipe thường dùng cho các chương trình có họ hàng; FIFO có tên nên ai cũng gọi được. Hiểu cái ống này là hiểu cách shell nối các lệnh với nhau. Nhớ nhé: pipe là ống không tên cho người nhà, FIFO là ống có tên cho tất cả.
The chapter's thesis: pipes and FIFOs are the same idea — a unidirectional, limited-capacity byte stream held by the kernel — differing only in that a FIFO has a name on the file system, so it can connect unrelated processes too. Grasping the descriptor-closing discipline, the PIPE_BUF limit, the FIFO's blocking open, and the SIGPIPE/EPIPE pair is grasping the simplest IPC channel that the shell and countless programs still use every day.1
Cụm khái niệm 01 Concept cluster 01
Pipe là một dòng byte một chiều, do nhân giữ, với dung lượng hữu hạn Pipe là một cái ống một chiều, chở dữ liệu thành dòng, có lúc đầy A pipe is a unidirectional kernel byte stream with a finite capacity
Trước khi tạo một pipe, cần hiểu nó là gì. Kerrisk nêu một loạt đặc tính nền tảng. Pipe là một byte stream: không có khái niệm thông điệp hay ranh giới thông điệp — bên đọc có thể đọc khối bất kỳ kích thước nào, bất kể bên ghi đã ghi thành từng khối to nhỏ ra sao, và dữ liệu đi qua theo đúng thứ tự được ghi, không thể truy cập ngẫu nhiên bằng lseek().13 Pipe là một chiều: dữ liệu chỉ chảy một hướng, một đầu để ghi và đầu kia để đọc.1 Và pipe có dung lượng giới hạn: nó chỉ là một vùng đệm trong bộ nhớ nhân, đầy rồi thì lời ghi tiếp theo bị chặn cho tới khi bên đọc lấy bớt dữ liệu ra.1
Hãy hình dung một ống nước thật: nước chỉ chảy một chiều, bạn đổ vào một đầu và hứng ở đầu kia. Nước không "chia thành gói" — nó là một dòng liên tục, ai hứng bao nhiêu tùy ca lớn ca nhỏ. Và ống chỉ chứa được chừng ấy nước: đổ nhanh quá mà chưa ai hứng thì phải chờ. Pipe trong máy tính đúng như vậy, chỉ thay nước bằng byte. Nhớ nhé: pipe là ống một chiều, chở byte thành dòng, và có lúc đầy phải chờ.
Before creating a pipe, you need to know what it is. Kerrisk lists several foundational properties. A pipe is a byte stream: there is no concept of messages or message boundaries — the reader may read blocks of any size regardless of how the writer wrote them, and the data passes through in exactly the order it was written, with no random access via lseek().13 A pipe is unidirectional: data flows in only one direction, one end for writing and the other for reading.1 And a pipe has a limited capacity: it is just a buffer in kernel memory, and once full, further writes block until the reader removes some data.1
Định nghĩa. Một pipe là một kênh IPC một chiều: một vùng đệm vòng trong bộ nhớ nhân, truy cập qua hai bộ mô tả tệp — một cho đầu đọc, một cho đầu ghi. Bên đọc một pipe rỗng bị chặn tới khi có ít nhất một byte được ghi; khi đầu ghi đóng lại, bên đọc thấy cuối tệp (read() trả về 0) sau khi đã đọc hết dữ liệu còn lại.12
Nói đơn giản. Pipe là cái ống có hai đầu: một đầu đổ vào, một đầu lấy ra. Lấy ở ống rỗng thì phải đứng chờ tới khi có người đổ vào. Khi không còn ai đổ nữa và ống đã cạn, người lấy nhận tín hiệu "hết rồi". Nhớ nhé: ống rỗng thì đợi, hết người đổ thì báo hết.
Definition. A pipe is a unidirectional IPC channel: a circular buffer in kernel memory, reached through two file descriptors — one for the read end, one for the write end. A reader of an empty pipe blocks until at least one byte is written; when the write end is closed, the reader sees end-of-file (read() returns 0) once it has read all remaining data.12
ghi ──► [ b y t e s t r e a m ] ──► đọc
• không ranh giới thông điệp
• đọc đúng thứ tự đã ghi
• không lseek()
• ghi ≤ PIPE_BUF là nguyên tử
• ống đầy ⇒ ghi bị chặn
đổ vào ──► [ ống byte ] ──► lấy ra
• một chiều
• dòng liên tục, không chia gói
• đầy thì phải chờ
write ──► [ b y t e s t r e a m ] ──► read
• no message boundaries
• read in write order
• no lseek()
• writes ≤ PIPE_BUF are atomic
• full pipe ⇒ write blocks
-
byte stream
Không có "thông điệp" — chỉ có dòng byte Dữ liệu là một dòng liền, không phải từng gói There are no "messages" — only a byte stream
Nếu bên ghi gọi
write()ba lần, bên đọc không nhất thiết nhận lại thành ba khối tương ứng — nó có thể gộp cả ba vào một lầnread(), hoặc đọc nhỏ giọt từng chút. Pipe không ghi nhớ ranh giới giữa các lần ghi. Muốn có khái niệm "thông điệp rời rạc", ứng dụng phải tự dựng lấy; nếu không, tốt hơn nên dùng cơ chế khác như hàng thông điệp hoặc datagram socket.1Đổ ba ca nước vào ống, người hứng ở đầu kia có thể hứng gọn vào một ca to, hoặc hứng làm năm lần nhỏ — ống không nhớ bạn đã đổ thành mấy lần. Byte cũng vậy. Nhớ nhé: pipe không chia dữ liệu thành gói, bạn muốn gói thì tự đóng gói lấy.
If the writer calls
write()three times, the reader does not necessarily get three matching blocks back — it may gather all three into oneread(), or sip them a little at a time. The pipe keeps no record of boundaries between writes. To get discrete "messages," the application must build that itself; otherwise a mechanism such as a message queue or datagram socket is preferable.1Bạn gặp nó ở đâuWhere you meet it Khi gửi các bản ghi có kích thước khác nhau qua pipe mà bên nhận đọc sai ranh giới — đó là lúc bạn cần tự thêm độ dài hay dấu phân cách vào mỗi bản ghi.1 Khi bạn gửi nhiều mẩu tin qua ống mà bên kia đọc lẫn vào nhau — hãy tự đánh dấu đâu là hết một mẩu. When sending variable-sized records through a pipe and the receiver reads the boundaries wrong — that is when you add a length or delimiter to each record yourself.1 -
unidirectional
Một chiều — muốn hai chiều thì tạo hai pipe Chỉ chảy một hướng; cần hai hướng thì dùng hai ống One direction — for two-way, make two pipes
Dữ liệu chỉ đi một hướng qua pipe. Trên vài hiện thực UNIX khác — đáng chú ý là các bản dẫn xuất từ System V Release 4 — pipe là hai chiều (gọi là stream pipe), nhưng tính chất này không có trong bất kỳ chuẩn UNIX nào, nên tốt nhất đừng dựa vào.1 Nếu cần liên lạc hai chiều, giải pháp đơn giản là tạo hai pipe, mỗi cái một hướng; hoặc dùng cặp UNIX domain stream socket qua
socketpair(), vốn hai chiều một cách chuẩn mực.1Một cái ống nước chỉ cho nước chảy một chiều. Muốn trò chuyện qua lại hai bên thì lắp hai ống: một ống "tôi nói cho bạn", một ống "bạn nói cho tôi". Nhớ nhé: một ống một chiều, cần qua lại thì bắc hai ống.
Data travels in only one direction through a pipe. On some other UNIX implementations — notably those derived from System V Release 4 — pipes are bidirectional (so-called stream pipes), but this is not specified by any UNIX standard, so it is best not to rely on it.1 If you need two-way communication, the simple answer is to create two pipes, one per direction; or use a UNIX domain stream socket pair via
socketpair(), which is bidirectional in a standardized way.1Bạn gặp nó ở đâuWhere you meet it Khi dựng hai pipe cho liên lạc qua lại — hãy đề phòng deadlock: cả hai tiến trình cùng chặn vì đang chờ đọc từ ống rỗng hoặc chờ ghi vào ống đã đầy.1 Khi dùng hai ống để nói qua lại — coi chừng cả hai cùng đứng chờ nhau mà không ai nhúc nhích. When wiring two pipes for two-way talk — beware deadlock: both processes blocking because each waits to read an empty pipe or write a full one.1 -
PIPE_BUF
Ghi ≤ PIPE_BUF byte được bảo đảm nguyên tử Đổ một lượng vừa đủ nhỏ thì không bị trộn lẫn Writes of ≤ PIPE_BUF bytes are guaranteed atomic
Nếu nhiều tiến trình cùng ghi vào một pipe, dữ liệu của chúng không bị trộn lẫn miễn là mỗi lần ghi không quá
PIPE_BUFbyte. SUSv3 đòiPIPE_BUFít nhất bằng_POSIX_PIPE_BUF(512); giá trị thực khác nhau giữa các hệ — ví dụ 512 byte trên FreeBSD 6.0, 4096 byte trên Tru64 5.1, 5120 byte trên Solaris 8. Trên Linux,PIPE_BUFbằng 4096.13 Khi ghi khối lớn hơnPIPE_BUF, nhân có thể chia thành nhiều mẩu nhỏ và, nếu có nhiều bên ghi, các mẩu đó có thể bị xen kẽ với dữ liệu của tiến trình khác.1Tưởng tượng nhiều người cùng đổ nước vào một ống. Nếu mỗi người chỉ đổ một ca nhỏ vừa đủ, nước của mỗi người vào gọn một mạch, không lẫn với người khác. Nhưng nếu ai đó đổ cả xô to, xô ấy có thể bị chia nhỏ và xen giữa những ca của người bên cạnh. Nhớ nhé: đổ ít một lần thì gọn, đổ quá nhiều thì dễ bị trộn.
If multiple processes write to one pipe, their data is not intermingled as long as each write is no more than
PIPE_BUFbytes. SUSv3 requiresPIPE_BUFto be at least_POSIX_PIPE_BUF(512); the actual value varies — e.g. 512 bytes on FreeBSD 6.0, 4096 on Tru64 5.1, 5120 on Solaris 8. On Linux,PIPE_BUFis 4096.13 When writing a block larger thanPIPE_BUF, the kernel may split it into smaller pieces and, if there are several writers, those pieces may be interleaved with other processes' data.1Bạn gặp nó ở đâuWhere you meet it Khi nhiều tiến trình cùng ghi log vào một pipe — giữ mỗi bản ghi ≤PIPE_BUFbyte để các dòng không bị cắt xén chồng lên nhau.7 Khi nhiều chương trình cùng ghi vào một ống — giữ mỗi lần ghi đủ nhỏ để các dòng không lẫn vào nhau. When several processes log into one pipe — keep each record ≤PIPE_BUFbytes so the lines are not chopped and mixed.7Khác biệt giữa các phiên bản nhânA kernel-version difference
Kerrisk lưu ý: trên Linux 2.2, mọi lời ghi vào pipe — bất kể kích thước — đều nguyên tử, trừ khi bị ngắt bởi một trình xử lý tín hiệu. Từ Linux 2.4 trở đi, bất kỳ lời ghi lớn hơn
PIPE_BUFbyte đều có thể bị xen kẽ với lời ghi của tiến trình khác, vì phần mã hiện thực pipe đã thay đổi lớn giữa hai dòng nhân 2.2 và 2.4.1Kerrisk notes: on Linux 2.2, pipe writes of any size were atomic unless interrupted by a signal handler. From Linux 2.4 onward, any write larger thanPIPE_BUFbytes may be interleaved with other processes' writes, because the pipe implementation code changed substantially between the 2.2 and 2.4 kernels.1Trần ảnh hưởng tới lúc nào dữ liệu được chuyểnThe limit shapes when data moves
Khi ghi tối đa
PIPE_BUFbyte,write()sẽ chặn nếu cần cho tới khi có đủ chỗ để hoàn tất nguyên tử. Khi ghi nhiều hơnPIPE_BUF,write()đổ được chừng nào hay chừng ấy rồi chặn chờ bên đọc lấy bớt; nếu bị tín hiệu ngắt giữa chừng, nó trả về số byte đã chuyển (một "partial write").1When writing up toPIPE_BUFbytes,write()blocks if needed until there is enough room to complete atomically. When writing more thanPIPE_BUF,write()transfers as much as it can, then blocks until the reader drains some; if a signal interrupts it midway, it returns the count of bytes transferred (a "partial write").1
Cụm khái niệm 02 Concept cluster 02
Dựng ống: pipe(), nối cha–con, nối filter, và popen() Tự tay lắp ống: tạo, nối cha–con, nối lệnh, nói với shell Plumbing: pipe(), parent–child, connecting filters, and popen()
Lời gọi pipe(int filedes[2]) tạo một pipe mới và trả về hai bộ mô tả tệp đang mở trong mảng filedes: filedes[0] cho đầu đọc và filedes[1] cho đầu ghi.12 Một pipe gần như vô dụng trong phạm vi một tiến trình đơn lẻ; sức mạnh của nó lộ ra khi phối hợp với fork(): tiến trình gọi pipe() rồi fork(), và vì con thừa kế bản sao các bộ mô tả của cha, hai bên lập tức có chung một pipe nối liền. Ngay sau đó, mỗi bên đóng đầu không dùng: bên đọc đóng filedes[1], bên ghi đóng filedes[0].1
Hàm pipe() giao cho bạn hai đầu ống: một đầu để lấy ra (số 0), một đầu để đổ vào (số 1). Một mình thì ống chẳng để làm gì; nhưng khi một tiến trình đẻ ra tiến trình con, đứa con được chép lại hai đầu ống ấy — thế là hai cha con nối với nhau. Việc đầu tiên mỗi người làm là bịt cái đầu mình không dùng tới. Nhớ nhé: tạo ống, sinh con, rồi ai cũng bịt bớt đầu thừa.
The call pipe(int filedes[2]) creates a new pipe and returns two open file descriptors in the array filedes: filedes[0] for the read end and filedes[1] for the write end.12 A pipe is nearly useless within a single process; its power appears when combined with fork(): a process calls pipe() then fork(), and because the child inherits copies of the parent's descriptors, both sides immediately share one connected pipe. Right afterward, each side closes the end it does not use: the reader closes filedes[1], the writer closes filedes[0].1
Định nghĩa. Đóng bộ mô tả không dùng không chỉ để khỏi cạn hạn mức bộ mô tả — nó thiết yếu cho sự đúng đắn. Bên đọc phải đóng đầu ghi để, khi bên kia ghi xong và đóng đầu ghi của họ, nó mới thấy cuối tệp; nếu không, read() sẽ chặn mãi vì nhân biết vẫn còn một đầu ghi đang mở. Bên ghi phải đóng đầu đọc để nhận được SIGPIPE/EPIPE khi không còn ai đọc.1
Nói đơn giản. Phải bịt đầu ống mình không dùng, không phải cho gọn mà vì nếu không mọi thứ sẽ kẹt. Người lấy nước mà không bịt đầu đổ của chính mình thì sẽ đứng chờ mãi, tưởng vẫn còn người sắp đổ. Nhớ nhé: không bịt đầu thừa thì chương trình treo.
Definition. Closing unused descriptors is not merely about not exhausting the descriptor limit — it is essential for correctness. The reader must close the write end so that, when the other side finishes and closes its write end, the reader sees end-of-file; otherwise read() blocks forever because the kernel knows a write end is still open. The writer must close the read end to receive SIGPIPE/EPIPE when no reader remains.1
#include <unistd.h>
int pipe(int filedes[2]);
/* filedes[0] = đầu đọc */
/* filedes[1] = đầu ghi */
/* trả 0 nếu ok, -1 nếu lỗi */
pipe(filedes)
filedes[0] --> lấy ra
filedes[1] --> đổ vào
rồi fork() để chia cho con
#include <unistd.h>
int pipe(int filedes[2]);
/* filedes[0] = read end */
/* filedes[1] = write end */
/* returns 0 on success, -1 on error */
-
close()
Đóng đầu không dùng — quy tắc sống còn của pipe Bịt đầu ống mình không dùng, nếu không sẽ kẹt Close the unused end — the pipe's survival rule
Thông thường một pipe chỉ nên có một bên ghi. Lý do đóng đầu đọc ở bên ghi: khi một tiến trình ghi vào pipe mà không còn tiến trình nào mở đầu đọc, nhân gửi tín hiệu
SIGPIPE(mặc định giết tiến trình); nếu bắt hoặc bỏ qua tín hiệu,write()thất bại với lỗiEPIPE("broken pipe").17 Còn khi tất cả bộ mô tả trỏ tới pipe trong mọi tiến trình đều đóng, pipe mới bị hủy và tài nguyên được giải phóng — mọi dữ liệu chưa đọc bị mất.1Nếu người lấy nước quên bịt đầu đổ của chính mình, họ sẽ chờ mãi vì nghĩ "biết đâu lát nữa có người đổ" — kể cả khi người đổ thật đã bỏ đi. Còn nếu đổ nước vào ống mà đầu kia chẳng còn ai hứng, hệ thống "đập vỡ ống" báo lỗi. Nhớ nhé: bịt đầu thừa để thấy khi nào hết, và để biết khi nào không còn ai nghe.
Normally a pipe should have only one writer. Why the writer closes the read end: when a process writes to a pipe with no process holding the read end open, the kernel sends
SIGPIPE(which by default kills the process); if you catch or ignore it,write()fails withEPIPE("broken pipe").17 And only when all descriptors referring to the pipe in every process are closed is the pipe destroyed and its resources freed — any unread data is lost.1Bạn gặp nó ở đâuWhere you meet it Mỗi khi một chương trình "treo" ởread()trên pipe dù bên ghi đã xong — gần như chắc chắn là một đầu ghi còn mở đâu đó chưa được đóng.1 Khi chương trình đứng im mãi lúc đọc ống dù bên kia đã xong — thường là vì còn một đầu đổ chưa bịt. Whenever a program "hangs" onread()though the writer has finished — almost always a write end is still open somewhere, unclosed.1pipe() và pipe2()pipe() and pipe2()
Từ nhân 2.6.27, Linux hỗ trợ lời gọi phi chuẩn
pipe2(), giốngpipe()nhưng thêm đối sốflags. CờO_CLOEXECbật close-on-exec cho hai bộ mô tả mới; cờO_NONBLOCKđánh dấu chúng là không chặn, tiết kiệm các lời gọifcntl()về sau.12Since kernel 2.6.27, Linux supports the nonstandardpipe2(), likepipe()but with an extraflagsargument. TheO_CLOEXECflag sets close-on-exec on the two new descriptors;O_NONBLOCKmarks them nonblocking, saving laterfcntl()calls.12Tiến trình họ hàng, không chỉ cha–conRelated processes, not just parent–child
Pipe dùng được giữa bất kỳ hai (hay nhiều) tiến trình họ hàng, miễn pipe được tạo bởi một tổ tiên chung trước chuỗi
fork(). Ví dụ quen thuộc nhất là hai tiến trình anh em — cha tạo pipe rồi tạo hai con; đó chính là cách shell dựng một pipeline.1A pipe works between any two (or more) related processes, as long as it was created by a common ancestor before the series offork()calls. The most familiar case is two sibling processes — a parent creates the pipe then creates two children; that is exactly how the shell builds a pipeline.1 -
dup2()
Nối filter: bẻ stdin/stdout vào pipe bằng dup2() Nối hai lệnh: cắm đầu ra lệnh này vào đầu vào lệnh kia Connecting filters: bend stdin/stdout into a pipe with dup2()
Để tái tạo
ls | wc -l, shell phải làm cho đầu ra chuẩn củalschảy vào đầu ghi của pipe và đầu vào chuẩn củawclấy từ đầu đọc — mà không sửa mã của chính hai lệnh đó. Thủ thuật là nhân bản bộ mô tả:dup2(pfd[1], STDOUT_FILENO)làm bộ mô tả 1 trỏ tới đầu ghi của pipe, rồi đóng bản thừa.110 Kerrisk minh họa bằng chương trình nốilsvớiwc -l; chạy nó cho ra cùng con số với khi gõls | wc -ltrực tiếp trong shell.1Cái hay của shell là nó khiến
lstưởng mình vẫn in ra màn hình, cònwctưởng mình vẫn đọc từ bàn phím — trong khi thực ra chúng đang nói chuyện qua một cái ống giấu kín. Mẹo nằm ở chỗ "tráo" đầu ra chuẩn và đầu vào chuẩn sang hai đầu ống. Nhớ nhé: nối lệnh là lừa cho mỗi lệnh tưởng nó vẫn nói với màn hình và bàn phím.To recreate
ls | wc -l, the shell must makels's standard output flow into the pipe's write end andwc's standard input come from the read end — without touching the two commands' code. The trick is descriptor duplication:dup2(pfd[1], STDOUT_FILENO)makes descriptor 1 refer to the pipe's write end, then closes the surplus copy.110 Kerrisk illustrates this with a program wiringlstowc -l; running it produces the same count as typingls | wc -ldirectly in the shell.1Bạn gặp nó ở đâuWhere you meet it Mỗi dấu|bạn gõ trong shell là một lần dàn dựng đúng chuỗipipe()→fork()→dup2()→exec()này cho bạn.1 Mỗi dấu gạch đứng trong shell chính là lúc nó âm thầm lắp và nối ống giúp bạn. Every|you type in the shell stages exactly thispipe()→fork()→dup2()→exec()dance for you.1Vì sao dùng dup2() thay close()+dup()Why dup2() over close()+dup()
Cách cũ là
close(STDOUT_FILENO)rồidup(pfd[1])— dựa vào việcdup()dùng bộ mô tả rảnh nhỏ nhất. Nhưng nếu bộ mô tả 1 vốn đã đóng, ta có thể vô tình gán nhầm.dup2(pfd[1], STDOUT_FILENO)nói rõ đích, tránh được bẫy đó; sau đó nên bọc lời gọi trongif (pfd[1] != STDOUT_FILENO)để phòng trường hợp chúng vốn trùng nhau.1The old way isclose(STDOUT_FILENO)thendup(pfd[1])— relying ondup()using the lowest free descriptor. But if descriptor 1 was already closed, you can bind the wrong thing.dup2(pfd[1], STDOUT_FILENO)names the target explicitly, avoiding that trap; afterwards bracket the calls inif (pfd[1] != STDOUT_FILENO)in case they already coincide.1FIFO + tee(1) tạo pipeline rẽ nhánhFIFO + tee(1) forks a pipeline
Pipeline shell vốn tuyến tính, nhưng dùng một FIFO cùng lệnh
teeta có thể rẽ đôi luồng:ls -l | tee myfifo | sort -k5ngửi một bản sao đầu ra qua FIFO cho một tiến trình khác đọc song song.1A shell pipeline is linear, but with a FIFO plus theteecommand you can fork the stream:ls -l | tee myfifo | sort -k5nsends a duplicate of the output through the FIFO for another process to read in parallel.1 -
popen()
popen()/pclose(): nói chuyện với một lệnh shell popen(): chạy một lệnh shell rồi đọc hoặc gửi dữ liệu popen()/pclose(): talk to a shell command
Một việc thường gặp là chạy một lệnh shell rồi đọc đầu ra hoặc gửi đầu vào cho nó.
popen(command, mode)làm gọn: nó tạo một pipe,fork()một con đểexec()một shell chạy chuỗicommand, rồi trả về một luồngFILE *. Nếumodelà"r", lời gọi đọc đầu ra chuẩn của lệnh; nếu là"w", nó ghi vào đầu vào chuẩn của lệnh. Vì pipe một chiều, không thể giao tiếp hai chiều. Kết thúc bằngpclose(), hàm này đợi shell chạy xong và trả về trạng thái kết thúc.16popen()giống như bạn nhờ shell chạy giùm một lệnh rồi đưa cho bạn một cái phễu: phễu "r" để hứng những gì lệnh in ra, phễu "w" để rót dữ liệu cho lệnh ăn. Xong việc thì gọipclose()để dọn dẹp. Nhớ nhé: popen là nhờ shell chạy lệnh và trao cho bạn một cái phễu đọc hoặc ghi.A common need is to run a shell command and read its output or feed it input.
popen(command, mode)makes this tidy: it creates a pipe,fork()s a child toexec()a shell runningcommand, and returns aFILE *stream. Ifmodeis"r", the caller reads the command's standard output; if"w", it writes to the command's standard input. Because pipes are unidirectional, two-way talk is impossible. Finish withpclose(), which waits for the shell to finish and returns its termination status.16Bạn gặp nó ở đâuWhere you meet it Nguy hiểm thật sự: nếucommandđược dựng từ đầu vào người dùng, một chuỗi như; rm *có thể bị shell thực thi — luôn phải kiểm tra kỹ đầu vào trước khi đưa vàopopen()haysystem().16 Coi chừng: nếu lệnh được ghép từ chữ người dùng gõ, kẻ xấu có thể lén chèn lệnh phá hoại. Luôn kiểm tra đầu vào trước. The real danger: ifcommandis built from user input, a string like; rm *may be executed by the shell — always check input carefully before passing it topopen()orsystem().16
Cụm khái niệm 03 Concept cluster 03
FIFO: một pipe có tên trên hệ thống tệp — và một ứng dụng client–server FIFO: cái ống có tên, ai cũng gọi được, dùng cho client–server FIFOs: a pipe with a name on the file system — and a client–server app
Về ngữ nghĩa, FIFO giống pipe; khác biệt chính là FIFO có một tên trong hệ thống tệp và được mở như một tệp thường. Nhờ đó, FIFO nối được cả những tiến trình không họ hàng — chẳng hạn một client và một server.14 Tên "FIFO" đến từ "first in, first out": dữ liệu được đọc đúng thứ tự được ghi; FIFO cũng thường được gọi là named pipe. Tạo FIFO từ shell bằng lệnh mkfifo, hoặc trong chương trình bằng hàm mkfifo(pathname, mode).15
FIFO cũng là cái ống một chiều như pipe, chỉ khác là nó có tên và nằm trên đĩa như một tệp. Vì có tên, hai chương trình chẳng quen biết gì nhau cũng có thể hẹn "gặp nhau ở cái ống tên này". Tên FIFO nghĩa là "vào trước, ra trước" — đổ vào trước thì lấy ra trước. Nhớ nhé: FIFO là pipe có tên, nên người lạ cũng gặp nhau được.
Semantically a FIFO is like a pipe; the chief difference is that a FIFO has a name in the file system and is opened like a regular file. This lets a FIFO connect even unrelated processes — such as a client and a server.14 The name "FIFO" comes from "first in, first out": data is read in the order it was written; FIFOs are also called named pipes. Create one from the shell with the mkfifo command, or in a program with mkfifo(pathname, mode).15
Định nghĩa. Mở một FIFO có ngữ nghĩa khác thường. Vì cách dùng hợp lý duy nhất là có một bên đọc và một bên ghi ở hai đầu, nên mặc định mở FIFO để đọc (O_RDONLY) sẽ chặn cho tới khi một tiến trình khác mở nó để ghi (O_WRONLY), và ngược lại. Nói cách khác, việc mở FIFO đồng bộ hóa hai bên. Nếu đầu kia đã mở sẵn, open() thành công ngay.14
Nói đơn giản. Khi bạn mở một đầu FIFO, bạn phải đứng chờ tới khi có người mở đầu kia — như hai người hẹn nhau ở quán, ai tới trước thì ngồi đợi. Chỉ khi đủ cả hai, cuộc nói chuyện mới bắt đầu. Nhớ nhé: mở FIFO một mình thì phải đợi bạn mình tới.
Definition. Opening a FIFO has unusual semantics. Since the only sensible use is a reader on one end and a writer on the other, by default opening a FIFO for reading (O_RDONLY) blocks until another process opens it for writing (O_WRONLY), and vice versa. In other words, opening a FIFO synchronizes the two sides. If the opposite end is already open, open() succeeds immediately.14
$ mkfifo myfifo # tạo từ shell
#include <sys/stat.h>
int mkfifo(const char *path,
mode_t mode);
# ls -l: kiểu 'p'; ls -F: thêm '|'
$ mkfifo myfifo
mkfifo("đường/dẫn", quyền)
ls -l thấy chữ 'p' ở đầu dòng
$ mkfifo myfifo # from the shell
#include <sys/stat.h>
int mkfifo(const char *path,
mode_t mode);
# ls -l: type 'p'; ls -F: appends '|'
-
mkfifo()
mkfifo() tạo một FIFO với quyền như một tệp mkfifo() tạo ống có tên, có quyền như tệp thường mkfifo() creates a FIFO with file-like permissions
Hàm
mkfifo(pathname, mode)tạo một FIFO mới với quyền cho bởimode(đã lọc qua umask). Khi dùngstat()trên một FIFO, kiểu tệp trả về làS_IFIFO; khi liệt kê bằngls -l, FIFO hiện chữ p ở cột đầu, cònls -Fthêm ký hiệu ống|vào sau tên.15 Về lịch sử, FIFO được tạo bằngmknod(pathname, S_IFIFO, 0); POSIX.1-1990 đưa ramkfifo()như một API gọn hơn, tránh sự tổng quát củamknod().19Gõ
mkfifo myfifolà bạn đã tạo một cái ống có tên nằm ngay trong thư mục, giống như tạo một tệp. Khi liệt kê thư mục, nó hiện chữ p ở đầu để báo "đây là một ống, không phải tệp thường". Nhớ nhé: mkfifo đặt một cái ống có tên lên đĩa cho mọi người dùng chung.The function
mkfifo(pathname, mode)creates a new FIFO with permissions given bymode(masked by the umask). Runningstat()on a FIFO returns file typeS_IFIFO; listing withls -lshows a p in the first column, andls -Fappends the pipe symbol|to the name.15 Historically FIFOs were made withmknod(pathname, S_IFIFO, 0); POSIX.1-1990 introducedmkfifo()as a simpler API avoiding the generality ofmknod().19Bạn gặp nó ở đâuWhere you meet it Khi cần một kênh IPC đơn giản giữa các chương trình khởi chạy độc lập mà không chia sẻ tổ tiên — một FIFO đặt ở đường dẫn ai cũng biết là đủ.4 Khi hai chương trình chạy riêng cần trao dữ liệu — hẹn nhau ở một FIFO có tên cố định là xong. When you need a simple IPC channel between independently launched programs with no shared ancestor — a FIFO at a known path is enough.4 -
open()
Mở FIFO chặn tới khi đủ cả hai đầu Mở một đầu phải chờ đầu kia có người mở Opening a FIFO blocks until both ends are present
Đây là điểm dễ gây bất ngờ nhất với người mới. Mở một FIFO để đọc sẽ đứng im ở lời gọi
open()cho tới khi một tiến trình khác mở nó để ghi — và đối xứng cho chiều ngược lại. Kerrisk tóm tắt bốn trường hợp trong Bảng 44-1. Hành vi chặn này chính là một cơ chế đồng bộ tiện lợi: hai tiến trình "gặp nhau" tại FIFO.14Nếu bạn là người tới quán trước, bạn ngồi đợi; cửa chỉ "mở thông" khi cả hai người cùng có mặt. FIFO cũng thế: mở một đầu thì chờ, tới khi đầu kia có người mở mới bắt đầu chạy tiếp. Nhớ nhé: mở FIFO là một cuộc hẹn — ai tới trước thì đợi.
This is the point that most surprises newcomers. Opening a FIFO for reading stalls at the
open()call until another process opens it for writing — and symmetrically the other way. Kerrisk summarizes the four cases in Table 44-1. This blocking is a handy synchronization mechanism: two processes "meet" at the FIFO.14Bạn gặp nó ở đâuWhere you meet it Khi một chương trình "treo" ngay tạiopen()trên FIFO — nó không hỏng, chỉ đang kiên nhẫn chờ đầu kia xuất hiện.1 Khi chương trình đứng im lúc mở FIFO — yên tâm, nó đang đợi người ở đầu kia. When a program "hangs" right atopen()on a FIFO — it is not broken, just patiently waiting for the other end to appear.1Cái bẫy O_RDWRThe O_RDWR trap
Trên hầu hết hiện thực (gồm Linux), có thể lách chặn bằng cách mở FIFO với cờ
O_RDWR, nhưng SUSv3 nói rõ hành vi này là không xác định — nên tránh để bảo đảm khả chuyển. Muốn không chặn một cách chuẩn mực, hãy dùngO_NONBLOCK.14On most implementations (including Linux) you can circumvent the blocking by opening the FIFO withO_RDWR, but SUSv3 explicitly calls this unspecified — so avoid it for portability. For a standardized non-blocking open, useO_NONBLOCK.14Dữ liệu còn lại bị bỏLeftover data is discarded
Như với pipe, khi mọi bộ mô tả trỏ tới một FIFO đều đóng, dữ liệu chưa đọc còn lại bị bỏ đi. FIFO vẫn tồn tại như một mục trên hệ thống tệp, nhưng nội dung trong "ống" thì mất.1As with pipes, when all descriptors referring to a FIFO are closed, any outstanding unread data is discarded. The FIFO still exists as a file-system entry, but the contents in the "pipe" are gone.1
-
client-server
Một server, nhiều client: tên quen biết và FIFO riêng Một người phục vụ, nhiều người gọi, ai cũng có ống riêng One server, many clients: a well-known name and per-client FIFOs
Kerrisk dựng một ứng dụng client–server minh họa: server cấp các số thứ tự duy nhất cho mỗi client. Mọi client gửi yêu cầu qua một server FIFO tên cố định (
/tmp/seqnum_sv) mà ai cũng biết. Nhưng không thể dùng chung một FIFO cho hồi đáp, vì các client sẽ tranh nhau đọc và có thể đọc nhầm hồi đáp của nhau; vậy nên mỗi client tạo một FIFO riêng, tên dựng từ PID của nó, để server trả lời đúng địa chỉ.1Hãy hình dung một quầy phát số thứ tự. Ai cũng bỏ phiếu yêu cầu vào một hộp chung đặt ở chỗ quen thuộc (server FIFO). Nhưng khi nhận kết quả, mỗi người phải có hộp thư riêng mang tên mình, kẻo lấy nhầm số của người khác. Nhớ nhé: hỏi thì dùng hộp chung, trả lời thì mỗi người một hộp riêng.
Kerrisk builds an illustrative client–server application: a server hands out unique sequence numbers to each client. Every client sends its request through one server FIFO at a fixed, well-known name (
/tmp/seqnum_sv). But a single FIFO cannot carry the replies, because clients would race to read and might read each other's responses; so each client creates its own FIFO, named from its PID, for the server to reply to the right address.1Bạn gặp nó ở đâuWhere you meet it Mẫu "địa chỉ quen biết + kênh phản hồi riêng cho mỗi client" lặp lại trong thiết kế server — từ FIFO tới socket; học nó ở đây là nền cho các chương về socket sau này.1 Kiểu "một chỗ chung để hỏi, mỗi người một chỗ riêng để nghe trả lời" sẽ gặp lại hoài trong lập trình mạng. The "well-known address + a private reply channel per client" pattern recurs in server design — from FIFOs to sockets; learning it here grounds the later socket chapters.1
Cụm khái niệm 04 Concept cluster 04
Không chặn, và ngữ nghĩa chính xác của read() và write() Chế độ không chờ, và luật đọc/ghi trên pipe và FIFO Nonblocking, and the exact semantics of read() and write()
Khi mở một FIFO, mặc định tiến trình bị chặn nếu đầu kia chưa được mở. Muốn không chặn, truyền cờ O_NONBLOCK cho open(). Cờ này bất đối xứng: nếu mở để đọc mà chưa ai mở đầu ghi, open() vẫn thành công ngay; nhưng nếu mở để ghi mà chưa ai mở đầu đọc, open() thất bại với errno đặt thành ENXIO.14 Sự bất đối xứng ấy hợp lý: đọc khi chưa có người ghi thì chỉ là không có dữ liệu, còn ghi khi chưa có người đọc sẽ sinh SIGPIPE và lỗi EPIPE.1
Mặc định, mở FIFO là phải đứng chờ đầu kia. Thêm cờ O_NONBLOCK nghĩa là "đừng bắt tôi chờ". Nhưng có một điểm lạ: mở để đọc thì được vào ngay dù chưa ai ghi; còn mở để ghi mà chưa ai đọc thì bị từ chối luôn. Nhớ nhé: không chờ thì người đọc được vào ngay, người ghi thì bị đuổi nếu chưa có ai nghe.
When opening a FIFO, a process blocks by default if the other end is not yet open. To avoid blocking, pass the O_NONBLOCK flag to open(). The flag is asymmetric: opening for reading with no writer yet succeeds immediately; but opening for writing with no reader yet fails with errno set to ENXIO.14 The asymmetry makes sense: reading with no writer just yields no data, while writing with no reader would raise SIGPIPE and the EPIPE error.1
Định nghĩa. Cờ O_NONBLOCK không chỉ đổi ngữ nghĩa của open() mà — vì nó bám theo open file description — còn đổi ngữ nghĩa các read()/write() sau đó. Khác biệt duy nhất khi đọc là: khi pipe rỗng mà đầu ghi vẫn mở, read() thường sẽ chặn, còn read() không chặn thất bại ngay với lỗi EAGAIN.1
Nói đơn giản. Khi đã bật chế độ "không chờ", việc đọc và ghi về sau cũng không chờ nữa. Đọc ống rỗng mà vẫn còn người ghi: bình thường thì đứng đợi, còn ở chế độ không chờ thì bị báo "chưa có gì, thử lại sau". Nhớ nhé: không chờ nghĩa là chưa sẵn thì báo lỗi ngay chứ không đứng đợi.
Definition. The O_NONBLOCK flag changes not only open() but — because it stays set on the open file description — the semantics of later read()/write() too. The only difference when reading is: with an empty pipe and the write end open, a normal read() blocks, while a nonblocking read() fails at once with EAGAIN.1
open("fifo", O_RDONLY | O_NONBLOCK)
mở để đọc, chưa ai ghi → OK ngay
mở để ghi, chưa ai đọc → lỗi ENXIO
read() ống rỗng, đầu ghi mở:
chặn ↔ không chặn: EAGAIN
O_NONBLOCK = "đừng bắt tôi chờ"
đọc: vào ngay
ghi mà chưa ai đọc: bị từ chối
đọc ống rỗng: báo "thử lại sau"
open("fifo", O_RDONLY | O_NONBLOCK)
open for read, no writer → OK now
open for write, no reader → ENXIO
read() empty pipe, write end open:
block ↔ nonblock: EAGAIN
-
O_NONBLOCK
O_NONBLOCK: không chờ, và một bất đối xứng đọc/ghi O_NONBLOCK: đọc được vào ngay, ghi thì có thể bị đuổi O_NONBLOCK: don't wait, and a read/write asymmetry
Dùng
O_NONBLOCKkhi mở FIFO phục vụ hai mục đích chính: cho phép một tiến trình mở cả hai đầu FIFO (mở đọc vớiO_NONBLOCKtrước, rồi mở ghi), và ngăn deadlock giữa các tiến trình cùng mở hai FIFO.1 Nếu đầu kia đã mở sẵn, cờ này không có tác dụng gì vớiopen()— nó mở thành công ngay như thường.1Chế độ "không chờ" có hai cái lợi: một mình bạn mở được cả hai đầu ống, và tránh được cảnh hai bên cùng đứng chờ nhau mãi. Nếu đầu kia vốn đã có người mở rồi thì cờ này chẳng thay đổi gì cả. Nhớ nhé: không chờ giúp một mình mở hai đầu và tránh kẹt cứng.
Using
O_NONBLOCKwhen opening a FIFO serves two main purposes: it lets a single process open both ends of a FIFO (open for reading withO_NONBLOCKfirst, then open for writing), and it prevents deadlock between processes that each open two FIFOs.1 If the other end is already open, the flag has no effect onopen()— it opens successfully at once as usual.1Bạn gặp nó ở đâuWhere you meet it Trong một server dùng FIFO: mở đầu đọc vớiO_NONBLOCKđể server khởi động được mà không phải chờ client đầu tiên xuất hiện.1 Trong server dùng FIFO: mở kiểu không chờ để khởi động được ngay, khỏi đợi client đầu tiên. In a FIFO-based server: open the read end withO_NONBLOCKso the server can start without waiting for the first client to appear.1Đổi O_NONBLOCK trên bộ mô tả đã mởToggling O_NONBLOCK on an open descriptor
Với bộ mô tả do
pipe()trả về (không quaopen(), nên không truyền cờ lúc tạo được), ta bật/tắt chế độ không chặn bằngfcntl(): lấy cờ hiện tại bằngF_GETFL, bật bitO_NONBLOCK, rồi đặt lại bằngF_SETFL.18For descriptors returned bypipe()(not viaopen(), so no flag can be passed at creation), toggle nonblocking mode withfcntl(): fetch the current flags withF_GETFL, set theO_NONBLOCKbit, then store them back withF_SETFL.18Bảng 44-1 trong sáchThe book's Table 44-1
Kerrisk gom bốn trường hợp mở FIFO vào một bảng: mở đọc/ghi × đầu kia mở/đóng. Chỉ một ô duy nhất là thất bại — mở ghi không chặn khi đầu kia đóng, trả
ENXIO; ba ô còn lại hoặc thành công ngay hoặc (ở chế độ chặn) đứng chờ.1Kerrisk collects the four FIFO-open cases into one table: open for reading/writing × other end open/closed. Only one cell fails — a nonblocking open for writing when the other end is closed, returningENXIO; the other three either succeed at once or (in blocking mode) wait.1 -
SIGPIPE
Ghi khi không còn ai đọc: SIGPIPE rồi EPIPE Ghi vào ống không còn ai nghe: bị báo "ống vỡ" Writing with no reader: SIGPIPE then EPIPE
Khi ghi vào một pipe hay FIFO mà không còn tiến trình nào mở đầu đọc, nhân gửi
SIGPIPEcho bên ghi; mặc định tín hiệu này giết tiến trình. Nếu bắt hoặc bỏ quaSIGPIPE, lờiwrite()thất bại với lỗiEPIPE.17 Kerrisk khuyên: cách dễ nhất thường là cho ứng dụng bỏ quaSIGPIPErồi phát hiện "ống vỡ" qua lỗiEPIPE.1Rót nước vào ống mà đầu kia chẳng còn ai hứng thì vô nghĩa — hệ thống "đập vỡ ống" và báo cho người rót biết. Mặc định cú báo ấy giết luôn chương trình; nhưng bạn có thể chọn "bỏ qua tiếng chuông" để tự xử lý lỗi cho êm. Nhớ nhé: ghi mà không ai nghe thì ống vỡ, chương trình có thể chết nếu không lo trước.
When writing to a pipe or FIFO with no process holding the read end open, the kernel sends
SIGPIPEto the writer; by default this signal kills the process. If you catch or ignoreSIGPIPE, thewrite()fails withEPIPE.17 Kerrisk advises: it is usually easiest to have the application ignoreSIGPIPEand detect a "broken" pipe via theEPIPEerror.1Bạn gặp nó ở đâuWhere you meet it Khiyes | headkết thúc êm:headđọc đủ rồi đóng,yesnhậnSIGPIPEvà dừng — đó là cơ chế này đang vận hành.7 Khi một lệnh trong pipeline tự dừng đúng lúc vì lệnh sau đã đọc đủ — đó là nhờ tín hiệu "ống vỡ". Whenyes | headends cleanly:headreads enough then closes,yesgetsSIGPIPEand stops — this mechanism at work.7 -
EAGAIN
Ngữ nghĩa write(): nguyên tử ≤ PIPE_BUF, nếu không là partial Luật ghi: ít thì trọn vẹn, nhiều thì ghi được chừng nào hay chừng ấy write() semantics: atomic if ≤ PIPE_BUF, else partial
Kerrisk gom ngữ nghĩa
write()vào Bảng 44-3. Ở chế độ chặn: ghi ≤PIPE_BUFbyte là nguyên tử (có thể chặn tới khi đủ chỗ); ghi >PIPE_BUFthì không bảo đảm nguyên tử và có thể bị xen kẽ. Ở chế độO_NONBLOCK: nếu ghi ≤PIPE_BUFmà không đủ chỗ,write()thất bại ngay vớiEAGAIN(vì không thể ghi một phần mà vẫn giữ tính nguyên tử); nếu ghi >PIPE_BUF, nó ghi được bao nhiêu hay bấy nhiêu (partial write) rồi trả về số byte đã chuyển, hoặcEAGAINnếu không chuyển nổi một byte.17Khi ghi: nếu lượng đủ nhỏ (≤ PIPE_BUF) thì hoặc ghi trọn một mạch, hoặc chờ tới khi có chỗ. Nếu lượng lớn thì ghi được tới đâu hay tới đó. Ở chế độ không chờ, không ghi được ngay thì nó báo "thử lại sau" (EAGAIN) chứ không đứng đợi. Nhớ nhé: ít thì trọn vẹn, nhiều thì ghi dần, không chờ thì báo thử lại sau.
Kerrisk collects
write()semantics into Table 44-3. In blocking mode: a write of ≤PIPE_BUFbytes is atomic (it may block until there is room); a write of >PIPE_BUFis not guaranteed atomic and may be interleaved. InO_NONBLOCKmode: if a ≤PIPE_BUFwrite lacks room,write()fails at once withEAGAIN(it cannot do a partial write while keeping atomicity); if >PIPE_BUF, it writes as much as it can (a partial write) and returns the byte count, orEAGAINif it cannot move even one byte.17Bạn gặp nó ở đâuWhere you meet it Khi code không chặn trảEAGAIN— đó không phải lỗi thật sự, chỉ là "chưa sẵn, hãy thử lại", thường được dùng cùng một vòng lặp sự kiện (ví dụselect()/poll()).1 Khi thấyEAGAINở chế độ không chờ — đừng hoảng, nó chỉ bảo "lát nữa thử lại nhé". When nonblocking code returnsEAGAIN— that is not a real error, just "not ready, try again," usually paired with an event loop (e.g.select()/poll()).1
Ba ngộ nhận thường gặp Three common misconceptions
“Mỗi write() tương ứng một read() ở đầu kia” “Each write() matches one read() at the other end”
Ngộ nhận: ghi ba lần thì đọc được đúng ba khối tương ứng.
Thực tế: pipe là byte stream, không giữ ranh giới thông điệp — ba lần ghi có thể gộp thành một lần đọc, hoặc tách thành nhiều lần. Muốn có "gói", ứng dụng phải tự đóng khung.1
Myth: three writes yield exactly three matching blocks when read.
Reality: a pipe is a byte stream with no message boundaries — three writes may merge into one read, or split into several. For "messages," the application must frame them itself.1
“Ghi bao nhiêu vào pipe cũng là một thao tác nguyên tử” “Any write to a pipe is atomic, whatever its size”
Ngộ nhận: mọi write() vào pipe đều không bị trộn với bên ghi khác.
Thực tế: chỉ ghi ≤ PIPE_BUF (trên Linux là 4096) mới được bảo đảm nguyên tử; ghi lớn hơn có thể bị chia nhỏ và xen kẽ khi có nhiều bên ghi.13
Myth: every write() to a pipe is immune to interleaving with other writers.
Reality: only writes of ≤ PIPE_BUF (4096 on Linux) are guaranteed atomic; larger writes may be split and interleaved when there are several writers.13
“Mở một FIFO giống hệt mở một tệp thường” “Opening a FIFO is just like opening a regular file”
Ngộ nhận: open() trên FIFO trả về ngay như với tệp thường.
Thực tế: mặc định mở để đọc chặn tới khi có bên mở để ghi (và ngược lại) — việc mở FIFO đồng bộ hai bên; muốn không chặn phải dùng O_NONBLOCK.14
Myth: open() on a FIFO returns at once like a regular file.
Reality: by default opening for reading blocks until a writer opens it (and vice versa) — opening a FIFO synchronizes both sides; to avoid blocking, use O_NONBLOCK.14
Tự kiểm Self-check
Năm câu hỏi để biết bạn đã nắm pipe và FIFO Năm câu hỏi kiểm tra nhanh Five questions to check you grasped pipes and FIFOs
Trả lời được những câu này nghĩa là bạn đã nắm bản chất của pipe, cách dựng và nối nó, FIFO có tên, và ngữ nghĩa đọc/ghi — nền cho các chương IPC về sau.
Nếu trả lời được, nghĩa là bạn đã hiểu chương này.
Answering these means you grasp what a pipe is, how to build and connect one, named FIFOs, and the read/write semantics — the groundwork for the later IPC chapters.
-
Nêu bốn đặc tính của một pipe: byte stream, một chiều, dung lượng hữu hạn, và bảo đảm nguyên tử. Trên Linux
PIPE_BUFbằng bao nhiêu?Gợi ý sâu hơn: điều gì thay đổi về tính nguyên tử của
write()giữa nhân Linux 2.2 và 2.4? -
pipe()trả về gì trongfiledes? Vì sao đóng bộ mô tả không dùng là thiết yếu chứ không chỉ để gọn?Gợi ý sâu hơn: vì sao nếu bên đọc không đóng đầu ghi thì nó không bao giờ thấy cuối tệp?
-
Làm sao nối đầu ra của một lệnh vào đầu vào của lệnh khác mà không sửa mã chúng? Vai trò của
dup2()là gì?Gợi ý sâu hơn:
popen()làm giúp bạn những bước nào, và vì sao kiểm tra đầu vào lại quan trọng với nó? -
FIFO khác pipe ở điểm nào? Mô tả ngữ nghĩa chặn khi mở một FIFO.
Gợi ý sâu hơn: trong ví dụ client–server, vì sao không thể dùng chung một FIFO cho mọi hồi đáp?
-
O_NONBLOCKđổi hành viopen()ra sao khi mở để đọc so với khi mở để ghi? Khi nào bên ghi nhậnSIGPIPE/EPIPE?Gợi ý sâu hơn: ở chế độ không chặn, vì sao một lời ghi ≤
PIPE_BUFlại thất bại vớiEAGAINthay vì ghi một phần?
Pipe là cái gì, và dữ liệu chảy theo mấy chiều?
Vì sao phải bịt đầu ống mình không dùng tới?
Dấu
|trong shell làm việc gì bên dưới?FIFO khác pipe ở chỗ nào?
Ghi vào một ống mà không còn ai đọc thì chuyện gì xảy ra?
What is a pipe, and in how many directions does data flow?
Why must you close the pipe end you do not use?
What does the shell's
|do under the hood?How does a FIFO differ from a pipe?
What happens when you write to a pipe with no reader left?
Khép lại chương Closing the chapter
Pipe và FIFO là kênh IPC đơn giản nhất của UNIX: một byte stream một chiều, dung lượng có hạn do nhân giữ, với ghi nguyên tử cho khối ≤
PIPE_BUF. Pipe nối người nhà, FIFO có tên nên nối cả người lạ; cả hai đòi kỷ luật đóng bộ mô tả để thấy cuối tệp và nhậnSIGPIPE/EPIPE. Nắm được cái ống này, bạn đã sẵn sàng cho những kênh IPC mạnh hơn — hàng thông điệp, bộ nhớ chia sẻ, và socket.1Pipe và FIFO là cái ống đơn giản nhất để các chương trình trao dữ liệu cho nhau: một chiều, chở byte thành dòng. Pipe cho người cùng nhà, FIFO có tên cho tất cả. Hiểu cái ống này là bước đệm sang các kênh liên lạc lớn hơn. Nhớ nhé: nắm được cái ống nhỏ này là mở đường tới mọi kênh liên lạc lớn hơn.
Pipes and FIFOs are UNIX's simplest IPC channel: a unidirectional, limited-capacity byte stream held by the kernel, with atomic writes for blocks of ≤
PIPE_BUF. Pipes join relatives; a named FIFO joins strangers too; both demand the descriptor-closing discipline to see end-of-file and receiveSIGPIPE/EPIPE. Grasp this little pipe and you are ready for the stronger IPC channels — message queues, shared memory, and sockets.1