ทำไม / dev / null ไฟล์? ทำไมฟังก์ชั่นการใช้งานไม่ได้เป็นโปรแกรมอย่างง่าย?


114

ฉันพยายามเข้าใจแนวคิดของไฟล์พิเศษบน Linux อย่างไรก็ตามมีไฟล์พิเศษใน/devดูเหมือนโง่ธรรมดาเมื่อฟังก์ชั่นสามารถนำมาใช้โดยไม่กี่บรรทัดใน C เพื่อความรู้ของฉัน

ยิ่งไปกว่านั้นคุณสามารถใช้มันในลักษณะเดียวกันได้อย่างมากเช่นการไพพ์ไปที่nullแทนที่จะเปลี่ยนไป/dev/nullใช้ มีเหตุผลที่เฉพาะเจาะจงในการใช้เป็นไฟล์หรือไม่? การไม่ทำให้เป็นไฟล์ทำให้เกิดปัญหาอื่น ๆ อีกมากมายเช่นมีหลายโปรแกรมที่เข้าถึงไฟล์เดียวกันหรือไม่


20
อนึ่งมากของค่าใช้จ่ายนี้เป็นเหตุผลที่cat foo | barเป็นเลวร้ายมาก bar <foo(ในระดับกว่า) catเป็นโปรแกรมเล็ก ๆ น้อย ๆ แต่แม้กระทั่งโปรแกรมเล็ก ๆ น้อย ๆ ก็สร้างค่าใช้จ่ายได้ (บางส่วนของโปรแกรมเฉพาะสำหรับซีแมนทิกส์ FIFO - เนื่องจากโปรแกรมไม่สามารถseek()อยู่ใน FIFO ได้ตัวอย่างเช่นโปรแกรมที่สามารถใช้งานได้อย่างมีประสิทธิภาพ เมื่อได้รับไพพ์ไลน์ด้วยอุปกรณ์ตัวละครอย่างเช่น/dev/nullมันสามารถปลอมการดำเนินการเหล่านั้นหรือด้วยไฟล์จริงมันสามารถนำไปใช้งานได้ แต่ FIFO ไม่อนุญาตให้มีการจัดการที่คำนึงถึงบริบทใด ๆ )
Charles Duffy

13
grep blablubb file.txt 2>/dev/null && dosomethingไม่สามารถทำงานได้โดยไม่มีค่า null เป็นโปรแกรมหรือฟังก์ชั่น
rexkogitans

16
คุณอาจพบว่าการให้ความสว่าง (หรืออย่างน้อยก็เพิ่มความคิด) เพื่ออ่านเกี่ยวกับระบบปฏิบัติการ Plan 9 เพื่อดูว่าวิสัยทัศน์ "ทุกอย่างเป็นไฟล์" กำลังดำเนินไปที่ไหน - มันจะกลายเป็นเรื่องง่ายกว่าที่จะเห็นพลังของการมีทรัพยากรพร้อมใช้งานเป็นไฟล์ พา ธ เมื่อคุณเห็นระบบครอบคลุมแนวคิดอย่างสมบูรณ์ (แทนที่จะเป็นส่วนใหญ่ / บางส่วนเช่น modern Linux / Unix do)
mtraceur

25
เช่นเดียวกับที่ไม่มีใครชี้ให้เห็นว่าไดรเวอร์อุปกรณ์ที่ทำงานในพื้นที่เคอร์เนลเป็น โปรแกรมที่มี "หยิบของบรรทัด C"ไม่มีคำตอบใด ๆ ที่ได้ระบุจ่าหน้าถึง "โปรแกรมมากเกินไปที่เข้าถึงไฟล์เดียวกัน" ในคำถาม
JdeBP

12
เรื่อง "หน้าที่ของมันอาจจะมีการดำเนินการโดยกำมือของสายใน C": คุณจะไม่เชื่อ แต่ก็จะดำเนินการโดยกำมือของสายใน C! ตัวอย่างเช่นเนื้อหาของreadฟังก์ชันสำหรับ/dev/nullประกอบด้วย "return 0" (หมายถึงมันไม่ได้ทำอะไรเลยและฉันคิดว่าเป็นผลลัพธ์ใน EOF): (จาก static github.com/torvalds/linux/blob/master/ drivers / char / mem.c ) ssize_t read_null(struct file *file, char __user *buf, size_t count, loff_t *ppos) { return 0; }(โอ้ฉันเพิ่งเห็นว่า @JdeBP ได้สร้างจุดนั้นแล้วอย่างไรก็ตามนี่คือภาพประกอบ :-)
Peter A. Schneider

คำตอบ:


153

นอกจากผลประโยชน์ประสิทธิภาพของการใช้อุปกรณ์ตัวอักษรพิเศษประโยชน์หลักคือต้นแบบ / dev / null อาจใช้ในเกือบทุกบริบทที่คาดว่าไฟล์ไม่เพียง แต่ในเปลือกท่อ พิจารณาโปรแกรมที่ยอมรับไฟล์เป็นพารามิเตอร์บรรทัดคำสั่ง

# We don't care about log output.
$ frobify --log-file=/dev/null

# We are not interested in the compiled binary, just seeing if there are errors.
$ gcc foo.c -o /dev/null  || echo "foo.c does not compile!".

# Easy way to force an empty list of exceptions.
$ start_firewall --exception_list=/dev/null

ทั้งหมดนี้เป็นกรณีที่การใช้โปรแกรมเป็นแหล่งหรืออ่างล้างจานจะยุ่งยากอย่างมาก แม้ในกรณีไปป์ไลน์เชลล์ stdout และ stderr อาจถูกเปลี่ยนเส้นทางไปยังไฟล์อย่างอิสระสิ่งที่ยากต่อการทำงานกับ executables เป็น sinks:

# Suppress errors, but print output.
$ grep foo * 2>/dev/null

