הדרכת שיחות מערכת לינוקס עם ג

Linux System Call Tutorial With C



במאמר האחרון שלנו בנושא שיחות מערכת לינוקס , הגדרתי שיחת מערכת, דנתי בסיבות שאפשר להשתמש בהן בתוכנית והתעמקתי ביתרונות ובחסרונות שלהן. אפילו נתתי דוגמא קצרה בהרכבה בתוך ג. זה המחיש את הנקודה ותיאר כיצד לבצע את השיחה, אך לא עשה דבר פרודוקטיבי. לא בדיוק תרגיל פיתוח מרגש, אבל הוא המחיש את הנקודה.

במאמר זה נשתמש בשיחות מערכת בפועל לביצוע עבודות אמיתיות בתוכנית C שלנו. ראשית, נבדוק אם עליך להשתמש בשיחת מערכת, ולאחר מכן נספק דוגמה באמצעות שיחת sendfile () שיכולה לשפר באופן דרמטי את ביצועי העתקת הקבצים. לבסוף, נעבור על כמה נקודות שיש לזכור בעת שימוש בשיחות מערכת Linux.







למרות שזה בלתי נמנע, תשתמש בשיחת מערכת בשלב כלשהו בקריירת הפיתוח שלך ב- C, אלא אם כן אתה ממקד לביצועים גבוהים או לפונקציונליות מסוג מסוים, ספריית glibc וספריות בסיסיות אחרות הכלולות בהפצות לינוקס גדולות יטפלו ברוב הצרכים שלך.



הספרייה הסטנדרטית של glibc מספקת מסגרת חוצה פלטפורמות שנבדקה היטב לביצוע פונקציות שאחרת היו דורשות שיחות מערכת ספציפיות למערכת. לדוגמה, אתה יכול לקרוא קובץ עם fscanf (), fread (), getc () וכו ', או שאתה יכול להשתמש בקריאת מערכת read () Linux. פונקציות glibc מספקות תכונות נוספות (כלומר טיפול טוב יותר בשגיאות, IO מעוצב וכו ') ויעבוד על כל תמיכות glibc של המערכת.



מצד שני, ישנם מקרים בהם ביצועים בלתי מתפשרים וביצוע מדויק הם קריטיים. העטיפה ש- fread () מספקת תוסיף תקורה, ולמרות שהיא מינורית, אינה שקופה לחלוטין. בנוסף, ייתכן שלא תרצה או תזדקק לתכונות הנוספות שהעטיפה מספקת. במקרה זה, עדיף שתשיג שיחת מערכת.





תוכל גם להשתמש בשיחות מערכת לביצוע פונקציות שטרם נתמכות על ידי glibc. אם העותק שלך של glibc מעודכן, זה כמעט לא יהווה בעיה, אך התפתחות בהפצות ישנות יותר עם גרעינים חדשים עשויה לדרוש טכניקה זו.

כעת, לאחר שקראת את ההצהרות, האזהרות והעקיפות הפוטנציאליות, בואו נתעמק בכמה דוגמאות מעשיות.



באיזה מעבד אנו פועלים?

שאלה שרוב התוכניות כנראה לא חושבות לשאול, אך בכל זאת שאלה. זוהי דוגמה לקריאת מערכת שלא ניתן לשכפל עם glibc ואינה מכוסה בעטיפת glibc. בקוד זה, נתקשר לשיחת getcpu () ישירות באמצעות הפונקציה syscall (). הפונקציה syscall פועלת כדלקמן:

סיסקאל(SYS_call,arg1,arg2,...);

הארגומנט הראשון, SYS_call, הוא הגדרה המייצגת את מספר קריאת המערכת. כאשר אתה כולל sys/syscall.h, אלה כלולים. החלק הראשון הוא SYS_ והחלק השני הוא שם קריאת המערכת.

טיעונים לשיחה נכנסים ל- arg1, arg2 לעיל. חלק מהשיחות דורשות טיעונים נוספים, והן ימשיכו לפי סדר מדף האיש שלהן. זכור שרוב הטיעונים, במיוחד להחזרות, יחייבו מצביעים למערכי char או זיכרון שהוקצה באמצעות הפונקציה malloc.

example1.c

#לִכלוֹל
#לִכלוֹל
#לִכלוֹל
#לִכלוֹל

