מטפלי קובץ קבצים ספריות

ioctl#

לבצע קריאת מערכת ioctl(2) של בקרת התקן על מטפל קובץ.

ioctl הוא הקישור הישיר של Perl ל־syscall של POSIX ioctl(2) - ממשק הקרנל הכוללני לפעולות ספציפיות־להתקן שאינן מתאימות למודל ה־read/write הרגיל: גודל חלון טרמינל, פרמטרים של קו טורי, שאילתות התקן סוקט, מיקום סרט, וכל מה שדרייבר בוחר לחשוף דרך קוד בקשה. הוא מבצע דיספאץ« של FUNCTION מול FILEHANDLE, ומעביר את SCALAR כארגומנט השלישי - או מספר שלם קטן או מצביע למאגר בתים, תלוי במה ש־FUNCTION הספציפי מצפה לו.

שלא כמו fcntl, שקודי הבקשה שלו חיים במודול Fcntl, קודי הבקשה של ioctl כמעט אף פעם אינם מיוצאים כקבועים מוכנים מראש. המסלול הרגיל הוא:

require "sys/ioctl.ph";

שמושך את ה־header המתורגם <sys/ioctl.h>. אם הקובץ הזה אינו קיים, או אינו מגדיר את הקוד שדרוש, יש לבנות אותו מ־headers של C עם הכלי h2ph שמשולח עם Perl, או להגדיר את הקבוע בעצמך מ־header של הקרנל.

תקציר#

require "sys/ioctl.ph";
ioctl FILEHANDLE, FUNCTION, SCALAR
ioctl($fh, $request, $buf)
my $rv = ioctl($fh, $request, $arg) || -1;

מה מקבלים בחזרה#

בהצלחה, המספר השלם שהקרנל החזיר. כאשר המספר השלם הזה הוא 0, Perl מחליף את המחרוזת דו־ערכית "0 but true" - אמת בהקשר בוליאני, 0 מספרי בהקשר מספרי, ופטור מהאזהרה Argument "..." isn't numeric. זה אומר שבדיקה בוליאנית פשוטה מבחינה באופן אמין בין הצלחה לכישלון אפילו עבור בקשות שערך ההצלחה שלהן הוא ממש אפס:

ioctl($fh, $request, $buf) or die "ioctl: $!";

בכישלון, ioctl מחזיר undef ומגדיר את $! ל־errno הרלוונטי. המיפוי המלא:

מערכת ההפעלה מחזירה

Perl מחזיר

-1

undef

0

מחרוזת "0 but true"

כל ערך אחר

המספר השלם הזה

כאשר נדרשת החזרה גולמית של הקרנל - עבור קודי בקשה שמקודדים מידע בערך ההחזרה עצמו - יש להשתמש במבנה מ־perlfunc:

my $retval = ioctl($fh, $request, $arg) || -1;
printf "System returned %d\n", $retval;

איך SCALAR משמש#

SCALAR מועבר לקרנל כארגומנט השלישי של הקריאה ioctl של C, ותפקידו תלוי בקוד הבקשה:

  • מצביע למאגר. המקרה הרגיל. רוב בקשות ה־ioctl קוראות מתוך או כותבות אל struct של C שהפריסה שלו מוגדרת על־ידי הדרייבר. יש לבנות את המאגר עם pack לפני הקריאה, ועבור בקשות שכותבות חזרה, לפענח עם unpack לאחר מכן. יש לקבוע מראש את גודל הסקלר לפחות לאורך ה־struct - Perl יגדיל סקלר קטן מדי אוטומטית, אך קביעת גודל מראש מבהירה את הכוונה ומונעת הפתעות אם הדרייבר כותב יותר ממה שציפית.

  • מספר שלם קטן. מיעוט הבקשות לוקח מספר שלם חשוף במקום מצביע. אם ל־SCALAR אין ערך מחרוזת אך יש לו ערך מספרי, Perl מעביר את המספר במקום מצביע. כדי להבטיח דיספאץ« זה - אפילו עבור סקלר שאולי הומר למחרוזת קודם לכן - יש להוסיף לו 0 קודם:

    my $n = 0 + $n;      # force numeric representation
    ioctl($fh, $request, $n);
    
  • מוזנח. עבור קודי בקשה שאינם לוקחים ארגומנט שלישי, יש להעביר 0 כ־placeholder. הקרנל מתעלם ממנו; המוסכמה שומרת על הקריאה קריאה.

מצב גלובלי שהוא נוגע בו#

  • $! - מוגדר בכישלון ל־errno שמוחזר על־ידי ioctl(2).

  • מצב התקן או descriptor ב־FILEHANDLE - ioctl בדרך כלל משנה מצב קרנל המשויך ל־file descriptor הבסיסי או להתקן שלו (תכונות טרמינל, אפשרויות סוקט, מיקום סרט). מצב זה אינו נראה ל־Perl דרך משתנה אחר כלשהו; יש לקרוא אותו חזרה עם קריאת ioctl נוספת באמצעות בקשת TIOCG... / SIOCG... המתאימה.

דוגמאות#

לתשאל את גודל חלון הטרמינל ב־Linux. TIOCGWINSZ כותב struct winsize (שני unsigned short rows/cols בתוספת שני שדות שמתעלמים מהם) לתוך המאגר:

require "sys/ioctl.ph";