14
นอกจากนี้คุณไม่ได้ใช้/dev/nullเพียงแค่คำสั่งของเชลล์ คุณสามารถใช้มันในพารามิเตอร์อื่น ๆ ที่ให้มากับซอฟต์แวร์ตัวอย่างเช่นในไฟล์กำหนดค่า --- ฟอร์แม็ตซอฟต์แวร์นี้สะดวกมาก ไม่จำเป็นต้องสร้างความแตกต่างระหว่าง/dev/nullและไฟล์ปกติ
pabouk

ฉันไม่แน่ใจว่าฉันเข้าใจส่วนเกี่ยวกับการเปลี่ยนเส้นทางแยกกันเพื่อให้ไฟล์โปรแกรมทำงานได้ยาก ใน C คุณเพียงแค่ทำpipe, forkและexecveเช่นท่อในการผลิตอื่น ๆ ใด ๆ เพียงแค่มีการเปลี่ยนแปลงไปยังdup2สายที่ตั้งค่าการเชื่อมต่อใช่มั้ย? มันเป็นความจริงหอยส่วนใหญ่ไม่ได้นำเสนอวิธีที่สวยที่สุดที่จะทำ แต่สันนิษฐานว่าถ้าเราไม่ได้มีมากดังนั้นอุปกรณ์ที่เป็นไฟล์รูปแบบและส่วนใหญ่ของสิ่งที่อยู่ใน/devและ/procได้รับการรักษาเป็น executables หอยจะได้รับการออกแบบด้วยวิธีการ ที่จะทำมันอย่างง่ายดายเหมือนที่เราเปลี่ยนเส้นทางตอนนี้
aschepler

6
@aschepler การที่ไม่เปลี่ยนเส้นทางไปยังโปรแกรมที่ทำงานได้นั้นเป็นเรื่องยาก นั่นคือการเขียนแอปพลิเคชันที่สามารถเขียน / อ่านจากทั้งไฟล์และที่เก็บ null จะซับซ้อนกว่านี้ถ้าที่เก็บ Null ไม่ใช่ไฟล์ ยกเว้นว่าคุณกำลังพูดถึงโลกที่แทนที่จะเป็นไฟล์ทุกอย่างจะสามารถทำงานได้ทุกอย่างหรือไม่ นั่นจะเป็นรูปแบบที่แตกต่างจากที่คุณมีใน * nix OS
คิว

1
@aschepler คุณลืมwait4! คุณถูกต้องเป็นไปได้แน่นอนที่จะไพพ์ stdout และ stderr ไปยังโปรแกรมต่าง ๆ โดยใช้ POSIX apis และอาจเป็นไปได้ที่จะสร้างเชลล์เชลล์ที่ฉลาดขึ้นสำหรับการเปลี่ยนเส้นทาง stdout และ stderr ไปยังคำสั่งต่าง ๆ อย่างไรก็ตามตอนนี้ฉันไม่ทราบว่าเชลล์ใดและจุดที่ใหญ่กว่าคือ / dev / null เหมาะสมกับการใช้เครื่องมือที่มีอยู่อย่างเรียบร้อย (ซึ่งส่วนใหญ่ทำงานกับไฟล์) และ / bin / null จะไม่ เราสามารถจินตนาการถึง IO API บางอย่างที่ทำให้การส่งออก gcc ไปยัง (อย่างปลอดภัย!) ไปยังโปรแกรมเป็นไฟล์ได้อย่างง่ายดาย แต่นั่นไม่ใช่สถานการณ์ที่เราอยู่
ioctl

2
@ioctl เกี่ยวกับหอย ทั้ง zsh และ bash อย่างน้อยจะช่วยให้คุณทำสิ่งต่าง ๆ เช่นgrep localhost /dev/ /etc/hosts 2> >(sed 's/^/STDERR:/' > errfile ) > >(sed 's/^/STDOUT:/' > outfile)ผลลัพธ์จะถูกประมวลผลแยกต่างหากerrfileและoutfile
Matija Nalis

62

ในความเป็นธรรมก็ไม่ได้เป็นแฟ้มปกติต่อ se; มันเป็นอุปกรณ์พิเศษของตัวละคร :

$ file /dev/null
/dev/null: character special (3/2)

มันทำหน้าที่เป็นอุปกรณ์แทนที่จะเป็นไฟล์หรือโปรแกรมหมายความว่าเป็นการดำเนินการที่ง่ายกว่าในการเปลี่ยนทิศทางอินพุตหรือเอาต์พุตจากมันเนื่องจากสามารถแนบกับตัวบ่งชี้ไฟล์ใด ๆ รวมถึงอินพุต / เอาต์พุต / ข้อผิดพลาดมาตรฐาน


24
cat file | nullจะมีค่าใช้จ่ายมากมายก่อนอื่นในการตั้งค่าไปป์การวางไข่กระบวนการเรียกใช้ "โมฆะ" ในกระบวนการใหม่ ฯลฯ นอกจากนี้nullตัวมันเองจะใช้ซีพียูสักเล็กน้อยในการอ่านไบต์เป็นบัฟเฟอร์ในภายหลัง เพิ่งถูกทิ้ง ... การนำไปใช้/dev/nullในเคอร์เนลนั้นมีประสิทธิภาพมากกว่า นอกจากนี้จะเกิดอะไรขึ้นถ้าคุณต้องการส่งผ่าน/dev/nullข้อโต้แย้งแทนที่จะเปลี่ยนเส้นทาง (คุณสามารถใช้<(...)ในการทุบตี แต่ที่แม้กระทั่งวิธีมือหนัก!)
filbranden

4
หากคุณต้องไพพ์ไปยังโปรแกรมที่ชื่อnullแทนที่จะใช้การเปลี่ยนเส้นทางไปยัง/dev/nullจะมีวิธีที่ง่ายและชัดเจนในการบอกเชลล์ให้รันโปรแกรมในขณะที่ส่ง stderr ไปเป็นโมฆะหรือไม่?
Mark Plotnick