intרָאשִׁי() {

ללא חתימהמעבד,צוֹמֶת;

// קבל את ליבת המעבד הנוכחית ואת הצומת NUMA באמצעות שיחת מערכת
// שימו לב שאין לזה עטיפת glibc ולכן עלינו לקרוא לזה ישירות
סיסקאל(SYS_getcpu, &מעבד, &צוֹמֶת,ריק);

// הצג מידע
printf ('תוכנית זו פועלת על CPU core %u ו- nodea node %u. n n',מעבד,צוֹמֶת);

לַחֲזוֹר 0;

}

כדי לאסוף ולהריץ:

gcc דוגמא 1.ג -o דוגמה 1
./דוגמה 1

לקבלת תוצאות מעניינות יותר, תוכל לסובב שרשורים באמצעות ספריית ה- pthreads ולאחר מכן להתקשר לפונקציה זו כדי לראות באיזה מעבד החוט שלך פועל.

קובץ Send: ביצועים מעולים

Sendfile מספק דוגמה מצוינת לשיפור הביצועים באמצעות שיחות מערכת. הפונקציה sendfile () מעתיקה נתונים מתיאור קבצים אחד למשנהו. במקום להשתמש בפונקציות מרובות fread () ו- fwrite (), sendfile מבצע את ההעברה במרחב הגרעין, ומוריד את התקורה ובכך מגביר את הביצועים.

בדוגמה זו, אנו הולכים להעתיק נתונים של 64 מגה -בייט מקובץ אחד למשנהו. במבחן אחד, נשתמש בשיטות הקריאה/כתיבה הסטנדרטיות בספרייה הסטנדרטית. בשני, נשתמש בשיחות מערכת ובשיחת sendfile () כדי לפוצץ נתונים אלה ממיקום אחד למשנהו.

test1.c (glibc)

#לִכלוֹל
#לִכלוֹל
#לִכלוֹל
#לִכלוֹל

#הגדר BUFFER_SIZE 67108864
#define BUFFER_1 'buffer1'
#define BUFFER_2 'buffer2'

intרָאשִׁי() {

קוֹבֶץ*שגוי, *סוֹף;

printf (' nבדיקת קלט/פלט עם פונקציות glibc מסורתיות. n n');

// קח מאגר BUFFER_SIZE.
// במאגר יהיו נתונים אקראיים, אך לא אכפת לנו מכך.
printf ('הקצאת מאגר 64 מגה -בתים:');
לְהַשְׁחִיר *בַּלָם= (לְהַשְׁחִיר *) malloc (BUFFER_SIZE);
printf ('בוצע n');

// כתוב את המאגר ל- fOut
printf ('כתיבת נתונים למאגר הראשון:');
שגוי= fopen (BUFFER_1, 'wb');
fwrite (בַּלָם, מידה של(לְהַשְׁחִיר),BUFFER_SIZE,שגוי);
fclose (שגוי);
printf ('בוצע n');

printf ('העתקת נתונים מהקובץ הראשון לשני:');
סוֹף= fopen (BUFFER_1, 'rb');
שגוי= fopen (BUFFER_2, 'wb');
מטריף (בַּלָם, מידה של(לְהַשְׁחִיר),BUFFER_SIZE,סוֹף);
fwrite (בַּלָם, מידה של(לְהַשְׁחִיר),BUFFER_SIZE,שגוי);
fclose (סוֹף);
fclose (שגוי);
printf ('בוצע n');

printf ('חיץ משחרר:');
חינם (בַּלָם);
printf ('בוצע n');

printf ('מחיקת קבצים:');
לְהַסִיר (BUFFER_1);
לְהַסִיר (BUFFER_2);
printf ('בוצע n');

לַחֲזוֹר 0;

}

test2.c (שיחות מערכת)

#לִכלוֹל
#לִכלוֹל
#לִכלוֹל
#לִכלוֹל
#לִכלוֹל
#לִכלוֹל
#לִכלוֹל
#לִכלוֹל
#לִכלוֹל

#הגדר BUFFER_SIZE 67108864

