5.9 - מערכת הקבצים proc - הרצאה
הקדמה¶
עד עכשיו למדנו איך לעבוד עם קבצים, processes, מתארי קבצים ועוד.
אבל יש שאלה שעולה - איך אפשר לדעת מה קורה בתוך מערכת ההפעלה? איך כלים כמו ps, top, htop ו-lsof יודעים להראות לנו מידע על processes, זיכרון ומשאבי מערכת?
התשובה היא מערכת הקבצים proc - או בשמה המלא /proc.
לינוקס חושפת מידע על הקרנל ועל processes דרך מערכת קבצים וירטואלית שנקראת /proc. אלה לא קבצים אמיתיים על הדיסק - הקרנל מייצר את התוכן שלהם בזמן אמת כשאנחנו קוראים אותם. כלומר, כל פעם שאנחנו עושים cat על קובץ ב-/proc, הקרנל מרכיב את התוכן באותו רגע ממש.
זה רעיון מאוד אלגנטי - במקום ליצור API מיוחד לקריאת מידע מהקרנל, לינוקס פשוט חושפת את הכל דרך קבצים. ואנחנו כבר יודעים לעבוד עם קבצים.
מידע על processes - proc/[pid]/¶
בתוך /proc יש ספריות שנקראות לפי מספרי PID. כל ספריה כזו מכילה מידע על process מסוים.
למשל, אם יש לנו process עם PID 1234, כל המידע עליו נמצא ב-/proc/1234/.
נעבור על הקבצים החשובים ביותר בספריה הזו.
מיפויי זיכרון - proc/[pid]/maps¶
הקובץ /proc/[pid]/maps מכיל את כל מיפויי הזיכרון של הprocess - כלומר, אילו אזורי זיכרון ממופים לprocess, עם איזה הרשאות, ומאיפה הם באו.
זוכרים את paging מפרק 2? אלה בעצם הentries של הpage table שדיברנו עליהן! כל שורה מייצגת אזור זיכרון וירטואלי שממופה לpages פיזיים.
נריץ לדוגמה:
פלט לדוגמה:
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. זה מאוד שימושי לדיבוג ולחקירת אבטחה.
סטטוס הprocess - proc/[pid]/status¶
הקובץ /proc/[pid]/status מכיל מידע כללי על הprocess בפורמט קריא. כל שורה היא 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 - שם הprocess (שם קובץ ההרצה)
- State - מצב הprocess (R=רץ, S=ישן, Z=זומבי, ועוד)
- Pid - מזהה הprocess
- PPid - מזהה process האב
- Uid - מזהה המשתמש שמריץ את הprocess
- VmSize - גודל הזיכרון הוירטואלי הכולל
- VmRSS - כמות הזיכרון הפיזי שהprocess באמת משתמש בו (Resident Set Size)
- Threads - מספר הthread-ים
מתארי קבצים פתוחים - proc/[pid]/fd/¶
הספריה /proc/[pid]/fd/ מכילה symlink-ים לכל מתארי הקבצים הפתוחים של הprocess. כל symlink נקרא לפי מספר הfd, ומצביע על הקובץ שהfd מייצג.
כזכור מהרצאה 5.5, לכל process יש לפחות שלושה 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 מכיל את הארגומנטים של שורת הפקודה שהprocess הופעל איתה. הארגומנטים מופרדים בתו null (\0) במקום רווח.
קישור לקובץ ההרצה - proc/[pid]/exe¶
הקובץ /proc/[pid]/exe הוא symlink שמצביע על קובץ ההרצה עצמו של הprocess. אפשר להשתמש בו כדי לדעת בדיוק איזה קובץ בינארי רץ:
משתני סביבה - proc/[pid]/environ¶
הקובץ /proc/[pid]/environ מכיל את כל משתני הסביבה של הprocess, מופרדים ב-\0:
proc/self/ - קיצור לprocess הנוכחי¶
במקום לכתוב /proc/[pid]/ עם הPID שלנו, לינוקס מספקת קיצור נוח: /proc/self/.
זהו symlink שתמיד מצביע על ספריית ה-/proc של הprocess שקורא אותו.
כלומר, אם התוכנית שלנו רצה עם PID 5678, אז /proc/self/ שקול ל-/proc/5678/.
זה מאוד שימושי כשהתוכנית רוצה לקרוא מידע על עצמה בלי לדעת מה הPID שלה.
מידע על המערכת כולה¶
בנוסף למידע על processes, /proc מכיל גם מידע כללי על המערכת:
proc/cpuinfo/ - מידע על המעבד¶
מציג מידע על כל ליבה של המעבד - דגם, מהירות, יצרן, דגלים נתמכים ועוד.
proc/meminfo/ - מידע על הזיכרון¶
מציג סטטיסטיקות זיכרון - כמה זיכרון כולל, כמה פנוי, כמה בcache, וכו'.
proc/version/ - גרסת הקרנל¶
מציג את גרסת הקרנל, את הקומפיילר שאיתו הוא קומפל, ועוד.
proc/uptime/ - זמן פעילות¶
מציג שני מספרים: כמה שניות המערכת פועלת, וכמה שניות המעבדים היו במצב idle.
קריאת proc/ בקוד C¶
מכיוון שהקבצים ב-/proc הם קבצים רגילים מבחינת הממשק, אפשר לקרוא אותם עם open() ו-read() (או fopen() ו-fgets()) כמו כל קובץ אחר. אין צורך בAPI מיוחד.
דוגמה 1 - קריאת סטטוס הprocess¶
תוכנית שקוראת את /proc/self/status ומדפיסה את שם הprocess, ה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() {
/* open a few files so there's something to see */
int fd1 = open("/etc/hostname", O_RDONLY);
int fd2 = open("/etc/passwd", O_RDONLY);
printf("fds opened: %d, %d\n", fd1, fd2);
printf("\nAll open fds:\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;
}
פלט לדוגמה:
fds opened: 3, 4
All open fds:
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 ומציגה את כל אזורי הזיכרון שממופים לprocess:
#include <stdio.h>
int main() {
FILE *f = fopen("/proc/self/maps", "r");
if (f == NULL) {
perror("fopen");
return 1;
}
char line[512];
printf("Memory map of the process:\n");
printf("-------------------------------------------\n");
while (fgets(line, sizeof(line), f)) {
printf("%s", line);
}
fclose(f);
return 0;
}
הערת אבטחה¶
מערכת הקבצים /proc חושפת מידע רגיש על processes - אפשר לראות דרכה מתארי קבצים פתוחים, משתני סביבה (שיכולים להכיל סיסמאות), ומיפויי זיכרון.
בהפצות לינוקס מודרניות, אפשר להגביל את הגישה ל-/proc של processes אחרים באמצעות אופציית ה-hidepid בזמן mount:
- hidepid=0 - ברירת מחדל, כל משתמש רואה את כל הprocesses
- hidepid=1 - משתמשים לא יכולים לגשת לספריות /proc/[pid]/ של processes של משתמשים אחרים
- hidepid=2 - ספריות /proc/[pid]/ של processes של משתמשים אחרים לא מופיעות בכלל
למה זה חשוב?¶
כמעט כל כלי הניטור והדיבוג בלינוקס משתמשים ב-/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, אתם מבינים מאיפה כל המידע הזה בא - ואתם יכולים לגשת אליו ישירות מהקוד שלכם.