5
นี่เป็นการตั้งค่าที่แพงมากสำหรับการสาธิตค่าใช้จ่าย ฉันอยากจะแนะนำให้ใช้/dev/zeroแทน
chrylis -on strike-

20
ตัวอย่างเหล่านั้นผิด dd of=-เขียนไปยังไฟล์ที่เรียกว่า-เพียงละเว้นการof=เขียนไปยัง stdout เป็นที่ที่ddเขียนโดยค่าเริ่มต้น การไปที่ท่อfalseไม่ทำงานเพราะfalseไม่ได้อ่าน stdin ดังนั้นddจะถูกฆ่าด้วย SIGPIPE สำหรับคำสั่งที่เหลือใช้การป้อนข้อมูลของคุณสามารถใช้ cat > /dev/null... นอกจากนี้การเปรียบเทียบผู้ที่อาจไม่เกี่ยวข้องเนื่องจากคอขวดน่าจะเป็นการสร้างตัวเลขสุ่มที่นี่
Stéphane Chazelas

8
รุ่น AST ddฯลฯ ไม่ต้องกังวลกับการเขียน syscall เมื่อตรวจพบปลายทางคือ / dev / null
Mark Plotnick

54

ฉันสงสัยว่าทำไมมีหลายอย่างที่เกี่ยวข้องกับวิสัยทัศน์ / การออกแบบที่มีรูปแบบ Unix (และต่อจาก Linux) และข้อดีที่เกิดขึ้นจากมัน

ไม่ต้องสงสัยเลยว่ามีข้อดีด้านประสิทธิภาพการทำงานที่ไม่อาจละเลยได้ แต่ฉันคิดว่ามีอีกมาก: Unix ยุคแรกมีอุปมา "ทุกอย่างเป็นไฟล์" ซึ่งมีข้อดีที่ไม่ชัดเจน แต่สง่างามถ้าคุณดู จากมุมมองของระบบมากกว่ามุมมองการเขียนสคริปต์เชลล์

สมมติว่าคุณมีnullโปรแกรมบรรทัดคำสั่งและ/dev/nullโหนดอุปกรณ์ จากมุมมองของการเขียนสคริปต์ shell foo | nullโปรแกรมจริงๆแล้วมีประโยชน์และสะดวกมากและfoo >/dev/nullใช้เวลาในการพิมพ์นานกว่าเล็กน้อยและอาจดูแปลก ๆ

แต่นี่คือแบบฝึกหัดสองข้อ:

  1. ลองใช้โปรแกรมnullโดยใช้เครื่องมือที่มีอยู่และ Unix /dev/null- cat >/dev/nullง่าย: เสร็จสิ้น

  2. คุณสามารถนำไปใช้/dev/nullในแง่ของnull?

คุณถูกต้องอย่างแน่นอนที่รหัส C ที่จะละทิ้งอินพุตนั้นเล็กน้อยดังนั้นจึงอาจยังไม่ชัดเจนว่าทำไมจึงมีประโยชน์ที่จะมีไฟล์เสมือนสำหรับงาน

พิจารณา: ภาษาการเขียนโปรแกรมเกือบทุกภาษาต้องทำงานกับไฟล์ตัวอธิบายไฟล์และพา ธ ไฟล์เนื่องจากเป็นส่วนหนึ่งของกระบวนทัศน์ "ทุกอย่างเป็นไฟล์" ของ Unix ตั้งแต่ต้น

หากสิ่งที่คุณมีคือโปรแกรมที่เขียนไปยัง stdout โปรแกรมไม่สนใจว่าคุณจะเปลี่ยนเส้นทางไปเป็นไฟล์เสมือนที่กลืนกินการเขียนทั้งหมดหรือไพพ์ลงในโปรแกรมที่กลืนการเขียนทั้งหมด

ตอนนี้ถ้าคุณมีโปรแกรมที่ใช้เส้นทางแฟ้มสำหรับการอ่านหรือเขียนข้อมูล (ซึ่งโปรแกรมส่วนใหญ่ทำ) - และคุณต้องการที่จะเพิ่ม "การป้อนข้อมูลว่างเปล่า" หรือ "ทิ้งเอาท์พุทนี้" การทำงานกับโปรแกรมเหล่านั้น - อย่างดีกับ/dev/nullที่มาฟรี

โปรดสังเกตว่าความสง่างามของมันคือการลดความซับซ้อนของรหัสของโปรแกรมที่เกี่ยวข้องทั้งหมด - สำหรับแต่ละ usecase ทั่วไป แต่พิเศษที่ระบบของคุณสามารถให้เป็น "ไฟล์" ด้วย "ชื่อไฟล์" จริงรหัสของคุณสามารถหลีกเลี่ยงการเพิ่มคำสั่งที่กำหนดเอง ตัวเลือกบรรทัดและเส้นทางรหัสที่กำหนดเองเพื่อจัดการ

วิศวกรรมซอฟต์แวร์ที่ดีมักจะขึ้นอยู่กับการหาคำอุปมาอุปมัยที่ดีหรือ "เป็นธรรมชาติ" เพื่อสรุปองค์ประกอบของปัญหาในแบบที่คิดง่ายขึ้นแต่ยังคงความยืดหยุ่นเพื่อให้คุณสามารถแก้ปัญหาระดับสูงกว่าเดิมได้โดยไม่ต้อง ใช้เวลาและพลังงานทางจิตในการปรับใช้โซลูชันในระดับต่ำกว่าเดิมอย่างต่อเนื่อง

