Linux /proc这个特殊的目录包含有关Linux系统的所有详细信息,包括其内核、进程和配置参数。通过研究/proc目录,可以了解Linux命令的工作原理,甚至可以执行一些管理任务。
走进Linux的/proc目录
今天,我们将查看/proc目录并熟悉它。/proc目录存在于所有Linux系统上,无论其风格或体系结构如何。
/proc中的文件不是真正的文件,它们充当内核数据结构和进程信息的接口。由于它们不是真正的文件,文件大小等属性不适用于它们。
最有趣的部分是,/proc文件系统中几乎所有文件的大小都是0字节。让许多用户困惑的是,尽管它们的大小为0,但在查看时仍然包含数据。这怎么可能?
Linux能够处理许多不同类型的文件系统,因为它被称为VFS(虚拟文件系统)。/proc文件系统也由内核使用VFS访问。因此,当用户试图访问/proc文件系统中的文件时,proc文件系统会借助内核中的信息创建该文件的内容。这就是为什么在列出/proc目录时,大多数目录的大小显示为0字节,但在访问时会动态填充。
让我们实际检查并理解它。Linux中有一个名为file的命令。
file [ -bcnsvzL ] [ -f 命名文件 ] [ -m 幻数文件 ] file …
linux中的“file”命令用于通过检查文件的内容来确定文件的类型。如果文件为空,它将给出“文件为空”的输出。让我们尝试使用file命令检查任何/proc文件的文件类型。
输出表明文件为空。但是,让我们尝试使用vi、cat或更少的编辑器访问该文件。
因此,当您访问内容时,当前值将从内核填充。这就是为什么您可以从/proc中的文件获得系统的最新和准确状态的原因。
我们之前看到proc手册页将proc定义为“进程信息伪文件系统”。这是因为它包含所有当前正在运行的进程的详细信息。让我们看看/proc的目录列表
可以在上面显示的目录列表中看到,里面有很多编号的目录。这些目录用相应的PID编号。当进程开始和停止时,它们会动态地出现和消失。以各自PID命名的每个目录都包含进程当前状态的详细信息,我们随便进入一个PID看看里面的信息。
让我们看看/proc中PID目录的内容。下图显示了PID目录的内容。
Linux中 /proc/[pid] 目录各文件简析
在 /proc 目录里, 每个正在运行的进程都有一个以该进程 ID 命名的子目录, 其下包括如下的目录和伪文件
包含传递给进程的 ELF 解释器信息,格式是每一项都是一个 unsigned long长度的 ID 加上一个 unsigned long 长度的值。
该文件保存了进程的完整命令行. 如果该进程已经 被交换出内存, 或者该进程已经僵死, 那么就没有 任何东西在该文件里,这时候对该文件的读操作将返回零 个字符. 该文件以空字符 null 而不是换行符作为结 束标志.
cwd
一个符号连接, 指向进程当前的工作目录. 例如, 要找出进程 20 的 cwd, 你可以:cd /proc/20/cwd;
/bin/pwd
environ
该文件保存进程的环境变量, 各项之间以空字符分隔, 结尾也可能是一个空字符. 因此, 如果要输出进程 1 的环境变量, 你应该: (cat /proc/1/environ; echo) | tr “;\000”; “;\n”;
exe
也是一个符号连接, 指向被执行的二进制代码
fd
进程所打开的每个文件都有一个符号连接在该子目 录里, 以文件描述符命名, 这个名字实际上是指向 真正的文件的符号连接,(和 exe记录一样). 例如, 0 是标准输入, 1是标准输出, 2 是标准错误, 等等
maps
该文件包含当前的映象内存区及他们的访问许可.
格式如下:
address perms offset dev inode
00000000-0002f000 r-x-- 00000400 03:03 1401
0002f000-00032000 rwx-p 0002f400 03:03 1401
00032000-0005b000 rwx-p 00000000 00:00 0
60000000-60098000 rwx-p 00000400 03:03 215
60098000-600c7000 rwx-p 00000000 00:00 0
bfffa000-c0000000 rwx-p 00000000 00:00 0
address 是进程所占据的地址空间, perms 是权限集:
r = read
w = write
x = execute
s = shared
p = private (copy on write)
offset 是文件或者别的什么的偏移量, dev 是设备号(主设 备号:从设备号), 而 inode 则是设备的节点号. 0 表明没有 节点与内存相对应, 就象 bss 的情形.
mem
该文件并不是 mem (1:1) 设备, 尽管它们有相同的设备号. /dev/mem 设备是做任何地址转换之前的物理内存, 而这里的 mem 文件是访问它的进程的内存.目前这个 mem 还不能 mmap(2) (内存映射)出去,而且可能一直要等到内核中增加了一个通用的mmap(2) 以后才能实现. (也许在你读本手册页时这一切已经发生了)
mmap
mmap(2) 做的 maps 映射目录,是和 exe, fd/* 等类似的符号连接. 请注意 maps 包含了比
/proc/*/mmap 更多的信息, 所以应该废弃 mmap.
root
依靠系统调用 chroot(2), unix 和 linux 可以让 每个进程有各自的文件系统根目录. 由 chroot(2) 系统调用设置. 根指向文件系统的根,性质就象 exe, fd/* 等一样.
stat
进程状态信息, 被命令 ps(1) 使用.
cpuinfo
保存了CPU 以及体系架构依赖条目的列表. 对于不同的系 统架构有不同的列表, 共有的两项是 cpu 和 BogoMIPS, cpu 可能是当前在用的 CPU, 而 BogoMIPS 则是内核初始化时计算出的一个系统常数.
devices
主设备号及设备组的列表, 文本格式. MAKEDEV 脚本使用 该文件来维持内核的一致性.
dma
一个列表, 指出正在使用的ISA DMA (直接内存访问)通道.
filesystems
以文本格式列出了被编译进内核的文件系统. 当没有给 mount(1) 指明哪个文件系统的时候, mount(1) 就依靠该文件遍历不同的文件系统.
interrupts
该文件以 ASCII 格式记录了(至少是在 i386 体系上的)每次 IRQ 的中断数目.
ioports
该文件列出了当前在用的已注册 I/O 端口范围.
kcore
该伪文件以 core 文件格式给出了系统的物理内存映象, 再 利用未卸载的内核 (/usr/src/linux/tools/zSystem), 我 们就可以用 GDB 查探当前内核的任意数据结构.
kmsg
可以用该文件取代系统调用 syslog(2) 来记录内核信息. 但是读该文件需要超级用户权限, 并且一次只能有一个进 程可以读该文件,因而如果一个使用了 syslog(2)系统调用功能来记录内核信息的系统日志进程正在运行的话,别的进程就不能再去读该伪文件了.该文件的内容可以用 dmesg(8) 来察看.
ksyms
该文件保存了内核输出的符号定义, modules(X) 使用该文件 动态地连接和捆绑可装载的模块.
loadavg
平均负载数给出了在过去的 1, 5, 15 分钟里在运行队列里 的任务数, 与 uptime(1) 等命令的结果相同.
locks
这个文件显示当前文件锁.
malloc
只有在编译时定义了 CONFIGDEBUGMALLOC 才会有该文件.
meminfo
free(1) 利用该文件来给出系统总的空闲内存和已用内存 (包括物理内存和交换内存), 以及内核所使用的共享内存 和缓冲区.该文件与 free(1) 格式相同, 但是以字节为单位而不是 KB.
modules
列出了系统已载入的模块, 文本格式.
self
当某进程访问 /proc 目录时, 该目录就指向 /proc 下以该进 程 ID 命名的目录.
stat
内核及系统的统计数据.
uptime
该文件包含两个数: 系统正常运行时间和总的空闲时间, 都以秒为单位.
version
指明了当前正在运行的内核版本
获取当前进程的名称 C/C++代码实现
proc.h
...
struct proc {
pid_t pid;
pid_t ppid;
pid_t tracer;
unsigned long *auxvals;
size_t nauxvals;
char state;
char *path;
char *name;
struct environ *environ;
size_t nenviron;
char **cmdline;
size_t ncmdline;
pid_t *threads;
size_t nthreads;
};
struct module {
void *base;
size_t size;
void *end;
char *path;
char *name;
};
struct page {
void *base;
size_t size;
unsigned long offset;
void *end;
int prot;
int flags;
};
int proc_enumpids(int(*callback)(pid_t pid, void *arg), void *arg);
int proc_checkpid(pid_t pid);
pid_t proc_getppid(pid_t pid);
pid_t proc_gettracer(pid_t pid);
int proc_enumauxvals(pid_t pid, int(*callback)(unsigned long type,
unsigned long val, void *arg), void *arg);
unsigned long proc_getauxval(pid_t pid, unsigned long type);
uid_t proc_getuid(pid_t pid);
uid_t proc_geteuid(pid_t pid);
gid_t proc_getgid(pid_t pid);
gid_t proc_getegid(pid_t pid);
char proc_getstate(pid_t pid);
unsigned long proc_getplatform(pid_t pid);
size_t proc_getpath(pid_t pid, char **ppathbuf, size_t maxlen);
size_t proc_getname(pid_t pid, char **pnamebuf, size_t maxlen);
int proc_enumenviron(pid_t pid,
int(*callback)(char *name, char *value, void *arg),
void *arg);
int proc_enumcmdline(pid_t pid, int(*callback)(char *cmdarg, void *arg),
void *arg);
size_t proc_getcmdline(pid_t pid, char **pcmdlinebuf, size_t maxlen);
int proc_enumthreads(pid_t pid, int(*callback)(pid_t tid, void *arg),
void *arg);
unsigned long proc_getentry(pid_t pid);
ssize_t proc_vmread(pid_t pid, off_t src, void *dst, size_t size);
ssize_t proc_vmwrite(pid_t pid, off_t dst, void *src, size_t size);
int proc_openproc(pid_t pid, struct proc *pproc);
void proc_closeproc(struct proc *pproc);
proc.c
...
int proc_openproc(pid_t pid, struct proc *pproc)
{
int ret = -1;
char *status_filebuf;
char status_path[64] = { 0 };
snprintf(status_path, sizeof(status_path) - 1, "/proc/%d/status", pid);
status_filebuf = get_filebuf(status_path);
if (!status_filebuf)
goto EXIT;
pproc->pid = pid;
pproc->ppid = _proc_getppid(status_filebuf);
if (pproc->ppid == (pid_t)-1)
goto ERR_PPID;
pproc->tracer = _proc_gettracer(status_filebuf);
if (pproc->tracer == (pid_t)-1)
goto ERR_TRACER;
pproc->auxvals = (unsigned long *)NULL;
pproc->nauxvals = 0;
proc_enumauxvals(pproc->pid, _proc_openproc_callback_auxv,
(void *)pproc);
if (!pproc->auxvals || !pproc->nauxvals)
goto ERR_AUXVALS;
pproc->state = _proc_getstate(status_filebuf);
if (pproc->state == 0)
goto ERR_STATE;
if (!proc_getpath(pproc->pid, &pproc->path, 0))
goto ERR_PATH;
if (!_proc_getname(status_filebuf, &pproc->name, 0))
goto ERR_NAME;
pproc->environ = (struct environ *)NULL;
pproc->nenviron = 0;
if (proc_enumenviron(pproc->pid, _proc_openproc_callback_env, (void *)pproc))
goto ERR_ENVIRON;
pproc->cmdline = (char **)NULL;
pproc->ncmdline = 0;
if (proc_enumcmdline(pproc->pid, _proc_openproc_callback_cmd, (void *)pproc))
goto ERR_CMDLINE;
pproc->threads = (pid_t *)NULL;
pproc->nthreads = 0;
if (proc_enumthreads(pproc->pid, _proc_openproc_callback_tid, (void *)pproc))
goto ERR_THREADS;
ret = 0;
goto EXIT; /* skip errors */
ERR_THREADS:
{
size_t i;
for (i = 0; i < pproc->ncmdline; ++i)
free(pproc->cmdline[i]);
free(pproc->cmdline);
}
ERR_CMDLINE:
{
size_t i;
for (i = 0; i < pproc->nenviron; ++i) {
free(pproc->environ[i].name);
free(pproc->environ[i].value);
}
free(pproc->environ);
}
ERR_ENVIRON:
free(pproc->name);
ERR_NAME:
free(pproc->path);
ERR_PATH:
ERR_STATE:
free(pproc->auxvals);
ERR_AUXVALS:
ERR_TRACER:
ERR_PPID:
free(status_filebuf);
EXIT:
return ret;
}
...
main.c
int main(int argc, char **argv)
{
...
pid = getpid();
if (proc_openproc(pid, &proc)) {
printf("[!] Unable to open process\n");
return -1;
}
printf("[*] PID: %d\n", proc.pid);
printf("[*] PPID: %d\n", proc.ppid);
printf("[*] Tracer: %d\n", proc.tracer);
printf("[*] UID: %d\n", (uid_t)proc.auxvals[AT_UID]);
printf("[*] EUID: %d\n", (uid_t)proc.auxvals[AT_EUID]);
printf("[*] GID: %d\n", (gid_t)proc.auxvals[AT_GID]);
printf("[*] EGID: %d\n", (gid_t)proc.auxvals[AT_EGID]);
printf("[*] Entry: %p\n", (void *)proc.auxvals[AT_ENTRY]);
printf("[*] State: %c\n", proc.state);
printf("[*] Path: %s\n", proc.path);
printf("[*] Name: %s\n", proc.name);
printf("[*] Environment Variables: \n");
{
size_t i;
for (i = 0; i < proc.nenviron; ++i) {
printf("\t%s=%s\n", proc.environ[i].name,
proc.environ[i].value);
}
}
printf("[*] Command Line: ");
{
size_t i;
for (i = 0; i < proc.ncmdline; ++i) {
printf("%s ", proc.cmdline[i]);
}
}
printf("\n");
printf("[*] Threads: { ");
{
size_t i;
for (i = 0; i < proc.nthreads; ++i) {
printf("%d ", proc.threads[i]);
}
}
printf("}\n");
printf("===================\n");
proc_closeproc(&proc);
printf("Press ENTER to exit...");
getchar();
return 0;
}
编译运行
proc获取当前进程的名称,其中包含有关进程的宝贵信息,如命令行、绝对路径、ppid、跟踪程序pid、状态、环境变量等。
If you need the complete source code of proc, add your WeChat number (c17865354792)
总结
proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为访问系统内核数据的操作提供接口。
系统中当前运行的每一个进程都有对应的一个目录在 proc 下,以进程的 PID 号为目录名,它们是读取进程信息的接口。
Welcome to follow WeChat official account【程序猿编码】
参考:https://man7.org/linux/man-pages/man5/proc.5.html