UNIX nonblocking I / O: O_NONBLOCK เทียบกับ FIONBIO


92

ในทุกตัวอย่างและการสนทนาที่ฉันใช้ในบริบทของการเขียนโปรแกรมซ็อกเก็ต BSD ดูเหมือนว่าวิธีที่แนะนำในการตั้งค่าตัวอธิบายไฟล์เป็นโหมด I / O ที่ไม่ปิดกั้นคือการใช้O_NONBLOCKแฟล็กเพื่อfcntl()เช่น

int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);

ฉันทำการเขียนโปรแกรมเครือข่ายใน UNIX มานานกว่าสิบปีแล้วและใช้การFIONBIO ioctl()โทรเพื่อทำสิ่งนี้มาโดยตลอด:

int opt = 1;
ioctl(fd, FIONBIO, &opt);

ไม่เคยคิดมากว่าทำไม เพิ่งเรียนรู้วิธีนี้

ใครมีความเห็นเกี่ยวกับข้อดีของข้อใดข้อหนึ่งหรือไม่? ฉันคิดว่าโลคัสการพกพานั้นแตกต่างกันบ้าง แต่ไม่รู้ว่าioctl_list(2)มันไม่ได้พูดถึงแง่มุมของแต่ละioctlวิธี

คำตอบ:


137

ก่อนที่จะมีการกำหนดมาตรฐานจะมีioctl(... FIONBIO... )และfcntl(... O_NDELAY... )แต่สิ่งเหล่านี้มีพฤติกรรมที่ไม่สอดคล้องกันระหว่างระบบและแม้แต่ในระบบเดียวกัน ตัวอย่างเช่นเป็นเรื่องปกติที่FIONBIOจะทำงานกับซ็อกเก็ตและO_NDELAYทำงานกับ ttys โดยมีหลายสิ่งที่ไม่สอดคล้องกันสำหรับสิ่งต่างๆเช่นไปป์ฟีโฟสและอุปกรณ์ และหากคุณไม่ทราบว่าคุณมีตัวอธิบายไฟล์ประเภทใดคุณต้องตั้งค่าทั้งสองอย่างให้แน่ใจ แต่นอกจากนี้การอ่านแบบไม่ปิดกั้นที่ไม่มีข้อมูลก็ถูกระบุว่าไม่สอดคล้องกัน ขึ้นอยู่กับระบบปฏิบัติการและชนิดของไฟล์ตัวอธิบายการอ่านอาจส่งคืน 0 หรือ -1 ด้วย errno EAGAIN หรือ -1 พร้อมกับ errno EWOULDBLOCK แม้วันนี้การตั้งค่าFIONBIOหรือO_NDELAYบน Solaris ทำให้การอ่านไม่มีข้อมูลส่งคืน 0 บน tty หรือไปป์หรือ -1 โดยมี errno EAGAIN บนซ็อกเก็ต อย่างไรก็ตาม 0 มีความคลุมเครือเนื่องจากถูกส่งคืนสำหรับ EOF ด้วย

POSIX แก้ไขปัญหานี้ด้วยการแนะนำO_NONBLOCKซึ่งมีลักษณะการทำงานที่เป็นมาตรฐานในระบบต่างๆและประเภทตัวอธิบายไฟล์ เนื่องจากระบบที่มีอยู่มักต้องการหลีกเลี่ยงการเปลี่ยนแปลงพฤติกรรมใด ๆ ที่อาจทำลายความเข้ากันได้ย้อนหลัง POSIX จึงกำหนดแฟล็กใหม่แทนที่จะกำหนดพฤติกรรมเฉพาะสำหรับหนึ่งในระบบอื่น ระบบบางระบบเช่น Linux ปฏิบัติต่อทั้ง 3 ระบบเหมือนกันและยังกำหนด EAGAIN และ EWOULDBLOCK เป็นค่าเดียวกัน แต่ระบบที่ต้องการรักษาพฤติกรรมดั้งเดิมอื่น ๆ เพื่อความเข้ากันได้แบบย้อนกลับสามารถทำได้เมื่อใช้กลไกรุ่นเก่า

โปรแกรมใหม่ควรใช้fcntl(... O_NONBLOCK... )ตามมาตรฐานของ POSIX


6
ฉันมักจะใช้ ioctl () สำหรับสิ่งนี้เนื่องจากฉันเสียค่าใช้จ่ายเพียง syscall เดียวในการเปิดใช้งานโหมดที่ไม่ปิดกั้นแทนที่จะเป็นสองสำหรับ fcntl () นอกจากนี้ Windows ioctlsocket () API จะเทียบเท่ากับ ioctl () สำหรับวัตถุประสงค์ของฟังก์ชันนี้
Wez Furlong

nginx จะทำเช่นนี้หากทำได้และทำเครื่องหมายด้วยความคิดเห็น "ioctl (FIONBIO) ตั้งค่าโหมดไม่ปิดกั้นด้วย syscall เดียว" ตอนนี้มี accept2 ซึ่งช่วยให้คุณยอมรับการเชื่อมต่อและวางไว้ในโหมดไม่ปิดกั้นใน syscall เดียวกัน
Eloff

6

ดังที่ @Sean กล่าวไว้ว่าfcntl()เป็นมาตรฐานส่วนใหญ่ดังนั้นจึงมีให้บริการในแพลตฟอร์มต่างๆ ioctl()ฟังก์ชั่นถือกำเนิดfcntl()ใน Unix แต่ไม่ได้มาตรฐานที่ทุกคน การioctl()ทำงานให้คุณในทุกแพลตฟอร์มที่เกี่ยวข้องกับคุณนั้นโชคดี แต่ไม่รับประกัน โดยเฉพาะอย่างยิ่งชื่อที่ใช้สำหรับอาร์กิวเมนต์ที่สองนั้นมีความลับและไม่น่าเชื่อถือในทุกแพลตฟอร์ม อันที่จริงพวกเขามักจะไม่ซ้ำกันสำหรับไดรเวอร์อุปกรณ์เฉพาะที่ตัวอธิบายไฟล์อ้างถึง (การioctl()โทรที่ใช้สำหรับอุปกรณ์กราฟิกบิตแมปที่ทำงานบน ICL Perq ที่รัน PNX (Perq Unix) เมื่อยี่สิบปีที่แล้วไม่เคยแปลเป็นอย่างอื่นที่อื่นเป็นต้น)


5

ฉันเชื่อว่าfcntl()เป็นฟังก์ชัน POSIX สิ่งที่เป็นioctl()มาตรฐาน UNIX นี่คือรายการของPOSIX io ioctl()เป็นสิ่งที่เฉพาะเจาะจงของเคอร์เนล / ไดรเวอร์ / ระบบปฏิบัติการ แต่ฉันแน่ใจว่าสิ่งที่คุณใช้ทำงานได้กับ Unix เกือบทุกรสชาติ ioctl()สิ่งอื่น ๆ บางอย่างอาจใช้ได้เฉพาะกับระบบปฏิบัติการบางระบบหรือแม้กระทั่งบางส่วนของเคอร์เนล


ฉันใช้ FIONBIO บน AIX, Solaris, Linux, * BSD และ IRIX โดยไม่มีปัญหา แต่ใช่ฉันเข้าใจว่ามันจะไม่ทำงานบน Windows ตัวอย่างเช่นมันเป็นอินเทอร์เฟซระดับต่ำสำหรับการใช้งานเคอร์เนลที่เฉพาะเจาะจงมาก ถึงกระนั้นฉันก็สงสัยว่ามีปัจจัยที่แตกต่างอื่น ๆ หรือไม่
Alex Balashov
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.