這裡主要介紹/proc偽文件系統及uname()函數來獲取系統或進程的一些信息。
/proc文件系統介紹在早期的UNIX發行版中,並不能很容易的分析內核的一些屬性,並且很難回答以下問題:
系統有多少進程正在運行,並且誰擁有這些進程?
一個進程都打開了哪些文件?
哪些文件目前被鎖住了,並且哪些進程擁有這些文件鎖?
系統有哪些套接字正在使用?
一些早期的UNIX發行版解決該問題是允許有權限的程序進入內核的內存空間的數據結構中。這樣的解決辦法有諸多不便。然而,最蛋疼的是它需要程序員了解的內核中的數據結構,並且這些數據結構在不同的內核版本中會略有不同,需要程序員根據實際所依賴的內核進行代碼的重寫。
為了提供便捷的訪問內核信息,很多現代的UNIX發行版提供了/proc虛擬文件系統。該文件系統駐留在/proc目錄下,該目錄下有很多文件以暴露內核信息,方便進程來從中讀取信息,並在某種情形下還可以使用系統的I/O調用來修改這些信息。說/proc文件系統是虛的,主要是因為它下面的文件及子目錄並不占用硬盤空間。相反是內核在進程訪問這些信息的時候創建的。
獲取某個進程的一些信息對於每一個系統中進程來說,內核提供了一個在/proc中相應的目錄,該目錄一般是進程的PID。在/proc/PID目錄中有一些文件及子目錄等,這些文件及目錄都包含了該進程的相關信息。在Gun/Linux系統中,我們最熟悉的1號進程就是init進程,與其相關的進程信息在/proc/1目錄下。
在/proc/1目錄下,有一個名為status的文件,status文件包含了關於該進程的一些詳盡信息,接下來我們可以看看裡面是什麼東東,
[root@lavenliu ~]# cat /proc/1/status Name: init State: S (sleeping) Tgid: 1 Pid: 1 PPid: 0 TracerPid: 0 Uid: 0 0 0 0 Gid: 0 0 0 0 Utrace: 0 FDSize: 64 Groups: VmPeak: 19296 kB VmSize: 19232 kB VmLck: 0 kB VmHWM: 1588 kB VmRSS: 1588 kB VmData: 200 kB VmStk: 88 kB VmExe: 140 kB VmLib: 2348 kB VmPTE: 56 kB VmSwap: 0 kB Threads: 1 SigQ: 0/3878 SigPnd: 0000000000000000 ShdPnd: 0000000000000000 SigBlk: 0000000000000000 SigIgn: 0000000000001000 SigCgt: 00000001a0016623 CapInh: 0000000000000000 CapPrm: ffffffffffffffff CapEff: fffffffffffffeff CapBnd: ffffffffffffffff Cpus_allowed: 3 Cpus_allowed_list: 0-1 Mems_allowed: 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001 Mems_allowed_list: 0 voluntary_ctxt_switches: 577 nonvoluntary_ctxt_switches: 69下面列出/proc/PID目錄下的一些其他文件,如下:
文件說明cmdline以'\0'結尾的命令行參數cwd指向當前工作目錄的軟連接environ環境變量列表,形如:NAME=value形式exe執行進程可執行文件的軟連接fd該目錄包含進程已打開的文件描述符,軟鏈接至打開的文件maps內存映射mem進程的虛擬內存mounts進程使用的掛載點root軟鏈接至根目錄status進程的一些信息(如:進程ID、內存使用情況、信號等)task該目錄下是進程的線程目錄,每個線程一個子目錄/proc/PID/fd目錄介紹
/proc/PID/fd目錄中包含了該PID進程打開的每一個文件描述符的軟鏈接。每一個軟鏈接的名字都與該進程打開的文件描述符是一致的。舉例來說,/proc/1990/1這個軟鏈接指向該1990進程的標准輸出。
任何一個進程都很方便地訪問它自己的/proc/PID目錄通過使用/proc/self軟鏈接。
線程:/proc/PID/task目錄Linux內核2.4版本增加了線程組的概念以符合POSIX標准的線程模型。由於線程組裡的線程的某些線程屬性不同,所以Linux2.4內核版本在/proc/PID目錄中增加了task子目錄。對於進程的每個線程都對應一個目錄,每個目錄都是以線程ID命名的,形如/proc/PID/task/TID形式,TID為進程的一個線程。比如我們看一下PID為1287的進程的線程,它有如下的線程在運行,這些目錄名稱就是1287進程的線程ID(可以使用gettid()來獲取線程的TID)
ls /proc/1287/task 1287 1297 1343 1528 1530 1680 1695 1776 1778 1863 1296 1342 1527 1529 1679 1694 1775 1777 1862在每一個/proc/PID/task/TID子目錄下也是一系列的文件和目錄,與/proc/PID目錄下的文件看起來很像。由於線程間共享了很多屬性,這些目錄下的文件所包含的信息是一樣的。既然這些線程間都共享大量的相同屬性,究竟是什麼然它們得以區別於其他線程呢?在/proc/PID/task/TID/status文件中包含的信息可以區分於該線程組中的其他線程,這些信息有State,Pid,SigPnd,SigBlk,CapInh,CapPrm,CapEff和CapBnd等信息來區分於其他線程。如,
[root@mydevops task]# cat 1287/status Name: salt-master State: S (sleeping) Tgid: 1287 Pid: 1287 PPid: 1282 TracerPid: 0 Uid: 0 0 0 0 Gid: 0 0 0 0 Utrace: 0 FDSize: 128 Groups: VmPeak: 1140892 kB VmSize: 1075356 kB VmLck: 0 kB VmHWM: 26800 kB VmRSS: 26800 kB VmData: 797808 kB VmStk: 260 kB VmExe: 4 kB VmLib: 11504 kB VmPTE: 520 kB VmSwap: 0 kB Threads: 19 SigQ: 0/3878 SigPnd: 0000000000000000 ShdPnd: 0000000000000000 SigBlk: 0000000000000000 SigIgn: 0000000001001000 SigCgt: 0000000180000a02 CapInh: 0000000000000000 CapPrm: ffffffffffffffff CapEff: ffffffffffffffff CapBnd: ffffffffffffffff Cpus_allowed: 3 Cpus_allowed_list: 0-1 Mems_allowed: 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001 Mems_allowed_list: 0 voluntary_ctxt_switches: 437 nonvoluntary_ctxt_switches: 256 [root@mydevops task]# cat 1297/status Name: salt-master State: S (sleeping) Tgid: 1287 Pid: 1297 PPid: 1282 TracerPid: 0 Uid: 0 0 0 0 Gid: 0 0 0 0 Utrace: 0 FDSize: 128 Groups: VmPeak: 1140892 kB VmSize: 1075356 kB VmLck: 0 kB VmHWM: 26800 kB VmRSS: 26800 kB VmData: 797808 kB VmStk: 260 kB VmExe: 4 kB VmLib: 11504 kB VmPTE: 520 kB VmSwap: 0 kB Threads: 19 SigQ: 0/3878 SigPnd: 0000000000000000 ShdPnd: 0000000000000000 SigBlk: fffffffe7ffbfeff SigIgn: 0000000001001000 SigCgt: 0000000180000a02 CapInh: 0000000000000000 CapPrm: ffffffffffffffff CapEff: ffffffffffffffff CapBnd: ffffffffffffffff Cpus_allowed: 3 Cpus_allowed_list: 0-1 Mems_allowed: 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001 Mems_allowed_list: 0 voluntary_ctxt_switches: 49 nonvoluntary_ctxt_switches: 7
/proc目錄下的系統信息在/proc目錄下有一些文件及目錄用來提供操作系統級別的信息。這些文件及目錄如下表所示:
目錄提供的信息/proc一些系統信息/proc/net網絡及套接字的狀態信息/proc/sys/fs與文件系統相關的設置/proc/sys/kernel與內核相關的一些設置/proc/sys/net網絡與套接字相關的設置/proc/sys/vm內存管理相關的設置/proc/sysvipcSystemV IPC相關的信息訪問/proc下面的文件
對於/proc目錄下面的很多文件,我們可以通過shell腳本來輕松地訪問(也可以使用Python與Perl等腳本語言來訪問之),如
[root@mydevops ~]# cat /proc/sys/kernel/pid_max 32768 [root@mydevops ~]# echo 100000 > /proc/sys/kernel/pid_max [root@mydevops ~]# cat /proc/sys/kernel/pid_max 100000 [root@mydevops ~]#/proc下的文件同樣可以被程序通過使用一般的I/O系統調用來訪問。程序使用I/O系統調用的方式訪問時有如下的限制:/proc下的一些文件是只讀的;那就是說它們的存在只是顯示內核的信息,並不能用來修改內核的信息。這些限制同樣適用於/proc/PID目錄下的文件。
/proc下的一些文件對文件的所有者是可以只讀的(或者是一個已授權的進程)。例如,/proc/PID目錄下的所有文件通常是擁有該進程的用戶所擁有,並且這些文件(如/proc/PID/environ)通常是授權只讀權限給文件的所有者。
除了/proc/PID子目錄下的文件,/proc下的大部分文件的擁有者是root用戶,通常這些文件有的是只可以通過root用戶來修改的。
訪問/proc/PID目錄下的文件/proc/PID目錄下的文件是經常變動的。當一個進程啟動時,相應的在/proc目錄下會創建以該進程ID的目錄;又當該進程終止時,該/proc/PID目錄就會消失。
接下來,我們寫一個簡單的程序,來更新/proc/sys/kernel/pid_max文件,該程序需要從命令行中讀取一個參數,並更新原有的數值;如果不提供參數,則獲取原先的數值。程序如下:
# cat mypid_max.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#define MAX_LINE 100
int main(int argc, char *argv[])
{
int fd;
char line[MAX_LINE];
ssize_t n;
fd = open("/proc/sys/kernel/pid_max", (argc > 1) ? O_RDWR : O_RDONLY);
if (fd == -1) {
perror("open");
exit(1);
}
n = read(fd, line, MAX_LINE);
if (n == -1) {
perror("read");
exit(1);
}
if (argc > 1) {
printf("Old value: ");
}
printf("%.*s", (int) n, line);
if (argc > 1) {
if (lseek(fd, 0, SEEK_SET) == -1) {
perror("lseek");
exit(1);
}
if (write(fd, argv[1], strlen(argv[1])) != strlen(argv[1])) {
perror("write() failed");
exit(1);
}
system("echo /proc/sys/kernel/pid_max now contains "
"`cat /proc/sys/kernel/pid_max`");
}
exit(EXIT_SUCCESS);
}編譯並運行:gcc -o mypid_max mypid_max.c [root@mydevops sysinfo]# ./mypid_max 100000 [root@mydevops sysinfo]# ./mypid_max 1024 Old value: 100000 /proc/sys/kernel/pid_max now contains 1024 [root@mydevops sysinfo]# ./mypid_max 1024系統鑒別:uname()
uname()系統調用通常會返回一些系統相關的信息。使用的結構體為utsbuf。函數原型為:
#include <sys/utsname.h>
int uname(struct utsname *utsbuf);
Returns 0 on success, or –1 on errorutsbuf參數是一個指向utsname結構體的指針,它的定義如下:#define _UTSNAME_LENGTH 65
struct utsname {
char sysname[_UTSNAME_LENGTH]; /* Implementation name */
char nodename[_UTSNAME_LENGTH]; /* Node name on network */
char release[_UTSNAME_LENGTH]; /* Implementation release level */
char version[_UTSNAME_LENGTH]; /* Release version level */
char machine[_UTSNAME_LENGTH]; /* Hardware on which system
is running */
#ifdef _GNU_SOURCE /* Following is Linux-specific */
char domainname[_UTSNAME_LENGTH]; /* NIS domain name of host */
#endif
};在CentOS6.5 64位系統上,可以在/proc/sys/kernel目錄中找到這些相關的信息。可以通過訪問/proc/sys/kernel/hostname,/proc/sys/kernel/osrelease,/proc/sys/kernel/version這三個文件來獲取這些信息。這些信息都是只讀的,我們可以看看這幾個文件的內容:
[root@mydevops sysinfo]# cat /proc/sys/kernel/hostname mydevops [root@mydevops sysinfo]# cat /proc/sys/kernel/osrelease 2.6.32-573.18.1.el6.x86_64 [root@mydevops sysinfo]# cat /proc/sys/kernel/version #1 SMP Tue Feb 9 22:46:17 UTC 2016另外一個文件/proc/version包含了上面三個文件的所有信息,並且還有一些關於內核編譯相關的信息(諸如:是誰進行的內核編譯工作,使用的是什麼機器進行編譯的,使用的GCC版本是多少等附加信息)。我們可以看看/proc/version文件的內容:
[root@mydevops sysinfo]# cat /proc/version Linux version 2.6.32-573.18.1.el6.x86_64 (mockbuild@c6b8.bsys.dev.centos.org) (gcc version 4.4.7 20120313 (Red Hat 4.4.7-16) (GCC) ) #1 SMP Tue Feb 9 22:46:17 UTC 2016utsname結構體中sysname,release,version與machine字段由內核自動設置。接下來看看如何使用uname()函數的使用,代碼如下:
[root@mydevops sysinfo]# cat my_uname.c
#ifdef __linux__
#define _GNU_SOURCE
#endif
#include <stdio.h>
#include <stdlib.h>
#include <sys/utsname.h>
int main(int argc, char *argv[])
{
struct utsname uts;
if (uname(&uts) == -1) {
perror("uname");
exit(1);
}
printf("Node name: %s\n", uts.nodename);
printf("System name: %s\n", uts.sysname);
printf("Release: %s\n", uts.release);
printf("Version %s\n", uts.version);
printf("Machine: %s\n", uts.machine);
#ifdef _GNU_SOURCE
printf("Domain name: %s\n", uts.domainname);
#endif
return 0;
}編譯並運行該程序,如下:[root@mydevops sysinfo]# gcc -o my_uname my_uname.c [root@mydevops sysinfo]# ./my_uname Node name: mydevops System name: Linux Release: 2.6.32-573.18.1.el6.x86_64 Version #1 SMP Tue Feb 9 22:46:17 UTC 2016 Machine: x86_64 Domain name: (none)nodename字段是使用sethostname()系統調用作為其返回值的。domainname字段是使用setdomainname()系統調用作為其返回值的,它是該主機的NIS(Network Information Services)域名。我們可以使用hostname及domainname進行設置我們的主機名及NIS域名,不過我們很少在程序中使用sethostname()及setdomainname()系統調用。一般地,hostname及NIS domain name是在系統啟動時由啟動腳本進行設置的。
想要查看更多的關於proc相關的信息,我們查看man page的第5章節的proc幫助信息,如下即可:
man 5 proc練習:
獲取系統用戶的進程PID及進程名稱。該程序需要一個命令行參數,該參數是/etc/passwd中的用戶名(如root,mysql等)。
cat my_procfs_user_exe.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <ctype.h>
#include <limits.h>
#include <sys/types.h>
#include <pwd.h>
#include <errno.h>
#define MAX_LINE 1000
typedef enum {FALSE, TRUE} Boolean;
char *username_from_id(uid_t uid)
{
struct passwd *pwd;
pwd = getpwuid(uid);
return (pwd == NULL) ? NULL : pwd->pw_name;
}
uid_t userid_from_name(const char *name)
{
struct passwd *pwd;
uid_t u;
char *endptr;
if (name == NULL || *name == '\0') {
return -1;
}
u = strtol(name, &endptr, 10);
if (*endptr == '\0') {
return u;
}
pwd = getpwnam(name);
if (pwd == NULL) {
return -1;
}
return pwd->pw_uid;
}
int main(int argc, char *argv[])
{
DIR *dirp;
struct dirent *dp;
char path[PATH_MAX];
char line[MAX_LINE], cmd[MAX_LINE];
FILE *fp;
char *p;
uid_t uid, checked_uid;
Boolean got_name, got_uid;
if (argc < 2 || strcmp(argv[1], "--help") == 0) {
printf("%s <username>\n", argv[0]);
exit(1);
}
checked_uid = userid_from_name(argv[1]);
if (checked_uid == -1) {
printf("Bad username: %s\n", argv[1]);
exit(1);
}
dirp = opendir("/proc");
if (dirp == NULL) {
perror("opendir");
exit(1);
}
/* scan entries unser /proc directory */
for ( ;; ) {
errno = 0;
dp = readdir(dirp);
if (dp == NULL) {
if (errno != 0) {
perror("readdir");
exit(1);
} else {
break;
}
}
/* since we are looking for /proc/PID directories, skip
entries that are not directories, or don't begin with a
digit*/
if (dp->d_type != DT_DIR || !isdigit((unsigned char) dp->d_name[0])) {
continue;
}
snprintf(path, PATH_MAX, "/proc/%s/status", dp->d_name);
fp = fopen(path, "r");
if (fp == NULL) {
continue;
}
got_name = FALSE;
got_uid = FALSE;
while (!got_name || !got_uid) {
if (fgets(line, MAX_LINE, fp) == NULL) {
break;
}
/* The "Name:" line contains the name of the command that
this process is running */
if (strncmp(line, "Name:", 5) == 0) {
for (p = line + 5; *p != '\0' && isspace((unsigned char) *p); ) {
p++;
}
strncpy(cmd, p, MAX_LINE - 1);
cmd[MAX_LINE -1] = '\0'; /* Ensure null-terminated */
got_name = TRUE;
}
/* The "Uid:" line contains the real, effective, saved set-,
and file-system user IDs */
if (strncmp(line, "Uid:", 4) == 0) {
uid = strtol(line + 4, NULL, 10);
got_uid = TRUE;
}
}
fclose(fp);
/* If we found a username and a UID, and the UID matches,
then display the PID and command name */
if (got_name && got_uid && uid == checked_uid)
printf("%5s %s", dp->d_name, cmd);
}
return 0;
}編譯並運行,[root@mydevops sysinfo]# ./my_procfs_user_exe mysql 1208 mysqld [root@mydevops sysinfo]# ./my_procfs_user_exe nginx [root@mydevops sysinfo]# ./my_procfs_user_exe daemon [root@mydevops sysinfo]# ./my_procfs_user_exe apache 3907 httpd 3908 httpd 3909 httpd 3910 httpd 3911 httpd 3912 httpd 3913 httpd 3914 httpd 3915 httpd好
本文出自 “固態U盤” 博客,請務必保留此出處http://lavenliu.blog.51cto.com/5060944/1783110