הרצאה שבוע #13- מערכות קבצים (חלק ד

‫שבוע ‪#13‬‬
‫פרק‪File System Implementation :‬‬
‫מימוש מערכת הקבצים‬
‫קורס מערכות הפעלה ב'‬
‫מכללת הדסה ‪ /‬מכללה חרדית‬
‫צבי מלמד‬
‫‪[email protected]‬‬
‫הרצאות הקורס מבוססות במידה רבה ביותר על ההרצאות של ד"ר יורם ביברמן‬
‫© כל הזכויות שמורות לד"ר יורם ביברמן ולצבי מלמד‬
‫©צבי מלמד‬
‫‪1‬‬
‫שימוש בקבצים ביוניקס‬
‫©צבי מלמד‬
‫‪113‬‬
‫חזרה – הבהרה‬
‫מערכת הקבצים הלוגית ‪ -‬מבט על‬
‫©צבי מלמד‬
‫‪114‬‬
‫חזרה – הבהרה‬
‫‪mount‬‬
‫©צבי מלמד‬
‫‪115‬‬
‫חזרה – הבהרה‬
‫מבנה מערכת הקבצים ביוניקס‬
‫©צבי מלמד‬
‫‪116‬‬
‫שימוש בקבצים ביוניקס‬
‫• הפילוסופיה של יוניקס‪ :‬לבנות מערכת הפעלה קומפקטית‪.‬‬
‫• בהתאמה‪ :‬יוניקס מספקת רק פעולות בסיסיות על קבצים‪:‬‬
‫– פתיחה‪ ,‬קריאה‪ ,‬כתיבה‪ ,‬תנועה )‪ (seek‬וסגירה‬
‫• כל פעולות הקו"פ מחייבות קריאות מערכת ולכן הן מכונות‬
‫‪) unbuffered i/o‬קו"פ נטול חציצה( – השם מטעה‪ ,‬כי מערכת‬
‫ההפעלה מחזיקה חוצצים עבור פעולות הקלט‪/‬פלט‪.‬‬
‫• מבחינת ה‪ kernel-‬כל פעולה על קובץ מתבצעת באמצעות ה‪file--‬‬
‫‪ = descriptor‬מספר הכניסה שהוקצתה לקובץ במערך הקבצים‬
‫הפתוחים של התהליך‪.‬‬
‫©צבי מלמד‬
‫‪117‬‬
‫מתארי הקבצים הסטנדרטיים‬
‫• מתאר קובץ ‪ :#0‬קלט סטנדרטי‬
‫• מתאר קובץ ‪ :#1‬פלט סטנדרטי‬
‫• מתאר קובץ ‪ :#2‬קובץ השגיאה הסטנדרטי‬
‫• בקובץ >‪ <unistd.h‬מוגדרים הקבועים‪:‬‬
‫– ‪STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO‬‬
‫©צבי מלמד‬
‫‪118‬‬
‫פעולות על קבצים – פתיחת קובץ‬
SYNOPSIS
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
int creat(const char *pathname, mode_t mode);
DESCRIPTION
The
open() system call is used to convert a pathname into
a file descriptor (a small, non-negative integer
for
in
When the
subsequent
I/O
as with read, write, etc.).
call is successful, the file descriptor returned
will
use
be
the lowest file descriptor not currently open for the process.
119
‫©צבי מלמד‬
‫פעולות על קבצים – פתיחת קובץ‬
‫;)‪int open(const char *pathname, int flags‬‬
‫;)‪int open(const char *pathname, int flags, mode_t mode‬‬
‫• הפרמטר הראשון הוא מחרוזת שכוללת את ה‪ path-‬לקובץ‬
‫• הפרמטר השני מתאר כיצד יש לפתוח את הקובץ‬
‫• הפרמטר השלישי מתאר את ההרשאות לקובץ במקרה‬
‫שמשתמשים בפקודה ליצור קובץ חדש‪.‬‬
‫– מתי יוצרים קובץ חדש? ‪ ‬את זה מתארים ה"דגלים" של‬
‫הפרמטר השני )בשקף הבא(‬
‫• ערך מוחזר‪ :‬מתאר הקובץ הפנוי הראשון במערך מתארי הקבצים‬
‫של התהליך‬
‫©צבי מלמד‬
‫‪120‬‬
‫פעולות על קבצים – פתיחת קובץ‬
‫;)‪int open(const char *pathname, int flags‬‬
‫;)‪int open(const char *pathname, int flags, mode_t mode‬‬
‫• הפרמטר השני – דגלי הסטטוס של הקובץ‪ :‬האפשרויות לפתיחת הקובץ‬
‫– ‪O_RDONLY, O_WRONLY, O_RDWR‬‬
‫– ניתן לבצע ‪ bit-wise OR‬לדגלים נוספים‬
‫– ‪ – O_APPEND‬הוסף בסוף‬
‫– ‪ – O_CREAT‬צור את הקובץ אם אינו קיים‬
‫– ‪ – O_EXCL‬הכשל אם התבקשת ליצור והקובץ כבר קיים‬
‫– ‪ – O_TRUNC‬אם הקובץ קיים ונפתח לכתיבה – רוקן אותו‬
‫– ‪ – O_SYNC‬פעולת כתיבה תחזור רק אחרי שהנתונים נכתבו לדיסק‬
‫)להבדיל‪ :‬החזרה מהקריאה לאחר שהנתונים נכתבו לחוצץ(‬
‫©צבי מלמד‬
‫‪121‬‬
‫סגירת קובץ‬/ ‫פעולות על קבצים – יצירת‬
:‫• יצירת קובץ‬
int open(const char *pathname, int flags, mode_t mode);
int creat(const char *pathname, mode_t mode);
creat()-‫ עם הדגלים המתאימים או ב‬open()-‫– ניתן להשתמש ב‬
int fd = open(“~/workspace/os/ex1.txt”,
W_ONLY | O_CREAT | O_TRUNC,
0600);
:‫• סגירת קובץ‬
int close(int fd);
.close() ‫– קריאה לפונקציה‬
‫ בכשלון‬-1 ,‫– מחזירה אפס בהצלחה‬
122
‫©צבי מלמד‬
(file pointer) ‫שינוי מקומו של מכוון הקובץ‬
NAME
lseek - reposition read/write file offset
SYNOPSIS
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fildes, off_t offset, int whence);
DESCRIPTION
The lseek function repositions the offset of the file
descriptor fildes to the argument offset according to the
directive whence as follows: .......
123
‫©צבי מלמד‬
‫שינוי מקומו של מכוון הקובץ )‪(file pointer‬‬
‫•‬
‫•‬
‫•‬
‫•‬
‫•‬
‫נשתמש בפקודת )(‪ [ l = long ] lseek‬בכדי לשנות את מיקומו של‬
‫המצביע לקובץ‬
‫הפעולה קובעת את ההסטה ‪ offset‬של המכוון מתחילת הקובץ )אפס –‬
‫הבית הראשון בקובץ(‬
‫כל פעולת קריאה או כתיבה משנה את המצביע בהתאם למספר הבתים‬
‫שנקראו‪/‬נכתבו בפועל‬
‫מחזירה את מקומו החדש של מכוון הקובץ או ‪ -1‬במקרה של כישלון‬
‫הארגומנט השלישי – ‪ =) whence‬מאיזה מקום(‬
‫– ‪ – SEEK_SET‬קבע את המצביע להיות ‪ offset‬בתים מתחילת‬
‫הקובץ‬
‫– ‪ – SEEK_CUR‬התקדם ‪ offset‬בתים יחסית למיקום הנוכחי‬
‫)‪ offset‬עשוי להיות שלילי(‬
‫– ‪ – SEEK_END‬התקדם ‪ offset‬בתים יחסית לסוף הקובץ‬
‫©צבי מלמד‬
‫‪124‬‬
‫שינוי מקומו של מכוון הקובץ )‪(file pointer‬‬
‫• בכדי לדעת את מיקומו הנוכחי של המכוון נקרא ל‪ lseek() -‬עם‬
‫‪offset=0‬‬
‫• הפונקציה )(‪ lseek‬אינה מבצעת פעולת קו"פ‬
‫• בקובץ שפתוח לכתיבה – ניתן להזיז את המכוון מעבר לסוף הקובץ‬
‫• התוצאה‪:‬‬
‫– הכתיבה הבאה תהיה במקום שאליו מצביע המכוון‬
‫– יצרנו "חור" בקובץ – חור שמכיל אפסים‬
‫• דוגמא‪file_creat_hole.c:‬‬
‫©צבי מלמד‬
‫‪125‬‬
file_creat_hole.c:‫דוגמא‬
Now, offset is: 0
126
‫©צבי מלמד‬
file_creat_hole.c:‫דוגמא‬
now, offset is: 3
now, offset is: 15
now, offset is: 18
127
‫©צבי מלמד‬
‫הרצה ‪file_creat_hole.c:‬‬
‫גודל הקובץ הוא ‪18‬‬
‫הפקודה ‪ (octal dump) od‬מציגה את‬
‫תוכן הקובץ‪.‬‬
‫האופציה ‪ –c‬הצג ב‪-‬ערכי תוים ‪ascii‬‬
‫©צבי מלמד‬
‫הכתובת בקובץ מוצגת‬
‫בבסיס ‪hexa‬‬
‫)‪ 00020‬שקול ל‪ 16-‬עשרוני(‬
‫‪128‬‬
read() – ‫קריאה מקובץ‬
read - read from a file descriptor
SYNOPSIS
ssize_t read(int fd, void *buf, size_t count);
DESCRIPTION
read() attempts to read up to count bytes from file
descriptor fd into the buffer starting at buf.
‫• הפונקציה מחזירה‬
‫ או‬,‫– את מספר הבתים שנקראו בפועל‬
eof ‫– אפס אם‬
‫ במקרה של כישלון‬-1 –
129
‫©צבי מלמד‬
‫קריאה מקובץ – )(‪read‬‬
‫;)‪ssize_t read(int fd, void *buf, size_t count‬‬
‫• מספר הבתים שקראנו )ערך ההחזרה( קטן מ‪ count -‬כאשר‪:‬‬
‫א‪ -‬נותרו פחות מ‪ count-‬בתים עד סוף הקובץ )ה‪ read-‬הבא יחזיר‬
‫אפס(‬
‫ב‪ -‬כשקוראים מטרמינל – נקראת לכל היותר שורה אחת‬
‫ג‪ -‬בקריאה מהרשת – בגלל חציצה ואופני העברת נתונים ייתכן‬
‫שייקראו פחות בתים מכפי שביקשנו‬
‫©צבי מלמד‬
‫‪130‬‬
‫דוגמא‪file_read.c :‬‬
‫©צבי מלמד‬
‫‪131‬‬
‫דוגמא‪file_read.c :‬‬
‫©צבי מלמד‬
‫‪132‬‬
‫דוגמא‪:‬‬
‫‪file_read.c‬‬
‫©צבי מלמד‬
‫‪133‬‬
‫הרצה‪file_read.c :‬‬
‫©צבי מלמד‬
‫‪134‬‬
‫כתיבה על קובץ‬
‫‪size_t write(int fd,‬‬
‫‪const void *buf,‬‬
‫;)‪size_t count‬‬
‫• הפונקציה מחזירה‪:‬‬
‫– מספר הבתים שנכתבו או ‪ -1‬בכישלון‬
‫• סיבות אפשריות לכישלון‪:‬‬
‫– דיסק מלא‪ ,‬גודל הקובץ עבר את המקסימום שהוגדר לו‬
‫• השפעה על מהירות הביצוע‪:‬‬
‫– תכנית שמשתמשת בפעולות ‪ read/write‬תרוץ במהירות גבוהה ביותר‬
‫אם החוצץ יהיה לפחות בגודל בלוק עבור אותו ציוד‬
‫– הגדלת החוצץ מעבר לגודל בלוק לא משפרת את הביצועים‬
‫– פקודת ‪) stat‬לדוגמא‪(stat /dev/tty :‬‬
‫©צבי מלמד‬
‫‪135‬‬
‫כתיבה על קובץ‬
‫• במקום לכתוב‪/‬לקרוא על קבצים באמצעות פעולות ‪read/write‬‬
‫ניתן למפות את הקובץ לזכרון )(‪ mmap‬ולפנות אליו כמו אל מערך‬
‫)באמצעות מצביע(‬
‫– המערך בזיכרון הראשי – מכיל את נתוני הקובץ שמקורו‬
‫בדיסק‬
‫– קריאת כתובת של הקובץ שעדיין לא נטענה לזיכרון ‪page ‬‬
‫‪ + fault‬טעינת הדפים לתוכנית )שקוף לתכנית(‬
‫– כתיבה למערך – מערכת ההפעלה תעדכן את הקובץ בדיסק –‬
‫באופן שקוף‪ ,‬בתזמון על פי שיקולי המערכת‪.‬‬
‫©צבי מלמד‬
‫‪136‬‬
‫שיתוף קבצים – הכנה …‪teaser‬‬
‫דילמה ‪:1#‬‬
‫•‬
‫להלן תסריט בעייתי‪:‬‬
‫תהליך א' פתח קובץ שגודלו ‪ 1000‬בתים לכתיבה )לא במוד ‪ (append‬ומבצע‪:‬‬
‫)‪if (lseek(fd, 0, SEEK_END) <0‬‬
‫‪........‬‬
‫‪if (write(fd, buff, 100)!=100‬‬
‫‪........‬‬
‫•‬
‫תהליך ב' עושה במקביל את אותן הפעולות על אותו הקובץ‬
‫©צבי מלמד‬
‫‪137‬‬
teaser… ‫שיתוף קבצים – הכנה‬
:fork() ‫אחרי‬/‫ פתיחת קבצים לפני‬:2# ‫דילמה‬
:'‫• מקרה א‬
if (fd=open(“xyz”) <0)...
fork()
...
write(fd, “abcde”);
:'‫• מקרה ב‬
fork()
if (fd=open(“xyz”) <0)...
write(fd, “abcde);
138
‫©צבי מלמד‬
‫שיתוף קבצים‬
‫• שלושה מבני נתונים שה‪ kernel-‬מחזיק עבור ניהול הקו"פ‪:‬‬
‫א‪ -‬במערך התהליכים ‪) PCB‬או ‪ – (process descriptor‬כניסה לכל‬
‫תהליך שמורץ במערכת‪.‬‬
‫–‬
‫ה ‪ PCB‬מכיל את טבלת מתארי הקבצים הפתוחים של התהליך‬
‫– ‪.file descriptor table‬‬
‫–‬
‫לכל מתאר‪:‬‬
‫•‬
‫דגלי הסטטוס – האם המתאר נשאר פתוח לאחר ביצוע )(‪exec‬‬
‫•‬
‫מצביע לטבלת קבצים פתוחים גלובלית של המערכת‬
‫©צבי מלמד‬
‫‪139‬‬
‫שיתוף קבצים‬
‫ב‪ -‬ה‪ kernel-‬מחזיק מערך של קבצים פתוחים‪.‬‬
‫– קובץ עשוי להיות מיוצג יותר מפעם אחת אם הוא נפתח מספר‬
‫פעמים‬
‫–‬
‫כל כניסה מכילה‪:‬‬
‫•‬
‫דגלי סטטוס של הקובץ ‪(read, write, sync, append,‬‬
‫)…‪trunc‬‬
‫•‬
‫ה‪ offset -‬הנוכחי של הקובץ‬
‫•‬
‫מצביע לכניסה המתאימה בטבלת ה‪ VNODE-‬של‬
‫המערכת‬
‫©צבי מלמד‬
‫‪140‬‬
‫שיתוף קבצים‬
‫ג‪ -‬לכל קובץ פתוח או רכיב ציוד‪ ,‬קיים מבנה בשם ‪ .vnode‬מכיל‪:‬‬
‫–‬
‫מידע אודות סוג הקובץ‬
‫– מצביעים לפונקציות שפועלות על הקובץ‬
‫– בדרך כלל ה‪ vnode-‬מכיל בתוכו את ה‪(v=virtual, inode-‬‬
‫)‪i=information‬‬
‫–‬
‫ה‪ inode-‬מכיל את כל ה‪ – meta-data-‬המידע "אודות הקובץ"‬
‫– הבעלים‪ ,‬הרשאות‪ ,‬גודל‪ ,‬זמנים‪ ,‬מצביעים לבלוקים‬
‫בדיסק‪(...‬‬
‫–‬
‫ה‪ inode-‬נשמר על הדיסק בטבלת ה‪ .inodes-‬נטען כאשר‬
‫הקובץ נפתח בפעם הראשונה‬
‫©צבי מלמד‬
‫‪141‬‬
‫שיתוף קבצים – מבני הנתונים העיקריים‬
‫©צבי מלמד‬
‫‪142‬‬
‫©צבי מלמד‬
‫‪143‬‬
‫שיתוף קבצים – ‪vnode structure‬‬
‫©צבי מלמד‬
‫‪144‬‬
file-descriptor & open-file tables
145
‫©צבי מלמד‬
‫פעולות אטומיות‬
‫להלן תסריט בעייתי‪:‬‬
‫• תהליך א' פתח קובץ שגודלו ‪ 1000‬בתים לכתיבה )לא במוד‬
‫‪ (append‬ומבצע את הפעולות‪:‬‬
‫)‪if (lseek(fd, 0, SEEK_END) <0‬‬
‫‪........‬‬
‫‪if (write(fd, buff, 100)!=100‬‬
‫‪........‬‬
‫תהליך ב' עושה במקביל את אותן הפעולות על אותו הקובץ‬
‫©צבי מלמד‬
‫‪146‬‬
‫הבהרה – שני תהליכים שפותחים אותו קובץ‬
‫• תזכורת‪ :‬להלן תיאור של מבנה הנתונים שה‪ kernel-‬מחזיק לטיפול‬
‫בקבצים‬
‫• מה קורה כשתהליך מבצע ‪?fork‬‬
‫• מה קורה כששני תהליכים נפרדים פותחים את אותו הקובץ?‬
‫©צבי מלמד‬
‫‪147‬‬
‫הבהרה – שני תהליכים שפותחים אותו קובץ‬
‫• מה קורה כאשר שני תהליכים פותחים – באופן עצמאי – את אותו‬
‫הקובץ?‬
‫• לכל פתיחה של קובץ נוצרת כניסה נפרדת בטבלת הקבצים‬
‫הפתוחה‬
‫– מאפשר‪ ,‬לדוגמא‪ ,‬מכוון קובץ )‪ (offset‬נפרד לכל אחד‬
‫מהתהליכים‬
‫• אבל‪ ..‬קיים רק ‪ vnode‬אחד )וכמובן גם רק ‪ inode‬אחד‪ ,‬עבור‬
‫הקובץ הזה(‪.‬‬
‫• המחשה בשקף הבא‪.‬‬
‫©צבי מלמד‬
‫‪148‬‬
‫הבהרה – שני תהליכים שפותחים אותו קובץ‬
‫©צבי מלמד‬
‫‪149‬‬
‫הפונקציות )(‪dup() dup2‬‬
‫• פגשנו אותן בסמסטר א'‬
‫;)‪int dup(int fildes‬‬
‫;)‪int dup2(int fildes, int fildes2‬‬
‫•‬
‫•‬
‫•‬
‫•‬
‫•‬
‫)(‪ dup‬משכפלת את מתאר הקובץ‪ ,‬ומחזירה את מתאר הקובץ‬
‫החדש‬
‫בפונקציה )(‪ dup2‬אנו קובעים את הערך הרצוי ל‪ .filedes2-‬אם‬
‫מקום זה "תפוש" על ידי מתאר של קובץ פתוח – אזי ראשית נסגר‬
‫הקובץ הקיים נסגר‬
‫הפעולות שמבצעות פקודות אלו הן אטומיות!‬
‫דרך אלטרנטיבית היא להשתמש בפקודה ‪ fcntl‬כפי שמיד נראה‪.‬‬
‫השקף הבא – המחשה של פעולת )(‪dup‬‬
‫©צבי מלמד‬
‫‪150‬‬
kernel structure after dup()
151
‫©צבי מלמד‬
‫שיתוף קבצים‬
‫כיצד משולבות הפעולות שמבצעים התהליכים השונים על הקובץ?‬
‫א‪ -‬כל כתיבה מזיזה את המצביע לקובץ בטבלת הקבצים הפתוחים‪.‬‬
‫–‬
‫אם הקובץ גדל כתוצאה מכך – אזי ה‪ inode-‬מתעדכן‬
‫ב‪ -‬אם הקובץ נפתח במוד של ‪ O_APPEND‬אזי הדבר מצוין בסטטוס‬
‫בטבלת הקבצים הפתוחים‬
‫–‬
‫בכל פעולת כתיבה מתבצע באופן אטומי‪ (1) :‬מצביע הכתיבה מקבל‬
‫את גודל הקובץ מה‪ (2) inode-‬מתבצעת כתיבה )‪ (3‬ערכו מתעדכן‬
‫ג‪ lseek -‬מעדכן רק את טבלת הקבצים הפתוחים )אך לא את ה‪(inode-‬‬
‫ד‪ fork() -‬ו‪ dup() -‬גורמות לכך ששתי כניסות בטבלת המתארים תתבצענה‬
‫על כניסה אחת בטבלת הקבצים הפתוחים‬
‫©צבי מלמד‬
‫‪152‬‬
‫פעולות אטומיות‬
‫תסריט אפשרי‪:‬‬
‫• שני תהליכים כותבים לאותו קובץ‪ ,‬והכוונה שכל כתיבה תתבצע‬
‫בסוף הקובץ )‪.(append‬‬
‫‪ .1‬בטבלת הקבצים הפתוחים‪ ,‬ברשומה של תהליך א' מסומן כי‬
‫מכוון הקובץ מצביע לכתובת ‪1000‬‬
‫‪ .2‬בטבלת הקבצים הפתוחים‪ ,‬ברשומה של תהליך ב' מסומן כי‬
‫מכוון הקובץ מצביע לכתובת ‪1000‬‬
‫‪ .3‬תהליך א' כותב את מאה הבתים שלו והקובץ גדל ל‪1100-‬‬
‫בתים‪.‬‬
‫‪ .4‬תהליך ב' כותב את הבתים שלו על גבי אילו של תהליך א'‬
‫• מכיוון שזה לא מה שרצינו ‪ ‬קיבלנו תוצאה שגויה‬
‫©צבי מלמד‬
‫‪153‬‬
‫פעולות אטומיות‬
‫הסיבה לבעיה‪:‬‬
‫• שתי הפעולות הללו‪:‬‬
‫קידום המכוון לסוף הקובץ ‪if (lseek(fd, 0, SEEK_END) <0); //‬‬
‫כתיבה לקובץ ‪if (write(fd, buff, 100)!=100; //‬‬
‫• אינן אטומיות‪ .‬ולכן‪ ,‬לא מובטח לנו שלכשיתבצע ה‪ write-‬הוא אכן‬
‫יכתוב לסוף הקובץ‬
‫©צבי מלמד‬
‫‪154‬‬
:‫דוגמא‬
file_share_bad.c
155
‫©צבי מלמד‬
file_share_bad.c :‫דוגמא‬
156
‫©צבי מלמד‬
:‫הרצה‬
file_share_
bad.c
157
‫©צבי מלמד‬
‫פעולות אטומיות‬
‫הפתרון‪:‬‬
‫• כל תהליך יפתח את הקובץ עם ‪ O_APPEND‬ואז ה‪ kernel-‬ידאג לכך‬
‫שלפני כל פעולת כתיבה‪ ,‬מכוון‪-‬הקובץ )‪ (offset‬יוזז לסוף הקובץ‪.‬‬
‫ובנוסף‪ ...‬שתי הפקודות תבוצענה באופן אטומי‬
‫• באופן דומה פעולת יצירת הקובץ‪:‬‬
‫)‪open(“...”, O_WRONLY | O_CREAT, 0600‬‬
‫מבצעת שתי פעולות‪:‬‬
‫– בדיקה האם הקובץ קיים‬
‫– אם אינו קיים – אז יצירתו‬
‫• לכאורה – בין שתי הפעולות עלול היה תהליך אחר ליצור את הקובץ‪,‬‬
‫לכתוב עליו‪ ,‬ואז התהליך שלנו היה דורס את התוכן שזה עתה נכתב‪.‬‬
‫• למעשה – מערכת ההפעלה מבטיחה לנו אטומיות של הבדיקה ‪ +‬יצירת‬
‫הקובץ‬
‫©צבי מלמד‬
‫‪158‬‬
file_share_good.c :‫דוגמא‬
159
‫©צבי מלמד‬
file_share_good.c :‫הרצה‬
160
‫©צבי מלמד‬
‫הפונקציה )(‪fcntl‬‬
‫• הפונקציה )(‪ fcntl‬משמשת לצורך קביעת ושליפת התכונות של‬
‫קובץ פתוח‪:‬‬
‫©צבי מלמד‬
‫‪161‬‬