- pipeline ที่เชื่อมเอาต์พุตที่ไหลเข้ามาช้า ๆ ผ่านหลายคำสั่ง เช่น
tail -f /some/log/file | grep thing1 | grep thing2อาจดูเหมือนว่างเปล่า ไม่ใช่เพราะค้างจริง แต่เพราะคำสั่งตรงกลาง เก็บเอาต์พุตไว้ในบัฟเฟอร์ grepและหลายโปรแกรมตรวจสอบด้วยisattyว่า stdout เป็นเทอร์มินัลหรือไม่ ถ้าเป็นเทอร์มินัลจะใช้ line buffering แต่ถ้าเป็น pipe หรือไฟล์จะใช้ block buffering ประมาณหน่วยละ 8KBtail,cat,teeเป็นตัวอย่างที่ไม่ทำ output buffering แต่ตัวเลือกเพื่อลด buffering แตกต่างกันไปตามคำสั่ง เช่นgrep --line-buffered,sed -u,tcpdump -l,jq -u,tr -u- หากตัด pipeline ด้วย
Ctrl-Cเอาต์พุตที่ค้างอยู่ในบัฟเฟอร์ของโปรแกรมอย่างtcpdumpอาจหายไปได้ แต่ถ้าจบโปรแกรมด้วยkill -TERM $PIDบัฟเฟอร์อาจถูก flush จนเห็นเอาต์พุต - วิธีแก้ในทางปฏิบัติคือเปลี่ยนไปใช้คำสั่งที่จบเร็ว,
grep --line-buffered,awkตัวเดียวหรือgrepที่ซับซ้อนขึ้น,stdbuf,unbufferเป็นต้น แต่แต่ละวิธีต้องตรวจสอบ เงื่อนไขการทำงานและผลข้างเคียง
ทำไม pipe จึงดูเหมือนค้าง
- เมื่อมีบรรทัดค่อย ๆ ถูกเพิ่มเข้าไปในไฟล์ log pipeline ต่อไปนี้อาจไม่แสดงเอาต์พุต แม้จะมีผลลัพธ์ที่ match ก็ตาม
tail -f /some/log/file | grep thing1 | grep thing2
- สาเหตุไม่ใช่ตัว pipe เอง แต่เป็นเพราะ
grep thing1ที่อยู่ตรงกลางไม่ได้เขียนผลลัพธ์ทันที แต่ เก็บไว้ในบัฟเฟอร์ - ถ้าโปรแกรมเขียนออกทุกครั้งทันที จำนวน system call จะเพิ่มขึ้น จึงมักรวบรวมข้อมูลระดับหนึ่งเพื่อประสิทธิภาพก่อนเขียนไปยัง pipe หรือไฟล์
- ในตัวอย่างนี้
grep thing1อาจรอจนมีเอาต์พุตสะสมประมาณ 8KB และใน log ที่ไหลช้า เงื่อนไขนั้นอาจแทบไม่มีวันเกิดขึ้น
วิธีเอาต์พุตที่ต่างกันระหว่างเทอร์มินัลกับ pipe
tail -f file | grep thingทำงานได้ดี แต่ถ้าต่อgrepตัวที่สองด้านหลัง อาจดูเหมือนเอาต์พุตค้างgrepและหลายโปรแกรมใช้ฟังก์ชันisattyเพื่อตรวจว่า stdout เป็นเทอร์มินัลหรือไม่- ถ้า stdout เป็น เทอร์มินัล จะใช้ line buffering และแสดงผลทันทีเป็นรายบรรทัด
- ถ้า stdout เป็น pipe หรือไฟล์ จะใช้ block buffering และจะแสดงผลเมื่อข้อมูลสะสมถึงขนาดหนึ่ง
- ดังนั้นถ้า
grepเขียนตรงไปที่เทอร์มินัล จะเห็นบรรทัดทันที แต่ถ้าเขียนไปยัง pipe ที่ส่งต่อให้คำสั่งถัดไป อาจไม่เห็น - ขนาดบัฟเฟอร์แตกต่างกันไปตามโปรแกรม
grepให้ libc จัดการ buffering และขนาดของ libc ถูกกำหนดด้วยตัวแปรBUFSIZ- ตำแหน่งนิยามของ glibc อยู่ใน stdio.h
- การไม่ใช้บัฟเฟอร์เอาต์พุต 8KB เมื่อเขียนไปยังเทอร์มินัลไม่ใช่กฎฟิสิกส์ หากโปรแกรมต้องการก็ทำเช่นนั้นได้ แต่ถือว่าเป็นพฤติกรรมที่แปลกมาก
พฤติกรรม buffering ที่ต่างกันในแต่ละคำสั่ง
- เหตุผลที่ output buffering รับมือยากคือผู้ใช้ต้องจำว่าคำสั่งใดทำ buffering เมื่อส่งเอาต์พุตไปยัง pipe
- ตัวอย่างคำสั่งที่ไม่ทำ output buffering มีดังนี้
tailcattee
- คำสั่งที่พบบ่อยซึ่งทำ output buffering เมื่อเขียนไปยัง pipe และวิธีลดผลกระทบ มีดังนี้
grep:--line-bufferedsed:-uawk: ฟังก์ชันfflush()tcpdump:-ljq:-utr:-ucut: ปิด buffering ไม่ได้
- สำหรับคำสั่งอย่าง
sortที่ต้องรับอินพุตทั้งหมดก่อนจึงทำงานได้ ประเด็นเรื่อง buffering แทบไม่มีความสำคัญในทางปฏิบัติ - พยายามทดสอบทั้ง Mac OS และเวอร์ชัน GNU แล้ว แต่มี variant จำนวนมาก จึงอาจมีข้อผิดพลาดบางส่วนได้
เอาต์พุตเริ่มต้นของภาษาโปรแกรมก็ทำ buffering
- เอาต์พุต
printเริ่มต้นของภาษาโปรแกรมบางภาษา ก็ทำ buffering เมื่อเขียนไปยัง pipe - วิธีปิดตามแต่ละภาษา มีดังนี้
- C:
setvbuf - Python:
python -u,PYTHONUNBUFFERED=1,sys.stdout.reconfigure(line_buffering=False),print(x, flush=True) - Ruby:
STDOUT.sync = true - Perl:
$| = 1
- C:
- พฤติกรรมเริ่มต้นแบบนี้ดูเหมือนเป็นการออกแบบเพื่อให้ฟังก์ชันเอาต์พุตพื้นฐานเร็วขึ้นในการประมวลผลแบบ batch
- buffering อาจต่างกันตามวิธีเขียนเอาต์พุต
- ใน C++
cout << "hello\n"จะทำ buffering เมื่อเขียนไปยัง pipe cout << "hello" << endlจะ flush เอาต์พุต
- ใน C++
ความแตกต่างที่เกิดกับ Ctrl-C และ file redirection
- หากเชื่อมเอาต์พุตของ
tcpdumpไปยังgrepแบบต่อไปนี้และลืม-lเอาต์พุตอาจค้างอยู่ในบัฟเฟอร์sudo tcpdump -ni any port 53 | grep example.com
- ตามอุดมคติ เมื่อกด
Ctrl-Cอาจคาดหวังว่าtcpdumpจะ flush บัฟเฟอร์ แล้วgrepจะค้นหาและแสดงเอาต์พุตที่ตกหล่น - แต่ในความเป็นจริง เมื่อโปรแกรมต่าง ๆ จบการทำงาน เอาต์พุตที่อยู่ในบัฟเฟอร์ของ
tcpdumpจะ สูญหาย - เมื่อตรวจด้วย
straceพบว่าgrepได้รับSIGINTก่อนtcpdumpดังนั้นแม้tcpdumpจะพยายาม flush แต่grepอาจตายไปแล้ว - วิธีเลี่ยงคือหา PID ของ
tcpdumpแล้วรันkill -TERM $PIDซึ่งอาจทำให้tcpdumpflush บัฟเฟอร์จนเห็นเอาต์พุตได้ - file redirection ก็ทำ buffering เช่นกัน
sudo tcpdump -ni any port 53 > output.txt
- อย่างไรก็ตาม file redirection ต่างจากปัญหาที่
Ctrl-Cทำให้เนื้อหาในบัฟเฟอร์หายไปทั้งหมด โดยจากประสบการณ์มักพบว่าเนื้อหาในบัฟเฟอร์ถูกเขียนลงไฟล์ก่อนโปรแกรมจบ - ยังไม่แน่ชัดว่าสามารถเชื่อถือพฤติกรรมนี้ได้เสมอหรือไม่
ห้าวิธีในการหลีกเลี่ยง buffering
-
เปลี่ยนไปใช้โปรแกรมที่จบเร็ว
- หลีกเลี่ยงสถานการณ์ที่เขียนไปยัง pipe อย่างช้า ๆ แล้วเปลี่ยนเป็นคำสั่งที่จบเร็วได้
- ตัวอย่างมีดังนี้
cat /some/log/file | grep thing1 | grep thing2 | tail
- แม้พฤติกรรมจะไม่เหมือนคำสั่ง
tail -fเดิม แต่ช่วยเลี่ยงปัญหา buffering ที่ซับซ้อนได้
-
ใช้ตัวเลือก line buffer ของ
grepgrepมี flag สำหรับหลีกเลี่ยง buffering- ตัวอย่างมีดังนี้
tail -f /some/log/file | grep --line-buffered thing1 | grep thing2
-
รวมด้วย
awkหรือgrepที่ซับซ้อนขึ้น- กรณีที่ใช้
grepหลายตัว สามารถเปลี่ยนเป็นawkตัวเดียวได้tail -f /some/log/file | awk '/thing1/ && /thing2/'
- หรือใช้
grepที่มี regular expression ซับซ้อนขึ้นได้tail -f /some/log/file | grep -E 'thing1.*thing2'
awkก็ทำ buffering เช่นกัน ดังนั้นวิธีนี้จะทำงานได้เมื่อawkเป็น คำสั่งสุดท้าย ของ pipeline
- กรณีที่ใช้
-
ใช้
stdbufstdbufใช้LD_PRELOADเพื่อปิด buffering ของ libc- ตัวอย่างการปิด output buffering มีดังนี้
tail -f /some/log/file | stdbuf -o0 grep thing1 | grep thing2
- วิธีที่อาศัย
LD_PRELOADมีความน่าเชื่อถือจำกัด- ไม่ทำงานกับ binary แบบ static
- อาจไม่ทำงานหากโปรแกรมไม่ได้ใช้ buffering ของ libc
- ไม่ได้ทำงานเสมอไปบน Mac OS
- มีคำอธิบายที่เกี่ยวข้องคือ How stdbuf works ของ Harry Marr
-
ใช้
unbufferunbuffer programบังคับให้เอาต์พุตของโปรแกรมเหมือนเป็น TTY ทำให้ buffering น้อยลงเหมือนใน TTY ปกติ และใช้สิ่งอย่าง output สี- ตัวอย่างมีดังนี้
tail -f /some/log/file | unbuffer grep thing1 | grep thing2
- ต่างจาก
stdbufตรงที่มักทำงานได้เสมอ แต่อาจมีผลข้างเคียงที่ไม่ต้องการ- เช่น
grep thing1อาจใส่สีให้ผลลัพธ์ที่ match
- เช่น
unbufferอยู่ในแพ็กเกจexpect
สถานการณ์ที่มักเห็นปัญหาและไอเดียเรื่อง environment variable
- ปัญหาแบบนี้มักเกิดกับโปรแกรมที่ค่อย ๆ ปล่อยข้อมูลไปยัง pipe
- ตัวอย่างมีดังนี้
tcpdumptail -f- การเฝ้าดู log แบบ
kubectl logs - เอาต์พุตจากการคำนวณที่ช้า
- คงจะดีหากมี environment variable มาตรฐานสำหรับปิด buffering เหมือน
PYTHONUNBUFFEREDของ Python - ไอเดียนี้มาจาก บทความบล็อก ปี 2018 ของ Mark Dominus และ บทความต่อเนื่อง
- ตัวอย่างชื่ออาจเป็น
NO_BUFFERในลักษณะเดียวกับNO_COLOR - การออกแบบทำได้ยาก
- NETBSD มี environment variable เช่น
STDBUF,STDBUF1และอื่น ๆ ที่ให้การควบคุมจำนวนมาก - นักพัฒนาส่วนใหญ่อาจไม่อยาก implement environment variable หลายตัวเพื่อ edge case ที่ค่อนข้างเล็ก
- NETBSD มี environment variable เช่น
- ยังสงสัยด้วยว่ามีโปรแกรมที่ flush บัฟเฟอร์เอาต์พุตอัตโนมัติตามรอบเวลา เช่น ทุก 1 วินาทีหรือไม่ แต่นึกโปรแกรมแบบนั้นไม่ออก และอาจมีข้อเสีย
ขอบเขตที่ไม่ได้กล่าวถึง
- ไม่ครอบคลุมความแตกต่างระหว่าง line buffering กับเอาต์พุตแบบไม่ใช้บัฟเฟอร์เลย
- ไม่ครอบคลุมความแตกต่างระหว่าง buffering ของ stderr กับ stdout
- เนื้อหานี้กล่าวถึงเฉพาะ buffering ที่เกิดภายในโปรแกรม
- TTY driver ของระบบปฏิบัติการก็ทำ buffering เล็กน้อยเป็นครั้งคราวเช่นกัน
- ไม่ครอบคลุมเหตุผลอื่น ๆ ที่ต้อง flush เอาต์พุตนอกเหนือจากสถานการณ์ที่เขียนไปยัง pipe
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
วิธีเข้าถึงแบบมีบัฟเฟอร์แทบจะควรเป็นแบบ “ถึงค่าเกณฑ์หรือหมดเวลา” เสมอ กล่าวคือฟลัชเมื่อถึง ค่าเกณฑ์ของจำนวนไบต์ หรือเมื่อเวลาผ่านไประยะหนึ่งหากมีข้อมูลอยู่อย่างน้อย 1 ไบต์
เป็นวิธีที่พบได้ทั่วไปในอินเทอร์เฟซฮาร์ดแวร์เพื่อแก้ปัญหาคล้าย ๆ กัน
ในกรณีนี้ ไลบรารีที่ทำบัฟเฟอร์ในพื้นที่ผู้ใช้ควรตั้งตัวจับเวลาที่เหมาะสมเมื่อใส่ข้อมูลลงบัฟเฟอร์ครั้งแรก ค่าหมดเวลาอาจรับเป็นอาร์กิวเมนต์ ตั้งไว้สั้นพอที่มนุษย์จะรู้สึกว่าสั้น เช่น 1–100ms ตั้งให้แปรผันตาม
{แบนด์วิดท์ / ค่าเกณฑ์}หรือกำหนดไม่ให้โอเวอร์เฮดของ system call เกิน 0.1% ของเวลารวมก็ได้วิธีนี้ใช้ได้กับการอ่านเช่นเดียวกับการเขียน หากทำการอ่านแบบรวมเป็นชุดหรืออ่านแบบรวมหลายรายการ ก็ต้องใช้แนวทางคล้ายกัน แต่ช่องทางข้อมูลต้องมีวิธีสอบถามหรือรับแจ้ง “ข้อมูลที่รออยู่” ได้อย่างมีประสิทธิภาพ จึงขึ้นกับการออกแบบช่องทางมากกว่า ในฮาร์ดแวร์ วิธีอย่างการรวมอินเทอร์รัปต์เป็นเรื่องพบได้ทั่วไป
ข้อผิดพลาด I/O อาจเกิดขึ้นได้ทุกเมื่อ ไม่ใช่แค่ตอนเขียน และ system call หลายตัวอาจถูกขัดจังหวะโดยตัวจับเวลาได้ ไม่ใช่แค่จุดที่โปรแกรมตั้งตัวจับเวลาเองหรือจุดที่สัญญาณมาถึง
หากทั้งแอปพลิเคชันและ libc ตั้งตัวจับเวลา ก็มีโอกาสทำให้สับสนได้เช่นกัน แม้ API ตัวจับเวลาของเคอร์เนลยุคนี้ดูดีกว่าที่จำได้ในอดีต จึงอาจเกี่ยวข้องน้อยลง แต่ถ้าแอปพลิเคชันบล็อกสัญญาณชั่วคราวในช่วงสำคัญ ก็จะกระทบตัวจับเวลา I/O ด้วย
เนื่องจากจังหวะและวิธีจัดการสัญญาณ จึงต้องระวัง การเข้าถึงโครงสร้างข้อมูล I/O มากขึ้น
ใช้ io_uring กับตัวจับเวลาในพื้นที่ผู้ใช้จะขยายสเกลได้ดีกว่ามาก แต่ถ้าต้องรองรับการเขียนขนาดเล็กที่รวดเร็วจำนวนมาก ก็ยังต้องใช้ลูกเล่นอยู่ดี ตัวอย่างเช่น เมื่อเกินราว 1 ล้านครั้งต่อวินาที ต้นทุนการจัดการตัวจับเวลาจะเริ่มเห็นชัดขึ้น และถ้าจะไปถึงการเขียน 100 ล้านครั้งต่อวินาที ก็ต้องใช้เทคนิคค่อนข้างพิสดาร
สาเหตุของปัญหาคือมีการปะปนกันระหว่างสิ่งที่ควรเป็นแบบโต้ตอบกับสัญญาที่ไม่ได้ตั้งอยู่บนสมมติฐานเรื่องการโต้ตอบ เช่น กรณีส่งเอาต์พุตติดตามของ
tailเข้าไปใน pipeผมไม่คิดว่ามีปัญหาจริงที่ต้องแก้ เปรียบกับฮาร์ดแวร์ก็เหมือนแท็งก์เก็บน้ำฝน ที่จะย้ายต่อเมื่อเต็มแล้วเท่านั้น ไม่รู้ว่าคุณนึกถึงตัวอย่างไหน แต่เท่าที่ผมรู้ การฟลัชตามเวลา ไม่ได้พบบ่อยในฮาร์ดแวร์
การแก้ไขที่เสนอทำให้สัญญาซับซ้อนขึ้นมาก
ปัญหาไม่ได้อยู่ที่ semantics เองเท่าไร แต่อยู่ที่ ไม่รู้ semantics ต่างหาก
ใช้ระบบ NIX มามากกว่า 20 ปี และรู้ว่าเรื่องแบบนี้เกิดขึ้นได้ แต่ทุกครั้งก็ยังต้องงงอยู่นานว่าทำไมเอาต์พุตไม่ออก ก่อนจะนึกขึ้นได้
สำหรับส่วนที่ว่า “ช่วงหลังบทความยาวขึ้นพอสมควร แล้วจะมีใครอยากอ่านบทความ 3000 คำเรื่องบัฟเฟอร์จริง ๆ ไหม” ส่วนตัวแล้วผมอยากอ่าน
ผมคิดว่าบางครั้งบทความยืดยาวก็เป็น น้ำท่วมทุ่ง เพื่อทำ SEO
หรืออาจมีส่วน TLDR กับ NTLDR คือส่วน “ยาวแต่ก็อ่านแล้ว” ก็ได้
อยากให้บัฟเฟอร์ทั้งหมดถูกฟลัชทุกครั้งที่ CPU ทั้งระบบเข้าสู่สถานะว่าง
โดยทั่วไป บัฟเฟอร์เป็นเทคนิคเพื่อประหยัด CPU หาก CPU มีไม่จำกัด บัฟเฟอร์ทั้งหมดก็คงมีขนาด 1 ไบต์ บัฟเฟอร์คือวิธีรวบรวมข้อมูลเพื่อประมวลผลเป็นชุดเพื่อประสิทธิภาพ
แต่เมื่อ CPU ว่าง ไม่ควรมี “งานที่ไว้ทำทีหลัง” ค้างอยู่ ทันทีที่ scheduler ของเคอร์เนลเข้าสู่สถานะว่าง ควรส่งสัญญาณไปยังทุกโปรเซสว่า ให้ฟลัชบัฟเฟอร์
เท่ากับทำงานทั้งหมดนั้นเสร็จแล้ว ยังบังคับให้ต้องทำ system call เพื่อฟลัชบัฟเฟอร์อีก อาจเพิ่มกลไกให้เคอร์เนลรู้จักบัฟเฟอร์ในพื้นที่ผู้ใช้ แล้วดึงจากตรงนั้นโดยตรงเมื่อว่างก็ได้
ผมสงสัยว่านี่คล้ายกับ io_uring https://man7.org/linux/man-pages/man3/io_uring_register_buff... ในระดับหนึ่งหรือเปล่า
บทความนี้สับสนระหว่างสองสิ่งที่ต่างกันคือ ไม่มีบัฟเฟอร์ กับ บัฟเฟอร์ตามบรรทัด
การไม่มีบัฟเฟอร์ทำให้ประสิทธิภาพแย่ลงโดยไม่จำเป็น และอาจทำให้เอาต์พุตผิดพลาดเมื่อมีหลายแหล่งเขียนไปยัง pipe เดียวกัน บรรทัดที่ยาวพออย่างไรก็จะปนกันอยู่ดี แต่บรรทัดเอาต์พุตส่วนใหญ่ในโลกจริงสั้นกว่า 4096 ไบต์ แม้รวมอักขระจัดรูปแบบ/ควบคุมและอักขระในระนาบเสริมแล้วก็ตาม
บัฟเฟอร์ตามบรรทัดเป็นค่าเริ่มต้นของเทอร์มินัล และมักเป็นพฤติกรรมที่ต้องการใน pipe ด้วย ให้รันแต่ละคำสั่งภายใต้
stdbuf -oL -eLก็พอ โปรแกรมหายากที่ต้องการอัปเดตภายในบรรทัดก็ต้องฟลัชเองอยู่แล้ว ดังนั้นตรงนี้ก็จะทำงานได้ถูกต้องสิ่งที่
stdbufทำจริง ๆ ดูได้ประมาณนี้:env -i \command -v stdbuf` -oL -eL `command -v env``เคยเขียนเกี่ยวกับปัญหานี้ไว้ก่อนหน้านี้: https://world-playground-deceit.net/blog/2024/09/bourne_shel...
สำหรับคำสั่งที่ไม่ทำ buffering นั้นอาจขึ้นกับ implementation หรือในกรณีของ
catอาจจะผิดก็ได้ ดู https://pubs.opengroup.org/onlinepubs/9799919799/utilities/c... และ-uได้ การที่ POSIX ไม่ใส่วิธีทางการในการจัดการเรื่องนี้ไว้เป็นความเจ็บปวดครั้งใหญ่อีกเรื่องที่ไม่ได้กล่าวถึงคือ input buffering ซึ่งให้ผลลัพธ์แปลก ๆ แบบนี้:
$ seq 5 | { v1=$(head -1); v2=$(head -1); printf '%s=%s\n' v1 "$v1" v2 "$v2"; }v1=1v2=ในกรณีนี้วิธีแก้คือใช้
stdbuf -i0 head -1socketpairจะสามารถบังคับข้อจำกัดแบบนั้นกับโปรเซสที่เขียนได้ ยกเว้นกรณีใช้แฮ็กหนัก ๆ อย่างptrace()อาจจะปรับขนาด pipe buffer ได้ แต่ไม่รู้ว่ามีธรรมเนียมใดที่บอกว่า standard C I/O ต้องทำตามสิ่งนี้
อย่างไรก็ตาม ในกรณีนี้ดูเหมือน
stdbufจะไม่ช่วย:$ ./a | stdbuf -i0 -- cat#include#includeint main(void) {for (;;) {printf("n");usleep(100000);}}มีเหตุผลที่ดีที่ buffer มีอยู่ การพิมพ์ output ลงหน้าจอช้ามากเมื่อเทียบกับการเขียนลง buffer
การ output ทีละตัวอักษรนั้นไม่มีประสิทธิภาพอย่างมาก
เป็นปัญหาเก่าแก่ และมักเจอเมื่อจัดการกับ UART วิธีแก้ที่เป็นไปได้มีหลายแบบ: แบบอิงบรรทัดที่ทำเครื่องหมายจบ output ด้วยอักขระพิเศษอย่าง newline, แบบอิงความยาวที่รอจนกว่าจะถึงความยาวอย่าง 8KB, และแบบอิงเวลาที่ output ทุก ๆ X มิลลิวินาที
แต่ละวิธีมีข้อดีข้อเสีย และอะไรเหมาะที่สุดก็ขึ้นกับแอปพลิเคชัน ผมคิดว่าส่วนในบทความที่บอกว่าโปรแกรมบางตัวไม่ใช้ buffering นั้นผิด โปรแกรมเหล่านั้นเพียงแค่ไม่ได้ใช้ แบบอิงความยาว ที่เห็นได้ชัดเท่านั้น
แนวทางแบบอิงบรรทัดเป็นแบบนั้น แต่ต้องมีข้อตกลงว่าจะใช้อักขระใด ปกติก็คือ newline
/dev/nullก็ทำให้ประสิทธิภาพตกลงอย่างมากได้ใช้ Unix มานานกว่า 35 ปีแล้ว แต่ไม่เคยเข้าใจอย่างถ่องแท้ว่าสิ่งนี้ทำงานอย่างไร
ดีที่ได้คำอธิบายโดยรวมเกี่ยวกับพฤติกรรม buffering ที่ครอบคลุมหลายระบบและหลายองค์ประกอบ และได้เรียนรู้อะไรแน่นอน
สำหรับส่วนที่ว่า “เมื่อกด Ctrl-C ใน pipe เนื้อหาใน buffer จะหายไป” ผมคิดว่าโปรแกรมส่วนใหญ่น่าจะ flush buffer เมื่อเจอ SIGINT
แต่ถ้าจะให้ shell ทำงานแบบนั้น คงต้องส่ง SIGINT ไปเฉพาะโปรแกรมแรกของ pipeline เท่านั้น ซึ่งดูเหมือนพฤติกรรมจริงอาจจะไม่ใช่อย่างนั้น
sigintและที่เหลือจะได้รับsigpipe