intרָאשִׁי() {

intשגוי,סוֹף;

printf (' nבדיקת קלט/פלט עם sendfile () ושיחות מערכת קשורות. n n');

// קח מאגר BUFFER_SIZE.
// במאגר יהיו נתונים אקראיים, אך לא אכפת לנו מכך.
printf ('הקצאת מאגר 64 מגה -בתים:');
לְהַשְׁחִיר *בַּלָם= (לְהַשְׁחִיר *) malloc (BUFFER_SIZE);
printf ('בוצע n');


// כתוב את המאגר ל- fOut
printf ('כתיבת נתונים למאגר הראשון:');
שגוי=לִפְתוֹחַ('buffer1',O_RDONLY);
לִכתוֹב(שגוי, &בַּלָם,BUFFER_SIZE);
סגור(שגוי);
printf ('בוצע n');

printf ('העתקת נתונים מהקובץ הראשון לשני:');
סוֹף=לִפְתוֹחַ('buffer1',O_RDONLY);
שגוי=לִפְתוֹחַ('buffer2',O_RDONLY);
שלח קובץ(שגוי,סוֹף, 0,BUFFER_SIZE);
סגור(סוֹף);
סגור(שגוי);
printf ('בוצע n');

printf ('חיץ משחרר:');
חינם (בַּלָם);
printf ('בוצע n');

printf ('מחיקת קבצים:');
לבטל את הקישור('buffer1');
לבטל את הקישור('buffer2');
printf ('בוצע n');

לַחֲזוֹר 0;

}

בדיקות הידור והרצה 1 & 2

כדי לבנות דוגמאות אלה, תזדקק לכלי הפיתוח המותקנים בהפצה שלך. ב- Debian ובאובונטו, תוכל להתקין זאת באמצעות:

מַתְאִיםלהתקיןיסודות לבנייה

לאחר מכן הידור עם:

gcctest1.c-אוֹמבחן 1&& gcctest2.c-אוֹtest2

כדי להריץ את שניהם ולבדוק את הביצועים, הפעל:

זְמַן./מבחן 1&& זְמַן./test2

אתה אמור לקבל תוצאות כאלה:

בדיקת קלט/פלט עם פונקציות glibc מסורתיות.

הקצאת מאגר 64 מגה בייט: בוצע
כתיבת נתונים למאגר הראשון: DONE
העתקת נתונים מהקובץ הראשון לשני: בוצע
חיץ משחרר: בוצע
מחיקת קבצים: בוצע
0m0.397 אמיתי
משתמש 0m0.000s
sys 0m0.203s
בדיקת קלט/פלט עם sendfile () ושיחות מערכת קשורות.
הקצאת מאגר 64 מגה בייט: בוצע
כתיבת נתונים למאגר הראשון: DONE
העתקת נתונים מהקובץ הראשון לשני: בוצע
חיץ משחרר: בוצע
מחיקת קבצים: בוצע
0m0.019 אמיתי
משתמש 0m0.000s
sys 0m0.016s

כפי שאתה יכול לראות, הקוד שמשתמש בשיחות המערכת פועל הרבה יותר מהר מהמקבילה glibc.

דברים שכדאי לזכור

שיחות מערכת יכולות להגביר את הביצועים ולספק פונקציונליות נוספת, אך אין להם חסרונות. יהיה עליך לשקול את היתרונות שמשיחות המערכת מספקות כנגד היעדר ניידות בפלטפורמה ולפעמים פונקציונליות מופחתת בהשוואה לפונקציות הספרייה.

בעת שימוש בשיחות מערכת מסוימות, עליך להקפיד להשתמש במשאבים המוחזרים משיחות מערכת ולא בפונקציות ספרייה. לדוגמה, מבנה ה- FILE המשמש לפונקציות fopen (), fread (), fwrite () ו- fclose () אינו זהה למספר מתאר הקבצים משיחת המערכת הפתוחה () (מוחזר כמספר שלם). ערבוב אלה יכול להוביל לבעיות.

באופן כללי, לשיחות מערכת Linux יש פחות נתיבי פגוש מאשר פונקציות glibc. למרות שזה נכון שלשיחות מערכת יש טיפול ודיווח על שגיאות, תקבל פונקציונליות מפורטת יותר מפונקציית glibc.

ולסיום, מילה בנושא אבטחה. שיחות מערכת ממשק ישירות עם הגרעין. לגרעין לינוקס יש אמנם הגנות נרחבות נגד שטויות מאדמות משתמשים, אך קיימים באגים שלא נחשפו. אל תבטח ששיחת מערכת תאמת את הקלט שלך או תבודד אותך מבעיות אבטחה. זה חכם לוודא שהנתונים שאתה מעביר לשיחת מערכת מחוטאים. מטבע הדברים, זו עצה טובה לכל קריאת API, אך אינך יכול להיות זהיר בעת עבודה עם הגרעין.

אני מקווה שנהנית מהצלילה העמוקה יותר לארץ שיחות מערכת לינוקס. לרשימה מלאה של שיחות מערכת Linux, עיין ברשימת המאסטר שלנו.