my $winsize = "\0" x 8;
ioctl(STDOUT, TIOCGWINSZ(), $winsize)
    or die "TIOCGWINSZ: $!";
my ($rows, $cols) = unpack('S S', $winsize);
print "terminal is $rows rows by $cols cols\n";

לספור בתים זמינים לקריאה על סוקט מבלי לצרוך אותם (FIONREAD נתמך באופן רחב על פני Unixes):

require "sys/ioctl.ph";

my $pending = pack('L', 0);
ioctl($sock, FIONREAD(), $pending)
    or die "FIONREAD: $!";
my $n = unpack('L', $pending);
print "$n bytes pending\n";

להכניס סוקט למצב non-blocking דרך בקשת FIONBIO - חלופה לגישה של fcntl / F_SETFL, והצורה ניידת היסטורית לחלק ממערכות ישנות יותר:

require "sys/ioctl.ph";

my $on = pack('L', 1);
ioctl($sock, FIONBIO(), $on)
    or die "FIONBIO: $!";

הבחנה בין ״syscall החזיר 0״ ל־״syscall נכשל״ באמצעות המוסכמה "0 but true":

my $rv = ioctl($fh, $request, $buf);
if (!defined $rv) {
    die "ioctl failed: $!";
} elsif ($rv eq "0 but true") {
    # kernel returned 0 - success with a zero value
} else {
    # kernel returned $rv
}

ייצור קוד בקשה משלך כאשר sys/ioctl.ph חסר או לא־שלם. הקידוד (_IOR / _IOW / _IOWR) ספציפי לקרנל; ב־Linux המאקרו מתרחב לערך בן 32 ביט שניתן לחשב מראש ולקבוע בקוד:

use constant TIOCGWINSZ => 0x5413;   # Linux <asm-generic/ioctls.h>

my $winsize = "\0" x 8;
ioctl(STDOUT, TIOCGWINSZ, $winsize) or die $!;

מקרי קצה#

  • שכחת ה־.ph require: קבועים כמו TIOCGWINSZ מנותחים כ־barewords ללא ערך מוגדר, מפעילים EINVAL מהקרנל. יש או require "sys/ioctl.ph" או להגדיר את הקבוע בעצמך עם use constant.

  • החזרת "0 but true": אין להשוות את ההחזרה ל־== 0 כבדיקת כישלון. יש להשתמש בבוליאני פשוט (or die) או defined. השוואה לשוויון מול המחרוזת המילולית גם עובדת ($rv eq "0 but true") אך לעיתים רחוקות נדרשת.

  • הפתעת דיספאץ« סקלר/מספר שלם: סקלר שהיה מספרי אך הומר למחרוזת - לדוגמה דרך אינטרפולציה להודעה - מתהפך מסמנטיקת ארגומנט־מספר שלם לסמנטיקת מצביע־למאגר בקריאה הבאה. יש לאלץ ייצוג מספרי עם 0 + $x כאשר קוד הבקשה מצפה למספר שלם.

  • פריסת struct ספציפית לפלטפורמה. struct winsize, struct termios, struct ifreq, וחבריהם שונים בין קרנלים ואפילו בין ארכיטקטורות באותו קרנל. אין לקבוע תבניות pack בקוד מהזיכרון; יש לאמת מול ה־header של מערכת היעד.

  • מאגר פלט קטן מדי. אם הדרייבר כותב יותר בתים ממה ש־SCALAR מחזיק כעת, הסקלר מוגדל, אך אין רמז ב־Perl שזה קרה. יש לקבוע גודל מראש לפחות לאורך ה־struct עם "\0" x N כך שקריאות מעבר לסוף המאגר יהיו באגי דרייבר, לא באגי Perl.

  • לא כל מערכת מממשת כל בקשה. ioctl עצמו הוא POSIX, אך קודי בקשה ספציפיים מוגדרים על־ידי דרייבר. בקשה לא־נתמכת נכשלת עם ENOTTY או EINVAL; קריאת ioctl על מערכת ללא ioctl(2) בכלל מעלה חריגה.

  • ioctl מול fcntl: עיבוד ארגומנטים ומוסכמת ערך החזרה זהים, ממשק קרנל שונה. יש להשתמש ב־fcntl לדגלי descriptor ולנעילות POSIX; יש להשתמש ב־ioctl לקודי בקרת התקן.

הבדלים מ־upstream#

תואם מלא ל־upstream Perl 5.42.

ראו גם#

  • fcntl - אותה מוסכמת קריאה וערך החזרה; משמש לדגלים ברמת descriptor ולנעילות מייעצות במקום לקודי בקרה ספציפיים להתקן

  • pack / unpack - נדרשים לבניית ופענוח מאגרי ה־struct שרוב בקשות ה־ioctl מחליפות עם הקרנל

  • open / sysopen - לפתוח את ההתקן או הקובץ המיוחד שעם הדרייבר שלו בקשת ה־ioctl תדבר

  • binmode - חלק מבקשות ioctl (לדוגמה שינויי מצב טרמינל) מקיימות אינטראקציה עם buffering של PerlIO; יש לעבור למצב raw כאשר הדרייבר ו־PerlIO חולקים על משמעת השורה

  • select / sysread - מסלול ה־I/O הרגיל; יש לפנות ל־ioctl רק כאשר הפעולה הדרושה אינה ניתנת להבעה כ־read או write