在shell終端裏不帶任何參數,直接運行mount
命令能夠顯示正在掛載的文件系統。其中有這麼一行node
none on /proc type proc (rw)
這就是/proc
文件系統。第一個域顯示none
,說明這個文件沒有和任何硬件設備掛鉤。/proc
文件系統其實是一個通向Linux內核的窗口,看起來像一個可以向內核提供參數、數據結構、統計信息等的文件。/proc
文件系統的內容是隨內核運行變化的。用戶進程還能夠經過改變/proc
文件系統內容來改變內核的設置。linux
在Linux手冊的proc(5)
項裏查看/proc
文件系統的詳細介紹。其源碼位於/usr/src/linux/fs/proc/
。docker
/proc
文件系統中的信息大多都是可讀的,也很容易解析。例如,/proc/cpuinfo
含有系統CPU的信息。shell
$ cat /proc/cpuinfo processor : 0 vendor_id : GenuineIntel cpu family : 6 model : 61 model name : Intel(R) Core(TM) i5-5250U CPU @ 1.60GHz stepping : 4 cpu MHz : 1599.999 cache size : 3072 KB physical id : 0 siblings : 1 core id : 0 cpu cores : 1 apicid : 0 initial apicid : 0 fpu : yes fpu_exception : yes cpuid level : 20 wp : yes flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 syscall nx rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc pni pclmulqdq monitor ssse3 cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx rdrand hypervisor lahf_lm abm 3dnowprefetch rdseed bugs : bogomips : 3199.99 clflush size : 64 cache_alignment : 64 address sizes : 39 bits physical, 48 bits virtual power management:
在C語言中提取文件信息的一個簡單辦法是:先將文件讀取到緩存中,而後調用sscanf
函數在內存中解析數據。vim
#include <stdio.h> #include <string.h> /* Returns the clock speed of the system’s CPU in MHz, as reported by /proc/cpuinfo. * On a multiprocessor machine, returns the speed of the first CPU. * On error returns zero. */ float get_cpu_clock_speed () { FILE* fp; char buffer[1024]; size_t bytes_read; char* match; float clock_speed; /* Read the entire contents of /proc/cpuinfo into the buffer. */ fp = fopen (「/proc/cpuinfo」, 「r」); bytes_read = fread (buffer, 1, sizeof (buffer), fp); fclose (fp); /* Bail if read failed or if buffer isn’t big enough. */ if (bytes_read == 0 || bytes_read == sizeof (buffer)) return 0; /* NUL-terminate the text. */ buffer[bytes_read] = ‘\0’; /* Locate the line that starts with 「cpu MHz」. */ match = strstr (buffer, 「cpu MHz」); if (match == NULL) return 0; /* Parse the line to extract the clock speed. */ sscanf (match, 「cpu MHz : %f」, &clock_speed); return clock_speed; } int main () { printf (「CPU clock speed: %4.0f MHz\n」, get_cpu_clock_speed ()); return 0; }
不一樣版本Linux的/proc
文件系統不盡相同。編寫跨版本程序時必定要考慮到這個問題。api
每個正在運行的進程都在/proc
文件系統對應一個文件夾。文件夾名稱就是相應進程的ID。這些文件夾隨着進程的生成而創建,並隨進程的終止而移除。這些文件夾裏的文件都描述着對應進程的信息:緩存
cmdline
裏是進程的參數表。cwd
是一個指向當前進程工做目錄的連接符號。environ
包含了進程的運行環境信息。exe
是一個指向進程正在執行的可執行文件的連接符號。fd
是一個子目錄,裏面都是進程所打開的文件描述符。maps
裏都是映射到當前進程的地址空間的文件的信息,例如文件名稱、所映射的內存地址以及這些地址的訪問權限。root
是指向進程根目錄的連接符號. 進程根目錄一般就是系統根目錄/
。進程在進程根目錄能夠調用超級用戶(chroot)命令或系統調用。stat
包含有進程的運行狀態信息和統計數據。這些內容很難讀懂,但容易用代碼解析。statm
包含有進程的內存使用信息。status
包含有進程的運行狀態信息和統計數據,並且方便調閱。cpu
條目,裏面是「breakdown of process time (user and system) by CPU「。出於安全考慮,其中一些條目只有進程全部者或超級用戶具備訪問權限。安全
/proc/self
每條進程調用/proc/self
都會指向各自的/proc/<pid>
目錄。bash
下面代碼展現瞭如何利用/proc
文件系統實現getpid
函數,即獲取調用進程的PID。數據結構
#include <stdio.h> #include <sys/types.h> #include <unistd.h> /* Returns the process ID of the calling processes, as determined from the /proc/self symlink. */ pid_t get_pid_from_proc_self () { char target[32]; int pid; /* Read the target of the symbolic link. */ readlink (「/proc/self」, target, sizeof (target)); /* The target is a directory named for the process ID. */ sscanf (target, 「%d」, &pid); return (pid_t) pid; } int main () { printf (「/proc/self reports process id %d\n」, (int) get_pid_from_proc_self ()); printf (「getpid() reports process id %d\n」, (int) getpid ()); return 0; }
每個進程的/proc/<pid>/cmdline
裏有進程的參數表。裏面的參數都是單個字符,並用NUL
隔開。不過問題是,大部分字符串函數沒法處理字符串中間夾雜的NUL
。
NUL
vsNULL
NUL
是一個字符,整數值爲0。而NULL
是一個值爲0的指針。C語言中,全部字符串的結尾都是
NUL
。因此字符串Hello, world!
雖然只有13個字符,但在C語言眼裏中,這裏有14個字符。由於還有一個NUL
在感嘆號以後。在C語言中,NUL
被定義爲\0
或(char) 0
。
NULL
是一個不指向內存區域的指針,也就是空指針。在C語言中,NULL
被定義爲((void*)0)
。
下面程序可以讀取指定PID的進程的參數表。
#include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> /* Prints the argument list, one argument to a line, of the process given by PID. */ void print_process_arg_list (pid_t pid) { int fd; char filename[24]; char arg_list[1024]; size_t length; char* next_arg; /* Generate the name of the cmdline file for the process. */ snprintf (filename, sizeof (filename), 「/proc/%d/cmdline」, (int) pid); /* Read the contents of the file. */ fd = open (filename, O_RDONLY); length = read (fd, arg_list, sizeof (arg_list)); close (fd); /* read does not NUL-terminate the buffer, so do it here. */ arg_list[length] = ‘\0’; /* Loop over arguments. Arguments are separated by NULs. */ next_arg = arg_list; while (next_arg < arg_list + length) { /* Print the argument. Each is NUL-terminated, * so just treat it like an ordinary string. */ printf (「%s\n」, next_arg); /* Advance to the next argument. Since each argument is NUL-terminated, * strlen counts the length of the next argument, not the entire argument list. */ next_arg += strlen (next_arg) + 1; } } int main (int argc, char* argv[]) { pid_t pid = (pid_t) atoi (argv[1]); print_process_arg_list (pid); return 0; }
使用:
$ ps 372 PID TTY STAT TIME COMMAND 372 ? S 0:00 syslogd -m 0 $ ./print-arg-list 372 syslogd -m 0
environ
裏包含了進程的運行環境信息。和cmdline
同樣,environ
裏的環境信息由NUL
分隔。
#include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> /* Prints the environment, one environment variable to a line, of the process given by PID. */ void print_process_environment (pid_t pid) { int fd; char filename[24]; char environment[8192]; size_t length; char* next_var; /* Generate the name of the environ file for the process. */ snprintf (filename, sizeof (filename), 「/proc/%d/environ」, (int) pid); /* Read the contents of the file. */ fd = open (filename, O_RDONLY); length = read (fd, environment, sizeof (environment)); close (fd); /* read does not NUL-terminate the buffer, so do it here. */ environment[length] = ‘\0’; /* Loop over variables. Variables are separated by NULs. */ next_var = environment; while (next_var < environment + length) { /* Print the variable. Each is NUL-terminated, so just treat it like an ordinary string. */ printf (「%s\n」, next_var); /* Advance to the next variable. Since each variable is * NUL-terminated, strlen counts the length of the next variable, * not the entire variable list. */ next_var += strlen (next_var) + 1; } } int main (int argc, char* argv[]) { pid_t pid = (pid_t) atoi (argv[1]); print_process_environment (pid); return 0; }
上面代碼展現瞭如何讀取並解析environ
裏的信息。
儘管,通常來說shell命令的第一個參數就是進程的執行文件,可是有時其參數能夠可能改變實際運行的執行文件。所以,/proc/<pid>/exe
中的信息更爲可靠。
下面代碼能夠獲取進程的執行文件目錄。
#include <limits.h> #include <stdio.h> #include <string.h> #include <unistd.h> /* Finds the path containing the currently running program executable. * The path is placed into BUFFER, which is of length LEN. * Returns the number of characters in the path, or -1 on error. */ size_t get_executable_path (char* buffer, size_t len) { char* path_end; /* Read the target of /proc/self/exe. */ if (readlink (「/proc/self/exe」, buffer, len) <= 0) return -1; /* Find the last occurrence of a forward slash, the path separator. */ path_end = strrchr (buffer, ‘/’); if (path_end == NULL) return -1; /* Advance to the character past the last slash. */ ++path_end; /* Obtain the directory containing the program * by truncating the path after the last slash. */ *path_end = ‘\0’; /* The length of the path is the number of characters up through the last slash. */ return (size_t) (path_end - buffer); } int main () { char path[PATH_MAX]; get_executable_path (path, sizeof (path)); printf (「this program is in the directory %s\n」, path); return 0; }
fd
子目錄裏包含了指向進程所打開文件的描述符的連接符號。對這些連接符號執行讀寫操做也就至關於對相應文件的讀寫操做。目錄裏每一個子項的名稱都是文件描述符打開的序號。
$ vim & [1] 16 $ ps axw PID TTY STAT TIME COMMAND 1 ? Ss 0:00 /bin/bash 16 ? T 0:00 vim 19 ? R+ 0:00 ps axw [1]+ Stopped vim $ ls -l /proc/16/fd total 0 lrwx------ 1 root root 64 May 18 05:49 0 -> /0 lrwx------ 1 root root 64 May 18 05:49 1 -> /0 lrwx------ 1 root root 64 May 18 05:48 2 -> /0
根據以前的內容能夠知道,進程的文件描述符0``1``2
分別對應於進程本身的stdin、stdout、stderr。
因此向/proc/16/fd/1
寫入的文本,實際上能夠在屏幕中顯示出來。
$ echo 「Hello, world.」 >> /proc/16/fd/1
若是進程繼續打開文件,對應的文件描述符也會出如今fd
目錄中。
#include <fcntl.h> #include <stdio.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> int main (int argc, char* argv[]) { const char* const filename = argv[1]; int fd = open (filename, O_RDONLY); printf (「in process %d, file descriptor %d is open to %s\n」, (int) getpid (), (int) fd, filename); while (1); return 0; }
將上述代碼編譯爲open-and-spin。在終端中執行程序。
$ ./open-and-spin /etc/fstab in process 2570, file descriptor 3 is open to /etc/fstab
在另外一終端裏查看fd
目錄。
% ls -l /proc/2570/fd total 0 lrwx------ 1 samuel samuel 64 Jan 30 01:30 0 -> /dev/pts/2 lrwx------ 1 samuel samuel 64 Jan 30 01:30 1 -> /dev/pts/2 lrwx------ 1 samuel samuel 64 Jan 30 01:30 2 -> /dev/pts/2 lr-x------ 1 samuel samuel 64 Jan 30 01:30 3 -> /etc/fstab
注意最後一項,最新打開的文件描述符名稱爲3。
statm
裏面含有7個數字,分別用空格分開。每一項都是進程某些內容佔用內存的狀況統計。
status
項裏是一些具備可讀性的進程狀況統計。主要有進程ID,父進程ID,真實或有效的用戶ID、組ID,內存使用以及決定是否捕捉、忽略或阻塞某些信號的位掩碼。
/proc/cpuinfo
能夠查看CPU信息。
/proc/devices
能夠查看設備信息。
/proc/pci
能夠查看PCI總線信息。
/proc/tty/driver/serial
能夠查看系統串口信息。
/proc/version
查看內核版本信息。
$ cat /proc/version Linux version 4.1.13-boot2docker (root@11aafb97cfeb) (gcc version 4.9.2 (Debian4.9.2-10) ) #1 SMP Fri Nov 20 19:05:50 UTC 2015 $ cat /proc/sys/kernel/ostype Linux $ cat /proc/sys/kernel/osrelease 4.1.13-boot2docker $ cat /proc/sys/kernel/version #1 SMP Fri Nov 20 19:05:50 UTC 2015
/proc/sys/kernel/hostname
和/proc/sys/kernel/domainname
分別記錄的系統的主機名和域名。
/proc/meminfo
理由系統的內存使用狀況統計。
$ cat /proc/meminfo MemTotal: 2050728 kB MemFree: 1825280 kB MemAvailable: 1849244 kB Buffers: 18684 kB Cached: 139328 kB SwapCached: 0 kB Active: 85608 kB Inactive: 97132 kB Active(anon): 58724 kB Inactive(anon): 92996 kB Active(file): 26884 kB Inactive(file): 4136 kB Unevictable: 0 kB Mlocked: 0 kB SwapTotal: 1448436 kB SwapFree: 1448436 kB Dirty: 0 kB Writeback: 0 kB AnonPages: 24768 kB Mapped: 30524 kB Shmem: 126996 kB Slab: 26008 kB SReclaimable: 13964 kB SUnreclaim: 12044 kB KernelStack: 2080 kB PageTables: 836 kB NFS_Unstable: 0 kB Bounce: 0 kB WritebackTmp: 0 kB CommitLimit: 2473800 kB Committed_AS: 244360 kB VmallocTotal: 34359738367 kB VmallocUsed: 9960 kB VmallocChunk: 34359698060 kB AnonHugePages: 14336 kB HugePages_Total: 0 HugePages_Free: 0 HugePages_Rsvd: 0 HugePages_Surp: 0 Hugepagesize: 2048 kB DirectMap4k: 49088 kB DirectMap2M: 2048000 kB
/proc/filesystems
裏是當前掛載到Linux內核的文件系統列表。若是是動態地掛載或卸載的文件系統,不必定可以在這裏看到。
/proc/mounts
是當前掛載到Linux內核的文件系統的詳情列表。包含有掛載描述符、掛載設備、掛載點以及其餘信息。
$ cat /proc/filesystems nodev sysfs nodev rootfs nodev ramfs nodev bdev nodev proc nodev cgroup nodev cpuset nodev tmpfs nodev devtmpfs nodev debugfs nodev securityfs nodev sockfs nodev pipefs nodev devpts ext3 ext2 ext4 nodev hugetlbfs vfat nodev ecryptfs fuseblk nodev fuse nodev fusectl nodev pstore nodev mqueue nodev binfmt_misc nodev mtd_inodefs $ cat /proc/mounts sysfs /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0 proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0 udev /dev devtmpfs rw,relatime,size=621336k,nr_inodes=155334,mode=755 0 0 devpts /dev/pts devpts rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000 0 0 tmpfs /run tmpfs rw,nosuid,noexec,relatime,size=127248k,mode=755 0 0 /dev/disk/by-uuid/8ce1d249-5033-42c5-bb7f-da454b447e60 / ext4 rw,relatime,errors=remount-ro,data=ordered 0 0 none /sys/fs/cgroup tmpfs rw,relatime,size=4k,mode=755 0 0 none /sys/fs/fuse/connections fusectl rw,relatime 0 0 none /sys/kernel/debug debugfs rw,relatime 0 0 none /sys/kernel/security securityfs rw,relatime 0 0 none /run/lock tmpfs rw,nosuid,nodev,noexec,relatime,size=5120k 0 0 none /run/shm tmpfs rw,nosuid,nodev,relatime 0 0 none /run/user tmpfs rw,nosuid,nodev,noexec,relatime,size=102400k,mode=755 0 0 none /sys/fs/pstore pstore rw,relatime 0 0 binfmt_misc /proc/sys/fs/binfmt_misc binfmt_misc rw,nosuid,nodev,noexec,relatime 0 0 systemd /sys/fs/cgroup/systemd cgroup rw,nosuid,nodev,noexec,relatime,name=systemd 0 0 gvfsd-fuse /run/user/1000/gvfs fuse.gvfsd-fuse rw,nosuid,nodev,relatime,user_id=1000,group_id=1000 0 0
/proc/locks
顯示了系統所知的文件鎖。文件所的使用可見下章。(P164)
/proc/loadavg
展現了系統的運行信息。其前三個數字分別是系統在過去1分鐘、5分鐘、15分鐘內活躍的運行進程;第四個數字是系統當前可調度進程,而沒有被阻塞的進程;第五個數字是系統的運行進程總計;第六個是最後一次運行進程ID。
/proc/uptime
所顯示的分別是系統啓動後的運行時間,系統閒置時間。uptime
命令和sysinfo
函數也一樣能夠查看系統的時間信息。
#include <stdio.h> /* Summarize a duration of time to standard output. * TIME is the amount of time, in seconds, and LABEL is a short descriptive label. */ void print_time (char* label, long time) { /* Conversion constants. */ const long minute = 60; const long hour = minute * 60; const long day = hour * 24; /* Produce output. */ printf (「%s: %ld days, %ld:%02ld:%02ld\n」, label, time / day, (time % day) / hour, (time % hour) / minute, time % minute); } int main () { FILE* fp; double uptime, idle_time; /* Read the system uptime and accumulated idle time from /proc/uptime. */ fp = fopen (「/proc/uptime」, 「r」); fscanf (fp, 「%lf %lf\n」, &uptime, &idle_time); fclose (fp); /* Summarize it. */ print_time (「uptime 「, (long) uptime); print_time (「idle time」, (long) idle_time); return 0; }