5.9 מערכת הקבצים proc הרצאה
הקדמה¶
עד עכשיו למדנו איך לעבוד עם קבצים, תהליכים, מתארי קבצים ועוד.
אבל יש שאלה שעולה - איך אפשר לדעת מה קורה בתוך מערכת ההפעלה? איך כלים כמו ps, top, htop ו-lsof יודעים להראות לנו מידע על תהליכים, זיכרון ומשאבי מערכת?
התשובה היא מערכת הקבצים proc - או בשמה המלא /proc.
לינוקס חושפת מידע על הקרנל ועל תהליכים דרך מערכת קבצים וירטואלית שנקראת /proc. אלה לא קבצים אמיתיים על הדיסק - הקרנל מייצר את התוכן שלהם בזמן אמת כשאנחנו קוראים אותם. כלומר, כל פעם שאנחנו עושים cat על קובץ ב-/proc, הקרנל מרכיב את התוכן באותו רגע ממש.
זה רעיון מאוד אלגנטי - במקום ליצור API מיוחד לקריאת מידע מהקרנל, לינוקס פשוט חושפת את הכל דרך קבצים. ואנחנו כבר יודעים לעבוד עם קבצים.
מידע על תהליכים - proc/[pid]/¶
בתוך /proc יש ספריות שנקראות לפי מספרי PID. כל ספריה כזו מכילה מידע על תהליך מסוים.
למשל, אם יש לנו תהליך עם PID 1234, כל המידע עליו נמצא ב-/proc/1234/.
נעבור על הקבצים החשובים ביותר בספריה הזו.
מיפויי זיכרון - proc/[pid]/maps¶
הקובץ /proc/[pid]/maps מכיל את כל מיפויי הזיכרון של התהליך - כלומר, אילו אזורי זיכרון ממופים לתהליך, עם איזה הרשאות, ומאיפה הם באו.
זוכרים את paging מפרק 2? אלה בעצם הentries של הpage table שדיברנו עליהן! כל שורה מייצגת אזור זיכרון וירטואלי שממופה לדפים פיזיים.
נריץ לדוגמה:
פלט לדוגמה:
55a3c8a00000-55a3c8a01000 r--p 00000000 08:01 1234567 /usr/bin/cat
55a3c8a01000-55a3c8a05000 r-xp 00001000 08:01 1234567 /usr/bin/cat
55a3c8a05000-55a3c8a07000 r--p 00005000 08:01 1234567 /usr/bin/cat
55a3c8a07000-55a3c8a08000 rw-p 00007000 08:01 1234567 /usr/bin/cat
55a3c9b00000-55a3c9b21000 rw-p 00000000 00:00 0 [heap]
7f1a2c000000-7f1a2c1a0000 r--p 00000000 08:01 7654321 /usr/lib/x86_64-linux-gnu/libc.so.6
7f1a2c1a0000-7f1a2c320000 r-xp 001a0000 08:01 7654321 /usr/lib/x86_64-linux-gnu/libc.so.6
7ffd4a300000-7ffd4a321000 rw-p 00000000 00:00 0 [stack]
כל שורה מכילה את העמודות הבאות:
| עמודה | הסבר |
|---|---|
| טווח כתובות | כתובת התחלה וסיום של האזור (בhex) |
| הרשאות | הרשאות האזור: r=קריאה, w=כתיבה, x=הרצה, p=פרטי (private), s=משותף (shared) |
| הסטה - offset | ההסטה בתוך הקובץ שממנו האזור נטען |
| התקן - device | מספר ההתקן שהקובץ נמצא עליו (major:minor) |
| inode | מספר הinode של הקובץ במערכת הקבצים |
| נתיב | הנתיב לקובץ שממנו האזור נטען, או שם מיוחד כמו [heap], [stack] |
שימו לב - אפשר לראות בדיוק איפה הקוד יושב בזיכרון (אזור עם הרשאת r-xp), איפה הנתונים (rw-p), איפה הstack, ואיפה הheap. זה מאוד שימושי לדיבוג ולחקירת אבטחה.
סטטוס התהליך - proc/[pid]/status¶
הקובץ /proc/[pid]/status מכיל מידע כללי על התהליך בפורמט קריא. כל שורה היא key-value:
פלט לדוגמה (חלקי):
Name: cat
State: R (running)
Pid: 5678
PPid: 1234
Uid: 1000 1000 1000 1000
VmSize: 8520 kB
VmRSS: 3200 kB
Threads: 1
השדות החשובים:
- Name - שם התהליך (שם קובץ ההרצה)
- State - מצב התהליך (R=רץ, S=ישן, Z=זומבי, ועוד)
- Pid - מזהה התהליך
- PPid - מזהה תהליך האב
- Uid - מזהה המשתמש שמריץ את התהליך
- VmSize - גודל הזיכרון הוירטואלי הכולל
- VmRSS - כמות הזיכרון הפיזי שהתהליך באמת משתמש בו (Resident Set Size)
- Threads - מספר הthread-ים
מתארי קבצים פתוחים - proc/[pid]/fd/¶
הספריה /proc/[pid]/fd/ מכילה symlink-ים לכל מתארי הקבצים הפתוחים של התהליך. כל symlink נקרא לפי מספר הfd, ומצביע על הקובץ שהfd מייצג.
כזכור מהרצאה 5.5, לכל תהליך יש לפחות שלושה fd-ים פתוחים: 0 (stdin), 1 (stdout), 2 (stderr).
פלט לדוגמה:
lrwx------ 1 user user 64 Mar 1 10:00 0 -> /dev/pts/0
lrwx------ 1 user user 64 Mar 1 10:00 1 -> /dev/pts/0
lrwx------ 1 user user 64 Mar 1 10:00 2 -> /dev/pts/0
lr-x------ 1 user user 64 Mar 1 10:00 3 -> /proc/1234/fd
הכלי lsof שמראה קבצים פתוחים - מאחורי הקלעים פשוט קורא מ-/proc/[pid]/fd/.
שורת הפקודה - proc/[pid]/cmdline¶
הקובץ /proc/[pid]/cmdline מכיל את הארגומנטים של שורת הפקודה שהתהליך הופעל איתה. הארגומנטים מופרדים בתו null (\0) במקום רווח.
קישור לקובץ ההרצה - proc/[pid]/exe¶
הקובץ /proc/[pid]/exe הוא symlink שמצביע על קובץ ההרצה עצמו של התהליך. אפשר להשתמש בו כדי לדעת בדיוק איזה קובץ בינארי רץ:
משתני סביבה - proc/[pid]/environ¶
הקובץ /proc/[pid]/environ מכיל את כל משתני הסביבה של התהליך, מופרדים ב-\0:
proc/self/ - קיצור לתהליך הנוכחי¶
במקום לכתוב /proc/[pid]/ עם הPID שלנו, לינוקס מספקת קיצור נוח: /proc/self/.
זהו symlink שתמיד מצביע על ספריית ה-/proc של התהליך שקורא אותו.
כלומר, אם התוכנית שלנו רצה עם PID 5678, אז /proc/self/ שקול ל-/proc/5678/.
זה מאוד שימושי כשהתוכנית רוצה לקרוא מידע על עצמה בלי לדעת מה הPID שלה.
מידע על המערכת כולה¶
בנוסף למידע על תהליכים, /proc מכיל גם מידע כללי על המערכת:
proc/cpuinfo/ - מידע על המעבד¶
מציג מידע על כל ליבה של המעבד - דגם, מהירות, יצרן, דגלים נתמכים ועוד.
proc/meminfo/ - מידע על הזיכרון¶
מציג סטטיסטיקות זיכרון - כמה זיכרון כולל, כמה פנוי, כמה בcache, וכו'.
proc/version/ - גרסת הקרנל¶
מציג את גרסת הקרנל, את הקומפיילר שאיתו הוא קומפל, ועוד.
proc/uptime/ - זמן פעילות¶
מציג שני מספרים: כמה שניות המערכת פועלת, וכמה שניות המעבדים היו במצב idle.
קריאת proc/ בקוד C¶
מכיוון שהקבצים ב-/proc הם קבצים רגילים מבחינת הממשק, אפשר לקרוא אותם עם open() ו-read() (או fopen() ו-fgets()) כמו כל קובץ אחר. אין צורך בAPI מיוחד.
דוגמה 1 - קריאת סטטוס התהליך¶
תוכנית שקוראת את /proc/self/status ומדפיסה את שם התהליך, הPID, וגודל הזיכרון:
#include <stdio.h>
#include <string.h>
int main() {
FILE *f = fopen("/proc/self/status", "r");
if (f == NULL) {
perror("fopen");
return 1;
}
char line[256];
while (fgets(line, sizeof(line), f)) {
if (strncmp(line, "Name:", 5) == 0 ||
strncmp(line, "Pid:", 4) == 0 ||
strncmp(line, "VmSize:", 7) == 0) {
printf("%s", line);
}
}
fclose(f);
return 0;
}
פלט לדוגמה:
דוגמה 2 - רשימת מתארי הקבצים הפתוחים¶
תוכנית שפותחת כמה קבצים ואז קוראת את /proc/self/fd/ כדי לראות את כל הfd-ים הפתוחים:
#include <stdio.h>
#include <dirent.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
int main() {
/* נפתח כמה קבצים כדי שיהיה מה לראות */
int fd1 = open("/etc/hostname", O_RDONLY);
int fd2 = open("/etc/passwd", O_RDONLY);
printf("fd-ים שנפתחו: %d, %d\n", fd1, fd2);
printf("\nכל הfd-ים הפתוחים:\n");
DIR *dir = opendir("/proc/self/fd");
if (dir == NULL) {
perror("opendir");
return 1;
}
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
if (entry->d_name[0] == '.') {
continue;
}
char link_path[512];
char target[512];
snprintf(link_path, sizeof(link_path), "/proc/self/fd/%s", entry->d_name);
ssize_t len = readlink(link_path, target, sizeof(target) - 1);
if (len != -1) {
target[len] = '\0';
printf(" fd %s -> %s\n", entry->d_name, target);
}
}
closedir(dir);
close(fd1);
close(fd2);
return 0;
}
פלט לדוגמה:
fd-ים שנפתחו: 3, 4
כל הfd-ים הפתוחים:
fd 0 -> /dev/pts/0
fd 1 -> /dev/pts/0
fd 2 -> /dev/pts/0
fd 3 -> /etc/hostname
fd 4 -> /etc/passwd
fd 5 -> /proc/12345/fd
שימו לב שfd 5 הוא הספריה /proc/self/fd/ עצמה - כי גם opendir פותח fd.
דוגמה 3 - הצגת מפת הזיכרון¶
תוכנית שקוראת את /proc/self/maps ומציגה את כל אזורי הזיכרון שממופים לתהליך:
#include <stdio.h>
int main() {
FILE *f = fopen("/proc/self/maps", "r");
if (f == NULL) {
perror("fopen");
return 1;
}
char line[512];
printf("מפת הזיכרון של התהליך:\n");
printf("-------------------------------------------\n");
while (fgets(line, sizeof(line), f)) {
printf("%s", line);
}
fclose(f);
return 0;
}
הערת אבטחה¶
מערכת הקבצים /proc חושפת מידע רגיש על תהליכים - אפשר לראות דרכה מתארי קבצים פתוחים, משתני סביבה (שיכולים להכיל סיסמאות), ומיפויי זיכרון.
בהפצות לינוקס מודרניות, אפשר להגביל את הגישה ל-/proc של תהליכים אחרים באמצעות אופציית ה-hidepid בזמן mount:
- hidepid=0 - ברירת מחדל, כל משתמש רואה את כל התהליכים
- hidepid=1 - משתמשים לא יכולים לגשת לספריות /proc/[pid]/ של תהליכים של משתמשים אחרים
- hidepid=2 - ספריות /proc/[pid]/ של תהליכים של משתמשים אחרים לא מופיעות בכלל
למה זה חשוב?¶
כמעט כל כלי הניטור והדיבוג בלינוקס משתמשים ב-/proc מאחורי הקלעים:
- הפקודה ps - קוראת מ-/proc/[pid]/status ו-/proc/[pid]/cmdline
- הפקודה top / htop - קוראת מ-/proc/[pid]/stat ומ-/proc/meminfo
- הפקודה lsof - קוראת מ-/proc/[pid]/fd/
- הפקודה free - קוראת מ-/proc/meminfo
- הפקודה uname - קוראת מ-/proc/version
ברגע שאתם מבינים את /proc, אתם מבינים מאיפה כל המידע הזה בא - ואתם יכולים לגשת אליו ישירות מהקוד שלכם.