"ทุกอย่างเป็นไฟล์" ดูเหมือนจะเป็นคำอุปมาอย่างหนึ่งสำหรับการเข้าถึงทรัพยากร: คุณเรียกopenใช้พา ธ ที่กำหนดในเนมสเปซแบบสืบทอด, รับการอ้างอิง (ตัวอธิบายไฟล์) ไปยังวัตถุและคุณสามารถreadและwriteฯลฯ ใน descriptor ไฟล์ stdin / stdout / stderr ของคุณเป็นไฟล์อธิบายที่เพิ่งจะเปิดให้คุณแล้ว ไพพ์ของคุณเป็นเพียงไฟล์และตัวอธิบายไฟล์และการเปลี่ยนเส้นทางไฟล์ช่วยให้คุณสามารถยึดชิ้นส่วนเหล่านี้เข้าด้วยกัน

ยูนิกซ์ประสบความสำเร็จมากเท่าที่ทำได้เนื่องจากบางส่วนของนามธรรมเหล่านี้ทำงานร่วมกัน/dev/nullได้ดีและเข้าใจได้ดีที่สุดในฐานะส่วนหนึ่งของทั้งหมด


ป.ล. มันน่าดูรุ่น Unix ของ "ทุกอย่างเป็นไฟล์" และสิ่งต่าง ๆ เช่น/dev/nullขั้นตอนแรกสู่การวางตัวแบบทั่วไปที่มีความยืดหยุ่นและทรงพลังของอุปมาอุปมัยที่ถูกนำไปใช้ในหลาย ๆ ระบบที่ตามมา

ตัวอย่างเช่นในวัตถุที่คล้ายไฟล์พิเศษของ Unix อย่างเช่น/dev/nullจะต้องมีการใช้งานในเคอร์เนลเอง แต่ปรากฎว่ามันมีประโยชน์พอที่จะเปิดเผยการทำงานในรูปแบบไฟล์ / โฟลเดอร์ที่ตั้งแต่นั้นมามีหลายระบบที่ทำวิธีสำหรับโปรแกรม ทำเช่นนั้น

หนึ่งในคนแรกคือระบบปฏิบัติการแผน 9 ทำโดยคนเดียวกันบางคนที่ทำยูนิกซ์ ต่อมา GNU Hurd ก็ทำสิ่งที่คล้ายกับ "นักแปล" ของมัน ในขณะเดียวกันลีนุกซ์ก็ได้รับ FUSE (ซึ่งแพร่กระจายไปยังระบบกระแสหลักอื่น ๆ ในตอนนี้เช่นกัน)


8
@PeterCordes จุดของคำตอบเริ่มต้นจากตำแหน่งที่ไม่เข้าใจการออกแบบ หากทุกคนเข้าใจการออกแบบแล้วคำถามนี้จะไม่มีอยู่จริง
OrangeDog

1
@mtraceur: เมาท์ไฟล์ภาพโดยไม่ได้รับอนุญาตจากรูต? แสดงหลักฐานบางอย่างที่ FUSE อาจไม่ต้องการรูท แต่ฉันไม่แน่ใจ
Peter Cordes

1
@PeterCordes RE: "ดูเหมือนแปลก": มันไม่ใช่คำขอโทษสำหรับการออกแบบเพียงแค่รับทราบว่ามันจะดูได้อย่างไรถ้าคุณไม่ได้คิดถึงการใช้งานระบบภายใต้มันและยังไม่มีช่วงเวลาที่ยูเรก้าเกี่ยวกับระบบ - ข้อดีการออกแบบกว้าง ฉันพยายามที่จะทำให้ชัดเจนโดยการเปิดประโยคที่มี "จากมุมมองสคริปต์เปลือกหอย" และยิ่งทำให้ความแตกต่างระหว่างที่และมุมมองของระบบสองประโยคก่อนหน้านี้ ในความคิดต่อไป "สามารถดูเหมือนแปลก" ดีกว่าดังนั้นฉันจะปรับแต่งมัน ฉันยินดีรับข้อเสนอแนะการใช้ถ้อยคำเพิ่มเติมเพื่อให้ชัดเจนขึ้นโดยไม่ทำให้เกินไป
mtraceur

2
สิ่งแรกที่ฉันถูกบอกว่าเป็นวิศวกรหนุ่มที่เกี่ยวข้องกับ Unix คือ "ทุกอย่างคือไฟล์" และฉันสาบานว่าคุณจะได้ยินเสียงเมืองหลวง และการยึดความคิดนั้นไว้ แต่เนิ่นๆทำให้ Unix / Linux ดูเหมือนง่ายต่อการเข้าใจมากขึ้น Linux ได้สืบทอดปรัชญาการออกแบบส่วนใหญ่ ฉันดีใจที่มีคนพูดถึงมัน
StephenG

2
@PeterCordes ดอส "แก้ปัญหา" พิมพ์โดยการทำให้ชื่อไฟล์มายากลปรากฏในทุกไดเรกทอรีคือสิ่งที่คุณต้องพิมพ์เป็นNUL > NUL
Cristian Ciupitu

15

ผมคิดว่า/dev/nullเป็นอุปกรณ์ตัว (ว่าพฤติกรรมเช่นไฟล์สามัญ) แทน programm สำหรับเหตุผลประสิทธิภาพ

ถ้ามันจะเป็นโปรแกรมมันจะต้องมีการโหลดเริ่มต้นการตั้งเวลาการทำงานและหลังจากนั้นหยุดและขนถ่ายโปรแกรม โปรแกรม C แบบง่าย ๆ ที่คุณกำลังอธิบายจะไม่กิน ressources มากมาย แต่ฉันคิดว่ามันสร้างความแตกต่างที่สำคัญเมื่อพิจารณาจำนวนมาก (พูดนับล้าน) ของการดำเนินการเปลี่ยนเส้นทาง / การวางท่อเนื่องจากการดำเนินการจัดการกระบวนการมีราคาสูง พวกเขาเกี่ยวข้องกับการสลับบริบท

