/proc文件系统
- 获取与进程有关的信息: /proc/PID
在老版UNIX中允许特权级程序深入内核内存中的数据结构以获取内核信息, 如:
1. 系统中有多少进程在运行,其属主是谁?
2. 一个进程打开多少文件?
3. 目前锁定了什么文件,
4. 哪些进程持有这些锁?系统正在使用什么套接字?
为了提供更为简便的方法访问内核信息,许多现代UNIX实现提供了一个/proc虚拟文件系统
该文件系统驻留于/proc目录中,包含了各种用于展示内核信息的文件,并且允许进程通过常规文件I/O系统调用来方便的读取,有时还可以修改这些信息。
之所以称/proc文件系统为虚拟,因其包含的文件和子目录并未存储于磁盘上,而是由内核在进程访问此类信息时动态创建而成
-
获取与进程相关的信息: /proc/PID
对于系统中的每个进程,内核都提供了相应的目录,命令为/proc/PID,其中PID为进程ID。此目录中的各种文件和子目录包含了进程的相关信息。
1)./Proc/PID/status 结果参数说明
每个/proc/PID目录中都存在一个命名为satus的文件,提供了进程的以系列信息,
$ cat /proc/1/status Name: systemd # name of command run by this process(进程名) Umask: 0000 State: S (sleeping) # State of this process (进程状态) Tgid: 1 # Thread group ID(traditional PID, getpid()) 线程组ID Ngid: 0 Pid: 1 # Actually,thread ID(gettid()) 进程ID(实际线程ID) PPid: 0 # Parent process ID (父进程ID) TracerPid: 0 # PID of tracing process(0 if not traced)(接收跟踪该进程信息的进程的ID号) Uid: 0 0 0 0 # Real, offective, saved set, and FS UIDS Gid: 0 0 0 0 # Real, offective, saved set, and FS GIDS FDSize: 128 # of file descriptor slots currently allocated(当前分配的文件描述符插槽最大个数) Groups: # supplementary group IDs(补充组ID) NStgid: 1 NSpid: 1 NSpgid: 1 NSsid: 1 VmPeak: 270708 kB # Peak virtual memory size(峰值虚拟内存大小) VmSize: 205172 kB # Current virtual memory size(当前虚拟内存大小,其中total_vm为进程的地址空间的大小,reserved_vm:进程在预留或特殊的内存间的物理页 ) VmLck: 0 kB # Locked memory (锁定存储器, 任务已经锁住的物理内存的大小。锁住的物理内存不能交换到硬盘 (locked_vm) VmPin: 0 kB VmHWM: 7440 kB # Peak resident set size (峰值驻留集大小) VmRSS: 7440 kB # Current resident set size (当前峰值驻留集大小, 应用程序正在使用的物理内存的大小,就是用ps命令的参数rss的值 (rss) ) RssAnon: 2208 kB # RssFile: 5232 kB RssShmem: 0 kB VmData: 18556 kB # Data segment size (数据段大小) VmStk: 132 kB # Stack size(栈大小) VmExe: 948 kB # Text(executable code)size (代码段大小,程序所拥有的可执行虚拟内存的大小,代码段,不包括任务使用的库 (end_code-start_code) ) VmLib: 6816 kB # Shared library size (共享库大小,被映像到任务的虚拟内存空间的库的大小 (exec_lib) VmPTE: 160 kB # Size of page table(since 2.6.10) (页表大小, 该进程的所有页表的大小,单位:kb ) VmSwap: 0 kB # HugetlbPages: 0 kB CoreDumping: 0 Threads: 1 # of threads in this thread's thread group (此线程的线程组中的线程数) SigQ: 0/31144 # Current/max,queued signals(since 2.6.10) (当前/最大,信号队列(从2.6.12开始)) SigPnd: 0000000000000000 # Signals pending for thread (线程的挂起信号, 屏蔽位,存储了该线程的待处理信号) ShdPnd: 0000000000000000 # Signals pending for process(since 2.6) (等待处理的信号, 屏蔽位,存储了该线程组的待处理信号 ) SigBlk: 7be3c0fe28014a03 # Blocked signals(阻塞信号, 存放被阻塞的信号) SigIgn: 0000000000001000 # Ignored singals(忽略的信号, 存放被忽略的信号 ) SigCgt: 00000001800004ec # Caught signals (捕捉到的信号, 存放被捕获到的信号) CapInh: 0000000000000000 # Inheritable capabilities (可继承能力, 能被当前进程执行的程序的继承的能力 ) CapPrm: 0000003fffffffff # Permitted capabilities (进程能够使用的能力,可以包含CapEff中没有的能力,这些能力是被进程自己临时放弃的,CapEff是CapPrm的一个子集,进程放弃没有必要的能力有利于提高安全性) CapEff: 0000003fffffffff # Effective capabilities (进程的有效能力) CapBnd: 0000003fffffffff # Capability boundings set(since 2.6.26) (能力边界集) CapAmb: 0000000000000000 NoNewPrivs: 0 Seccomp: 0 Speculation_Store_Bypass: vulnerable Cpus_allowed: f # CPUs allowed, mask (since 2.6.24) Cpus_allowed_list: 0-3 # Same as above, list format(since 2.6.24) (与上面相同, 列表格式) # Mems_allowed: Memory nodes allowed, mask (允许内存节点,掩码) since 2.6.24 Mems_allowed: 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001 Mems_allowed_list: 0 # Same as above, list format voluntary_ctxt_switches: 1383 # Voluntary context switches (自动上下文切换) since 2.6.23 nonvoluntary_ctxt_switches: 576 # Involuntary context switches (非自愿上下文切换)since 2.6.23 Statck usage: 8kb # Stack usage high-water mark (堆栈使用的量最高水位线) since 2.6.23
该文件内容随着时间而改变,这一事实揭示出关于/proc 文件使用的要点所在。当这些文
件由多个条目组成时,对其解析应当谨慎从事,在这种情况下,应查找包含特殊字符串(如,
PPid)的匹配行记录,而非按照(逻辑)行号来处理文件2)./Proc/PID目录中其他内容
a. /proc/PID/fd 目录:/proc/PID/fd 目录为进程打开的每个文件描述符都包含了一个符号链接,每个符号链接的
名称都与描述符的数值相匹配。例如,/proc/1968/1 是 ID 为 1968 的进程中指向标准输出的符
号链接,更多信息参见 5.11 节。
为方便起见,任何进程都可使用符号链接/proc/self 来访问其自己的/proc/PID 目录b. 线程:/proc/PID/task 目录:
Linux 2.4 增加了线程组概念,正式支持 POSIX 线程模型。因为线程组中的一些属性对于
线程而言是唯一的,所以 Linux 2.4 在/proc/PID 目录下增加了一个 task 子目录。针对进程中的
每个线程,内核提供了以/proc/PID/task/TID 命名的子目录,其中 TID 是该线程的线程 ID。
(此
值等同于在线程中调用 gettid()函数的返回值。)
每个/proc/PID/task/TID 子目录中都有一套类似于/proc/PID 目录内容的文件和目录。因为线
程共享了多个属性,所以这些文件中的许多信息对进程中各个线程而言都是相同的。然而,这
些文件也显示了每个线程的独特信息,故而是合理的。例如,在线程组的/proc/PID/task/TID/status
文件中,存在那种对每个线程而言,内容都有可能不同的字段,State、Pid、SigPnd、SigBlk、
CapInh、CapPrm、CapEff 和 CapBnd 就在此列。 -
/proc目录下的系统信息
- /proc目录下的各种文件和子目录访问的系统信息
- /proc子目录用途节选
- /proc目录下的各种文件和子目录访问的系统信息
-
访问/proc文件
使用常规I/O系统调用来访问/proc目录下的文件,但在访问时有如下限制:
1. /proc目录下的一些文件是只读的,即这些文件仅用于显示内核信息,但无法对其修改。/proc/PID目录下的大多数文件就属于此类型
2. /proc目录下的一些文件仅能有文件拥有者(或特权级进程)读取。例如,/proc/PID/目录下的所有文件都属于拥有相应进程的用户,而且即使是对文件的属主,其中的部分文件(如:/proc/PID/environ文件)也仅仅授予读权限
3. 除了/proc/PID子目录中的文件,/proc目录的其他大多属于root用户,并且也仅有root用户能够修改那写可修改的文件
示例程序: 修改/proc/sys/kernel/pid_max
#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");
}
if ( write(fd, argv[1], strlen(argv[1])) != strlen(argv[1]) ) {
perror("write()");
}
system("echo /proc/sys/kernel/pid_max now_contains "
" `cat /proc/sys/kernel/pid_max`");
}
exit(EXIT_SUCCESS);
return 0;
}
proc/PID 目录内容变化不定。每个目录随着含有相应进程 ID 的进程创建而生,又随进程的终止而灭。这意味着要确定特定/proc/PID 目录的存在,就需要干净利落地处理如下可能性:当打开此目录下的文件时,进程已经终止,并且也已经删除了相应的/proc/PID 目录
系统标识: uname()
uname()系统调用返回了一系列关于主机系统的标示信息,存储于utsbuf所指向的结构中
- utsname结构
utsbuf参数是一个指向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 实施发布级别 */
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
};
/* SUSv3规范了uname(),但对utsname结构中各种字段的长度未加定义,
仅要求字符串以空子节终止。在Linux中,这些字段长度均为65个字节,
其中包括了空字节终止符所占用的空间。而在一些UNIX实现中,这些长度更短,
但在其他操作系统(如Solaris)中,这些字段的长度长达257个字节
*/
/* utsname结构中的sysname、release、version、和machine字段
由内核自动设置 */
在 Linux 中, /proc/sys/kernel 目录下的 3 个文件提供了与 utsname 结构的 sysname、和 version 字段返回值相同的信息,这些只读文件分别为ostype、osrelease 和 version。另外一个文件/proc/version,也包含了这些信息,并且还包含了有关内核编译的步骤信息(即执行编译的用户名、用于编译的主机名,以及使用的 gcc 版本)。
- uname相关系统调用
1.sethostname()设置utsname结构中nodename字段值,(详情请参考此系统调用的手册页)。通常,该值类似于系统 DNS 域名中的前缀主机名。
- gethostname()
gethostname()系统调用(是 sethostname()函数的反向操作)用于获取系统主机名,也可利用 hostname(1)命令和 Linux 特有的/proc/hostname 文件来查看和设置系统主机名。
- getdomainame() & setdomainname()
getdomainname()系统调用(setdomainname()函数的反向操作)用于获取 NIS 域名,也
可利用 domainname(1)命令和 Linux 特有的/proc/domainname 文件来查看和设置 NIS 域名
sethostname()和 setdomainname()系统调用在应用程序中鲜有使用。通常,会在系统启动
时运行启动脚本来确立主机名和 NIS 域名。
- 使用uname
#include <sys/utsname.h>
int uname(struct utsname *utsbuf);
Returns 0 on sucess, or -1 on error
int main()
{
struct utsname uts;
if (uname(&uts) == -1) {
perror("uname");
exit(EXIT_FAILURE);
}
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
exit(EXIT_SUCCESS);
}
- 总结
/proc文件系统向应用程序暴露了一系列内核信息。每个/proc/PID子目录都包含有许多文件和子目录,是进程ID为PID的进程提供的相关信息。/proc目录下的其他许多文件和目录,则暴露了应用程序可以读取,有时还可修改的系统级信息。
使用uname系统调用,可以获取UNIX的实现信息以及应用程序所运行的机器类型
进阶: 关于/proc 文件系统的深入信息可见诸于 proc(5)手册页、内核源文件 Documentation/
filesystems/proc.txt 以及 Documentation/sysctl 目录下的各种文件。