12.1 /proc 文件系统
为了提供更为简便的方法来访问内核信息,许多现代 UNIX 实现提供了一个/proc 虚拟文件系统。该文件系统驻留于/proc 目录中,包含了各种用于展示内核信息的文件,并且允许进程通过常规文件 I/O 系统调用来方便地读取,有时还可以修改这些信息。之所以将/proc 文件系统称为虚拟,是因为其包含的文件和子目录并未存储于磁盘上,而是由内核在进程访问此类信息时动态创建而成。
12.1.1 获取与进程有关的信息:/proc/PID
对于系统中每个进程,内核都提供了相应的目录,命名为/proc/PID,其中 PID 是进程的ID。在此目录中的各种文件和子目录包含了进程的相关信息。例如,通过查看/proc/1 目录下的文件,可以获取 init 进程的信息,该进程的 ID 总是为 1。
每个/proc/PID 目录中都存在一个命名为 status 的文件,提供了有关该进程的一系列信息。
vainx@DESKTOP-0DN0PNJ:~/wsl-code/tlpi-book/time$ cat /proc/1/status
Name: init
Umask: 0000
State: S (sleeping)
Tgid: 1
Ngid: 0
Pid: 1
PPid: 0
TracerPid: 0
Uid: 0 0 0 0
Gid: 0 0 0 0
...
...
...
该文件内容随着时间而改变,这一事实揭示出关于/proc 文件使用的要点所在。当这些文件由多个条目组成时,对其解析应当谨慎从事,在这种情况下,应查找包含特殊字符串(如,PPid)的匹配行记录,而非按照(逻辑)行号来处理文件。
下表列举了在每个/proc/PID 目录中的部分其他文件。
文件 | 描述(进程属性) |
---|---|
cmdline | 以\0 分隔的命令行参数 |
cwd | 指向当前工作目录的符号链接 |
Environ | NAME=value 键值对环境列表,以\0 分隔 |
exe | 指向正在执行文件的符号链接 |
fd | 文件目录,包含了指向由进程打开文件的符号链接 |
maps | 内存映射 |
mem | 进程虚拟内存(在 I/O 操作之前必须调用 lseek()移至有效偏移量) |
mounts | 进程的安装点 |
root | 指向根目录的符号链接 |
status | 各种信息(比如,进程 ID、凭证、内存使用量、信号) |
task | 为进程中的每个线程均包含一个子目录(始自 Linux 2.6) |
/proc/PID/fd 目录为进程打开的每个文件描述符都包含了一个符号链接,每个符号链接的名称都与描述符的数值相匹配。例如,/proc/1968/1 是 ID 为 1968 的进程中指向标准输出的符号链接。为方便起见,任何进程都可使用符号链接/proc/self 来访问其自己的/proc/PID 目录。
针对进程中的每个线程,内核提供了以**/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 就在此列。
12.1.2 /proc 目录下的系统信息
/proc 目录下的各种文件和子目录提供了对系统级信息的访问。 下图展示了其中的部分。
目录 | 目录中文件表达的信息 |
---|---|
/proc | 各种系统信息 |
/proc/net | 有关网络和套接字的状态信息 |
/proc/sys/fs | 文件系统相关设置 |
/proc/sys/kernel | 各种常规的内核设置 |
/proc/sys/net | 网络和套接字的设置 |
/proc/sys/vm | 内存管理设置 |
/proc/sysvipc | 有关 System V IPC 对象的信息 |
12.1.3 访问/proc 文件
通常使用 shell 脚本来访问/proc 目录下的文件。
也可以从程序中使用常规 I/O 系统调用来访问/proc 目录下的文件。但在访问这些文件时,有如下一些限制。
- /proc 目录下的一些文件是只读的,即这些文件仅用于显示内核信息,但无法对其进行修改。/proc/PID 目录下的大多数文件就属于此类型。
- /proc 目录下的一些文件仅能由文件拥有者(或特权级进程)读取。例如,/proc/PID目录下的所有文件都属于拥有相应进程的用户,而且即使是对文件的属主,其中的部分文件(如:proc/PID/environ 文件)也仅仅授予了读权限。
- 除了/proc/PID 子目录中的文件,/proc 目录的其他文件大多属于 root 用户,并且也仅有 root 用户能够修改那些可修改的文件。
访问/proc/PID 目录中的文件
/proc/PID 目录内容变化不定。每个目录随着含有相应进程 ID 的进程创建而生,又随进程的终止而灭。这意味着要确定特定/proc/PID 目录的存在,就需要干净利落地处理如下可能性:当打开此目录下的文件时,进程已经终止,并且也已经删除了相应的/proc/PID 目录。
示例程序:访问/proc/sys/kernel/pid_max 文件
#include <fcntl.h>
#include "tlpi_hdr.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)
errExit("open");
n = read(fd, line, MAX_LINE);
if (n == -1)
errExit("read");
if (argc > 1)
printf("Old value: ");
printf("%.*s", (int) n, line);
if (argc > 1) {
if (lseek(fd, 0, SEEK_SET) == -1)
errExit("lseek");
if (write(fd, argv[1], strlen(argv[1])) != strlen(argv[1]))
fatal("write() failed");
system("echo /proc/sys/kernel/pid_max now contains " /* system()用于执行系统命令或外部程序 */
"`cat /proc/sys/kernel/pid_max`");
}
exit(EXIT_SUCCESS);
}
$ su
Password:*******
# ./procfs_pidmax 10000
Old value: 32768
/proc/sys/kernel/pid_max now contains 10000
12.2 系统标识:uname()
uname()系统调用返回了一系列关于主机系统的标识信息,存储于 utsbuf 所指向的结构中。
#incldue <sys/utsname.h>
int uname(struct utsname *utsbuf); /* 成功返回0,失败返回-1 */
utsbuf 参数是一个指向 utsname 结构的指针,其定义如下:
#define _UTSNAME_LENGTH 65
struct utname
{
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 on which system */
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
};
在 Linux 中,这些字段长度均为 65 个字节,其中包括了空字节终止符所占用的空间。
utsname 结构中的 sysname、release、version 和 machine 字段由内核自动设置。
nodename 字段的返回值由 sethostname()系统调用设置(详情请参考此系统调用的手册页)。通常,该值类似于系统 DNS 域名中的前缀主机名。
domainname 字段的返回值由 setdomainname()系统调用设置(详情请参考此系统调用的手册页)。该值是主机的网络信息服务(NIS)域名(与主机域名不同)。
【注】gethostname()系统调用(是 sethostname()函数的反向操作)用于获取系统主机名,也可利用 hostname(1)命令和 Linux 特有的/proc/hostname 文件来查看和设置系统主机名;getdomainname()系统调用(setdomainname()函数的反向操作)用于获取 NIS 域名,也可利用 domainname(1)命令和 Linux 特有的/proc/domainname 文件来查看和设置 NIS 域名;sethostname()和 setdomainname()系统调用在应用程序中鲜有使用。通常,会在系统启动时运行启动脚本来确立主机名和 NIS 域名。