proc文件系統

在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

1 獲取信息

/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

2 進程條目

每個正在運行的進程都在/proc文件系統對應一個文件夾。文件夾名稱就是相應進程的ID。這些文件夾隨着進程的生成而創建,並隨進程的終止而移除。這些文件夾裏的文件都描述着對應進程的信息:緩存

  • cmdline裏是進程的參數表。
  • cwd是一個指向當前進程工做目錄的連接符號。
  • environ包含了進程的運行環境信息。
  • exe是一個指向進程正在執行的可執行文件的連接符號。
  • fd是一個子目錄,裏面都是進程所打開的文件描述符。
  • maps裏都是映射到當前進程的地址空間的文件的信息,例如文件名稱、所映射的內存地址以及這些地址的訪問權限。
  • root是指向進程根目錄的連接符號. 進程根目錄一般就是系統根目錄/。進程在進程根目錄能夠調用超級用戶(chroot)命令或系統調用。
  • stat包含有進程的運行狀態信息和統計數據。這些內容很難讀懂,但容易用代碼解析。
  • statm包含有進程的內存使用信息。
  • status包含有進程的運行狀態信息和統計數據,並且方便調閱。
  • 只有SMP Linux系統會有一個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 vs NULL
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,內存使用以及決定是否捕捉、忽略或阻塞某些信號的位掩碼。

3 硬件信息

/proc/cpuinfo能夠查看CPU信息。

/proc/devices能夠查看設備信息。

/proc/pci能夠查看PCI總線信息。

/proc/tty/driver/serial能夠查看系統串口信息。

4 內核信息

/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

5 文件系統

/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)

6 系通通計

/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;
}
相關文章
相關標籤/搜索