ข้อสันนิษฐานอื่น: การวางโปรแกรมในโปรแกรมต้องได้รับการจัดสรรหน่วยความจำโดยโปรแกรมรับ (แม้ว่าจะถูกทิ้งในภายหลังโดยตรง) ดังนั้นหากคุณเข้าไปในเครื่องมือที่คุณใช้หน่วยความจำสองเท่าหนึ่งครั้งในโปรแกรมส่งและอีกครั้งในโปรแกรมที่ได้รับ


10
ไม่ใช่เพียงแค่ค่าใช้จ่ายในการติดตั้งเท่านั้น แต่การเขียนทุกครั้งในไพพ์จำเป็นต้องมีการคัดลอกหน่วยความจำและการสลับบริบทไปยังโปรแกรมการอ่าน (หรืออย่างน้อยบริบทเปลี่ยนเมื่อบัฟเฟอร์ท่อเต็มและผู้อ่านจะต้องทำสำเนาอื่นเมื่อมันreadเป็นข้อมูล) สิ่งนี้ไม่สำคัญกับ PDP-11 แบบ single-core ที่ Unix ได้รับการออกแบบ! วันนี้แบนด์วิดท์ / การคัดลอกหน่วยความจำถูกกว่ามากในตอนนี้ การwriteเรียกระบบไปยัง FD ที่เปิดอยู่/dev/nullสามารถกลับมาใช้ได้ทันทีโดยไม่ต้องอ่านข้อมูลใด ๆ จากบัฟเฟอร์
Peter Cordes

@PeterCordes บันทึกย่อของฉันเป็นวง แต่มันเป็นไปได้ที่ขัดแย้งกันในความทรงจำการเขียนวันนี้มีราคาแพงกว่าที่เคย ซีพียู 8 คอร์อาจทำการดำเนินการจำนวนเต็ม 16 ครั้งในเวลานาฬิกาในขณะที่การเขียนหน่วยความจำแบบ end-to-end จะเสร็จสมบูรณ์ในเช่น 16 นาฬิกา (CPU 4GHz, 250 MHz RAM) นั่นคือปัจจัย 256 หน่วยความจำสำหรับซีพียูสมัยใหม่นั้นเหมือนกับ RL02 ถึง PDP-11 CPU เกือบจะเหมือนกับหน่วยเก็บข้อมูลต่อพ่วง! :) ไม่ตรงไปตรงมาตามธรรมชาติ แต่ทุกสิ่งที่กดปุ่มแคชจะถูกเขียนออกไปและการเขียนที่ไร้ประโยชน์จะเป็นการตัดการคำนวณอื่น ๆ ของพื้นที่แคชที่สำคัญที่เคยมีมา
kkm

@kkm: ใช่เสียประมาณ 2 × 128kiB ของรอยเท้าแคช L3 บนไพพ์บัฟเฟอร์ในเคอร์เนลและบัฟเฟอร์การอ่านในnullโปรแกรมจะดูด แต่ซีพียูแบบมัลติคอร์ส่วนใหญ่ไม่ได้ทำงานกับคอร์ทั้งหมดยุ่งตลอดเวลาดังนั้น เวลาซีพียูในการรันnullโปรแกรมนั้นส่วนใหญ่ฟรี ในระบบที่มีแกนกลางทั้งหมดตรึงอยู่ท่อที่ไร้ประโยชน์เป็นข้อตกลงที่ใหญ่กว่า แต่ไม่ได้บัฟเฟอร์ "ร้อน" สามารถเขียนใหม่ได้หลายครั้งโดยไม่ถูกลบออกจาก RAM ดังนั้นเราส่วนใหญ่จะแข่งขันกับแบนด์วิดท์ L3 ไม่ใช่แคช ไม่ได้โดยเฉพาะอย่างยิ่งใน SMT (hyperthreading) ระบบที่หลักตรรกะอื่น ๆ (s) ในทางกายภาพเดียวกันมีการแข่งขัน ...
ปีเตอร์ Cordes

.... แต่การคำนวณหน่วยความจำของคุณมีข้อบกพร่องมาก CPU ที่ทันสมัยมีหน่วยความจำขนานกันมากมายดังนั้นแม้ว่าlatency to DRAM นั้นจะเป็นวงจรนาฬิกา 200-400 คอร์และ L3> 40 แบนด์วิดท์อยู่ที่ ~ 8 ไบต์ / นาฬิกา (น่าประหลาดใจแบนด์วิดท์เธรดเดียวกับ L3 หรือ DRAM นั้นแย่กว่าใน Xeon แบบหลายคอร์ที่มีหน่วยความจำแบบ quad-channel เทียบกับเดสก์ท็อปแบบ quad-core เนื่องจากถูก จำกัด ด้วยการทำงานพร้อมกันสูงสุดของคำขอหนึ่งคอร์ max_concurrency / latency: เพราะเหตุใด Skylake จึงดีกว่า Broadwell-E มากสำหรับการส่งผ่านหน่วยความจำเธรดเดียว? )
Peter Cordes

... ดูเพิ่มเติมที่7-cpu.com/cpu/Haswell.htmlสำหรับตัวเลขของ Haswell ที่เปรียบเทียบ quad-core กับ 18-core อย่างไรก็ตามใช่ซีพียูสมัยใหม่สามารถทำงานได้อย่างน่าขันต่อนาฬิกาหากพวกเขาไม่ได้ติดรอหน่วยความจำ ตัวเลขของคุณมีเพียง 2 ALU ต่อการดำเนินงานต่อหนึ่งครั้งเช่น Pentium ตั้งแต่ปี 1993 หรือ ARM dual-issue ARM ต่ำสุดที่ทันสมัย Ryzen หรือ Haswell อาจทำการดำเนินการจำนวนเต็ม 4 สเกลาร์ ALU + 2 หน่วยความจำ ops ต่อคอร์ต่อนาฬิกาหรือมากกว่านั้นด้วย SIMD เช่น Skylake-AVX512 มีอัตราความเร็ว 2 ต่อนาฬิกาในvpaddd zmm: 16 องค์ประกอบ 32- บิตต่อการเรียนการสอน
Peter Cordes

7

นอกเหนือจาก "ทุกอย่างเป็นไฟล์" และความสะดวกในการใช้งานในทุกที่ที่คำตอบอื่น ๆ ส่วนใหญ่อ้างอิงจากนั้นยังมีปัญหาด้านประสิทธิภาพตามที่ @ user5626466 กล่าวถึง

เพื่อแสดงในทางปฏิบัติเราจะสร้างโปรแกรมอย่างง่ายที่เรียกว่าnullread.c:

#include <unistd.h>
char buf[1024*1024];
int main() {
        while (read(0, buf, sizeof(buf)) > 0);
}

และรวบรวมมันด้วย gcc -O2 -Wall -W nullread.c -o nullread

(หมายเหตุ: เราไม่สามารถใช้ lseek (2) กับท่อดังนั้นวิธีเดียวที่จะระบายท่อคือการอ่านจากมันจนกว่ามันจะว่างเปล่า)

% time dd if=/dev/zero bs=1M count=5000 |  ./nullread
5242880000 bytes (5,2 GB, 4,9 GiB) copied, 9,33127 s, 562 MB/s
dd if=/dev/zero bs=1M count=5000  0,06s user 5,66s system 61% cpu 9,340 total
./nullread  0,02s user 3,90s system 41% cpu 9,337 total

ในขณะที่การ/dev/nullเปลี่ยนเส้นทางไฟล์มาตรฐานเราได้รับความเร็วที่ดีขึ้นมาก (เนื่องจากข้อเท็จจริงที่กล่าวถึง: การสลับบริบทน้อยกว่า, เคอร์เนลเพียงเพิกเฉยข้อมูลแทนที่จะคัดลอกเป็นต้น):

% time dd if=/dev/zero bs=1M count=5000 > /dev/null
5242880000 bytes (5,2 GB, 4,9 GiB) copied, 1,08947 s, 4,8 GB/s
dd if=/dev/zero bs=1M count=5000 > /dev/null  0,01s user 1,08s system 99% cpu 1,094 total

(นี่ควรเป็นความคิดเห็นที่นั่น แต่ใหญ่เกินไปสำหรับสิ่งนั้นและไม่สามารถอ่านได้อย่างสมบูรณ์)


คุณทดสอบฮาร์ดแวร์อะไร 4.8GB / s ค่อนข้างต่ำเมื่อเทียบกับ 23GB / s ที่ฉันใช้กับ Skylake i7-6700k (DDR4-2666 แต่บัฟเฟอร์ควรคงความร้อนในแคช L3 ดังนั้นส่วนที่ดีของค่าใช้จ่ายคือการโทรด้วยระบบของ Spectre + เปิดใช้งานการลดการล่มสลายที่เจ็บสองเท่าสำหรับการวางท่อเนื่องจากบัฟเฟอร์ของท่อมีขนาดเล็กกว่า 1M ดังนั้นจึงมีการเรียกใช้ระบบการเขียน / อ่านมากกว่านั้นความแตกต่างของ perf เกือบ 10 เท่าแย่กว่าที่ฉันคาดไว้ ในระบบ Skylake ของฉัน 3.3GB / s , รัน x86-64 Linux 4.15.8-1-ARCH, นั่นคือปัจจัยของ 6.8. ว้าว, การเรียกใช้ระบบมีราคาแพงตอนนี้)
Peter Cordes

1
@PeterCordes 3GB / s พร้อมไพพ์บัฟเฟอร์ 64kแนะนำ 2x120124 syscalls ต่อวินาที ... และจำนวนของสวิทช์บริบทนั้นเหรอ บนเซิร์ฟเวอร์ cpu ที่มี 20,000 syscalls ต่อวินาทีคุณอาจคาดหวังค่าโสหุ้ยประมาณ 8% จาก PTI เนื่องจากมีชุดการทำงานน้อยมาก (กราฟที่ฉันอ้างอิงไม่ได้รวมการเพิ่มประสิทธิภาพ PCID แต่อาจไม่สำคัญสำหรับชุดการทำงานขนาดเล็ก) ดังนั้นฉันจึงไม่แน่ใจว่า PTI มีผลกระทบอย่างมากหรือไม่ brendangregg.com/blog/2018-02-09/…
sourcejedi

1
น่าสนใจดังนั้นมันจึงเป็นSilvermont ที่มีแคช L2 2MBดังนั้นddบัฟเฟอร์ + รับบัฟเฟอร์ของคุณจึงไม่พอดี คุณอาจจัดการกับแบนด์วิดท์หน่วยความจำแทนแบนด์วิดธ์แคชระดับสุดท้าย คุณอาจได้รับแบนด์วิดธ์ที่ดีขึ้นด้วยบัฟเฟอร์ 512k หรือบัฟเฟอร์ 64k (ตามstraceเดสก์ท็อปของฉันwriteและreadส่งคืน 1048576 ดังนั้นฉันคิดว่านั่นหมายความว่าเราจ่ายเฉพาะผู้ใช้ <-> ต้นทุนเคอร์เนลของการตรวจสอบความถูกต้อง TLB + การคาดการณ์สาขาล้างหนึ่งครั้งต่อ MiB ไม่ใช่ต่อ 64k @sourcejedi ฉันคิดว่าการบรรเทาผลกระทบมีราคามากที่สุด)
Peter Cordes

1
@sourcejedi: กับอสุรกายบรรเทาผลกระทบการเปิดใช้ค่าใช้จ่ายของการsyscallที่ส่งกลับทันทีกับENOSYSเป็น ~ 1800 รอบใน Skylake กับอสุรกายบรรเทาผลกระทบการเปิดใช้งานมากที่สุดของมันเป็นwrmsrที่เลิก BPU, ตาม @ ทดสอบ เมื่อปิดการลดขนาดผู้ใช้ -> เคอร์เนล -> ผู้ใช้เวลาไปกลับประมาณ ~ 160 รอบ แต่ถ้าคุณได้สัมผัสกับความทรงจำมากมายการลดทอนของ Meltown ก็มีความสำคัญเช่นกัน Hugepages ควรช่วย (รายการ TLB ที่น้อยลงจำเป็นต้องโหลดซ้ำ)
Peter Cordes

1
@PeterCordes บนระบบ unix แบบ single-core เราจะเห็น 1 context switch ต่อ 64K หรืออะไรก็ตามที่ pipe buffer ของคุณเป็นและนั่นจะทำให้เจ็บ ... จริง ๆ แล้วฉันก็เห็น [จำนวนบริบทสวิตช์เดียวกันกับ 2 cpu cores] ( unix.stackexchange.com/questions/439260/… ); มันจะต้องนับรอบการสลีป / ปลุกสำหรับแต่ละ 64k เป็นสวิตช์บริบท (ไปยัง "กระบวนการไม่ได้ใช้งาน") การทำให้กระบวนการไพพ์ไลน์บน cpu เดียวกันนั้นใช้งานได้จริงเร็วกว่าสองเท่า
sourcejedi

6

คำถามของคุณถูกวางตัวราวกับว่าบางสิ่งอาจจะได้มาจากความเรียบง่ายโดยใช้โปรแกรม null แทนไฟล์ บางทีเราสามารถกำจัดความคิดของ "ไฟล์มายากล" และแทนที่จะมี "ท่อธรรมดา" แทน

แต่พิจารณาว่าไปป์นั้นเป็นไฟล์ด้วย โดยปกติแล้วพวกเขาจะไม่ตั้งชื่อและสามารถจัดการผ่านตัวอธิบายไฟล์เท่านั้น

ลองพิจารณาตัวอย่างที่ประดิษฐ์ขึ้นนี้:

$ echo -e 'foo\nbar\nbaz' | grep foo
foo

การใช้การทดแทนกระบวนการของ Bash ทำให้เราสามารถทำสิ่งเดียวกันให้สำเร็จผ่านทางวงเวียนที่มากขึ้น:

$ grep foo <(echo -e 'foo\nbar\nbaz')
foo

แทนที่grepสำหรับechoและเราสามารถเห็นภายใต้ฝาครอบ:

$ echo foo <(echo -e 'foo\nbar\nbaz')
foo /dev/fd/63

<(...)สร้างเป็นเพียงการแทนที่ด้วยชื่อไฟล์และ grep คิดว่ามันเปิดไฟล์เก่า ๆ /dev/fd/63มันก็เกิดขึ้นที่จะมีชื่อ นี่/dev/fdคือไดเรกทอรีมายากลที่ทำให้ชื่อ pipes สำหรับทุกไฟล์อธิบายที่ครอบครองโดยไฟล์ที่เข้าถึงมัน

เราสามารถทำให้เวทมนต์น้อยลงด้วยmkfifoการสร้างไปป์ที่มีชื่อที่ปรากฏขึ้นlsและทุกอย่างเหมือนไฟล์ทั่วไป:

$ mkfifo foofifo
$ ls -l foofifo 
prw-rw-r-- 1 indigo indigo 0 Apr 19 22:01 foofifo
$ grep foo foofifo

อื่น ๆ :

$ echo -e 'foo\nbar\nbaz' > foofifo

และดูเถิด grep fooออกจะ

ผมคิดว่าเมื่อคุณตระหนักท่อและไฟล์ปกติและไฟล์พิเศษเช่น / dev / null มีไฟล์ทั้งหมดเพียงแค่นี้ก็เห็นได้ชัดการใช้โปรแกรม null เป็นมากขึ้นซับซ้อน เคอร์เนลต้องจัดการกับการเขียนไปยังไฟล์ด้วยวิธีใดวิธีหนึ่ง แต่ในกรณีของ / dev / null มันสามารถปล่อยการเขียนลงบนพื้นได้ในขณะที่ไพพ์จะต้องถ่ายโอนไบต์ไปยังโปรแกรมอื่นซึ่งจากนั้นจะต้อง อ่านจริง ๆ พวกเขา


@Lyle ใช่ ถ้าอย่างนั้นทำไมเสียงก้องพิมพ์/dev/fd/63?
Phil

ฮึ่ม จุดดี. เปลือกของคุณอาจแตกต่างจากเชลล์เป้าหมายเก่าที่ฉันโตมาด้วย
Lyle

ข้อแตกต่างอย่างหนึ่งคือเสียงสะท้อนนั้นไม่ได้อ่านจาก stdin ในขณะที่ grep ทำ แต่ฉันไม่สามารถคิดได้ว่าเชลล์จะรู้ได้อย่างไรก่อนที่มันจะทำการประมวลผล
Lyle

1
และ Strace ทำให้ชัดเจนยิ่งขึ้นสำหรับฉัน คุณมีสิทธิ์ถูกต้องด้วยการทุบตี โครงสร้าง '<(... )' กำลังทำอะไรที่ค่อนข้างแตกต่างจาก <ชื่อไฟล์ ฮึ่ม ฉันเรียนรู้บางสิ่ง
Lyle

0

ฉันจะยืนยันว่ามีปัญหาด้านความปลอดภัยเกินกว่ากระบวนทัศน์ทางประวัติศาสตร์และประสิทธิภาพ การ จำกัด จำนวนของโปรแกรมที่มีสิทธิดำเนินการที่ได้รับสิทธิพิเศษไม่ว่าจะง่ายเพียงใดก็เป็นหลักการพื้นฐานของความปลอดภัยของระบบ การแทนที่จะ/dev/nullต้องมีสิทธิ์ดังกล่าวอย่างแน่นอนเนื่องจากการใช้งานโดยบริการของระบบ กรอบการรักษาความปลอดภัยที่ทันสมัยทำหน้าที่ได้ดีเยี่ยมในการป้องกันการหาประโยชน์ แต่ก็ไม่อาจป้องกันได้ เคอร์เนลเป็นอุปกรณ์ขับเคลื่อนเข้าถึงไฟล์เป็นเรื่องยากมากที่จะใช้ประโยชน์


ฟังดูเหมือนไร้สาระ การเขียนโปรแกรมควบคุมเคอร์เนลที่ปราศจากข้อบกพร่องนั้นง่ายกว่าการเขียนโปรแกรมที่ปราศจากข้อบกพร่องซึ่งอ่าน + ทิ้ง stdin ของมัน มันไม่จำเป็นต้องเป็น setuid หรืออะไรเพื่อให้ทั้งสอง/dev/nullหรือโปรแกรมอินทิ้งเสนอการโจมตีจะเป็นเหมือนเดิม: ได้รับสคริปต์หรือโปรแกรมที่ทำงานเป็นรากที่จะทำบางสิ่งบางอย่างแปลก ๆ (เช่นการพยายามที่จะlseekใน/dev/nullหรือเปิด มันหลายครั้งจากกระบวนการเดียวกันหรืออะไร IDK หรือเรียกใช้/bin/nullกับสภาพแวดล้อมที่แปลกหรืออะไรก็ตาม)
Peter Cordes

0

ดังที่คนอื่น ๆ ชี้ไปแล้ว/dev/null คือโปรแกรมที่ทำจากโค้ดจำนวนหนึ่ง เป็นเพียงแค่บรรทัดโค้ดเหล่านี้เป็นส่วนหนึ่งของเคอร์เนล

เพื่อให้ชัดเจนยิ่งขึ้นนี่คือการใช้งาน Linux: อุปกรณ์ตัวอักษรเรียกฟังก์ชันเมื่ออ่านหรือเขียน การเขียนไปยัง/dev/nullโทรwrite_nullในขณะที่อ่านโทรread_nullลงทะเบียนที่นี่

บรรทัดของโค้ดจำนวนเล็กน้อย: ฟังก์ชันเหล่านี้ไม่ทำอะไรเลย คุณต้องการรหัสบรรทัดมากกว่านิ้วในมือของคุณเฉพาะเมื่อคุณนับฟังก์ชั่นอื่นนอกเหนือจากการอ่านและเขียน


บางทีฉันควรจะใช้ถ้อยคำให้แม่นยำยิ่งขึ้น ฉันหมายถึงสาเหตุที่นำมันมาใช้เป็นอุปกรณ์ถ่านแทนที่จะเป็นโปรแกรม มันจะเป็นสองสามบรรทัด แต่การใช้งานโปรแกรมนั้นง่ายกว่าอย่างแน่นอน ตามที่คำตอบอื่น ๆ ชี้ให้เห็นมีประโยชน์ค่อนข้างน้อยในเรื่องนี้ ประสิทธิภาพและหัวหน้าการพกพาในหมู่พวกเขา
Ankur S

แน่ใจ ฉันเพิ่งเพิ่มคำตอบนี้เพราะการเห็นการใช้งานจริงนั้นสนุก (ฉันเพิ่งค้นพบตัวเองเมื่อเร็ว ๆ นี้) แต่เหตุผลที่แท้จริงคือสิ่งที่คนอื่น ๆ ชี้ให้เห็นแน่นอน
Matthieu Moy

ฉันด้วย! ฉันเพิ่งเริ่มเรียนรู้อุปกรณ์ใน linux และคำตอบนั้นค่อนข้างให้ข้อมูล
Ankur S

-2

ฉันหวังว่าคุณจะตระหนักถึง / dev / chargen / dev / zero และอื่น ๆ เช่นพวกเขารวมถึง / dev / null

LINUX / UNIX มีคุณสมบัติเหล่านี้บางส่วน - ทำให้ผู้ใช้สามารถใช้ WELL WRITTEN CODE FragMEnTS ได้ดี

Chargeen ได้รับการออกแบบมาเพื่อสร้างรูปแบบที่เฉพาะเจาะจงและซ้ำของอักขระ - มันค่อนข้างเร็วและจะผลักดันขีด จำกัด ของอุปกรณ์อนุกรมและมันจะช่วย debug โปรโตคอลอนุกรมที่เขียนและล้มเหลวในการทดสอบหรืออื่น ๆ

ศูนย์ถูกออกแบบมาเพื่อเติมไฟล์ที่มีอยู่หรือส่งออกจำนวนมากทั้งศูนย์

/ dev / null เป็นเพียงเครื่องมืออื่นที่มีแนวคิดเดียวกันในใจ

เครื่องมือเหล่านี้ทั้งหมดในชุดเครื่องมือของคุณหมายความว่าคุณมีโอกาสครึ่งหนึ่งในการสร้างโปรแกรมที่มีอยู่แล้วทำสิ่งที่ไม่เหมือนใครโดยไม่คำนึงถึงความต้องการเฉพาะของคุณ (อุปกรณ์ที่คุณต้องการ) เป็นอุปกรณ์หรือการเปลี่ยนไฟล์

ให้ตั้งค่าการแข่งขันเพื่อดูว่าใครสามารถสร้างผลลัพธ์ที่น่าตื่นเต้นที่สุดให้กับอุปกรณ์ตัวละครเพียงไม่กี่ตัวในรุ่น LINUX ของคุณ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.