TLPI 第12章 练习:System and Process Information

笔记和练习博客总目录见:开始读TLPI

练习 12-1.

编写一个程序,根据程序命令行参数中指定的用户名,列出该用户运行的所有进程的进程ID和命令名称。(第159页的代码清单8-1中的userIdFromName()函数可能对你有用。)这可以通过检查系统中所有/proc/PID/status文件中的Name:和Uid:行来实现。遍历系统中所有/proc/PID目录需要使用readdir(3),相关描述见第18.8节。请确保你的程序能够正确处理以下情况:在程序确定某个/proc/PID目录存在之后、尝试打开对应的/proc/PID/status文件之前,该目录可能已经消失。


代码清单8-1中的userIdFromName()函数来自文件users_groups/ugid_functions.c:

c 复制代码
uid_t           /* Return UID corresponding to 'name', or -1 on error */
userIdFromName(const char *name)
{
    struct passwd *pwd;
    uid_t u;
    char *endptr;

    if (name == NULL || *name == '\0')  /* On NULL or empty string */
        return -1;                      /* return an error */

    u = strtol(name, &endptr, 10);      /* As a convenience to caller */
    if (*endptr == '\0')                /* allow a numeric string */
        return u;

    pwd = getpwnam(name);
    if (pwd == NULL)
        return -1;

    return pwd->pw_uid;
}

原书提供了示例代码:sysinfo/procfs_user_exe.c。

我的代码如下:

c 复制代码
// ex12-1.c
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <dirent.h>
#include "tlpi_hdr.h"
#include "ugid_functions.h"

/*
 * We need below fields:
 *
 * Name:   vim
 * Pid:    16582
 * Uid:    1000    1000    1000    1000
 *
 */

#define STR_MAX_LEN 1024

int process_pid(int uid, pid_t pid);

int process_pid(int uid, pid_t pid)
{

    char pathname[PATH_MAX];
    char buf[STR_MAX_LEN + 1];
    char cmdname[STR_MAX_LEN + 1] = "";
    FILE *fp;
    int finduser = 0;
    int findcmd = 0;

    sprintf(pathname, "/proc/%d/status", pid);
        fp = fopen(pathname, "r");
    if (fp == NULL)
        return -1;


    while (fgets(buf, 1024, fp) != NULL) {
        if (strncmp("Name:", buf, 5) == 0)      {
            strcpy(cmdname, buf + 5);
            findcmd=1;
        }

        if (strncmp("Uid:", buf, 4) == 0)
            if (strtol(buf + 4, NULL, 10) == uid)
                finduser = 1;
    }

    if (finduser && findcmd)
        printf("Pid: %d, Command: %s\n", pid, cmdname);

    fclose(fp);

    return 0;
}

int
main(int argc, char *argv[])
{

    uid_t uid;
    char *username;
    DIR *dirp;
    struct dirent *dp;
    pid_t pid;

    if (argc != 2 || strcmp(argv[1], "--help") == 0)
        usageErr("%s username\n", argv[0]);

    username = argv[1];
    uid = userIdFromName(username);
    if((int)uid == -1)
        errExit("userIdFromName");

    printf("uid of user %s is %d\n", username, uid);


    dirp = opendir("/proc");
    if (dirp == NULL)
        errExit("opendir");

    while ((dp = readdir(dirp)) != NULL) {
        if (dp->d_name[0] >= '0' && dp->d_name[0] <= '9') {
            pid = strtol(dp->d_name, NULL, 10);


            process_pid(uid, pid);
        }
    }

    closedir(dirp);
    exit(EXIT_SUCCESS);
}

运行如下:

bash 复制代码
$ ./ex12-1 vagrant
uid of user vagrant is 1000
Pid: 15358, Command:    systemd

Pid: 15360, Command:    (sd-pam)

Pid: 15368, Command:    sshd

Pid: 15369, Command:    bash

Pid: 16515, Command:    sshd

Pid: 16516, Command:    bash

Pid: 16720, Command:    vim

Pid: 16739, Command:    ex12-1

我的代码和原书代码核心的点是一致的,原书代码比我好的点在于:

  1. 提示比较清晰
  2. errno的处理,能清晰的分别fgets是EOF还是出错

练习 12-2.

编写一个程序,绘制显示系统上所有进程的层级父子关系的树,一直追溯到 init。对于每个进程,程序应显示进程 ID 和正在执行的命令。程序输出应类似于 pstree(1) 的输出,虽然不必像其那么复杂。系统上每个进程的父进程可以通过检查系统上所有 /proc/PID/status 文件中的 PPid: 行来找到。注意处理在扫描所有 /proc/PID 目录时,某个进程的父进程(因此其 /proc/PID 目录)可能消失的情况。


pstree也是使用/proc,可以带用户名或PID参数:

bash 复制代码
$ pstree vagrant
sshd───bash───pstree

systemd───(sd-pam)

$ pstree $$
bash───pstree

代码就不写了,和上例差不多,但:

  1. 可以指定pid,例如当前运行程序的pid。
  2. 可以考虑写递归函数,直到PPID=0。(systemd的pid为1,其父进程pid为0)
bash 复制代码
$ ps -p 1 -o ppid,cmd
   PPID CMD
      0 /usr/lib/systemd/systemd --switched-root --system --deserialize 31

练习 12-3.

编写一个程序,列出所有打开了特定文件路径名的进程。可以通过检查所有 /proc/PID/fd/* 符号链接的内容来实现。这将需要使用嵌套循环,利用 readdir(3) 来扫描所有 /proc/PID 目录,然后扫描每个 /proc/PID 目录中所有 /proc/PID/fd 条目的内容。读取 /proc/PID/fd/n 符号链接的内容需要使用 readlink(),其在第 18.5 节中有描述。


代码不写了,但可以模拟下场景。

启动一个vi会话:

bash 复制代码
$ ls > testfile
$ vi testfile

在另一会话监控:

bash 复制代码
$ ps -ef|grep vim|grep -v grep
vagrant    15387   15252  0 02:31 pts/1    00:00:00 /usr/bin/vim testfile
$ cd /proc/15387/fd
$ ls
0  1  2  4
$ ls -l 4
lrwx------. 1 vagrant vagrant 64 Apr 21 02:32 4 -> /home/vagrant/tlpi-book/ex/.testfile.swp

有两点注意:

  1. 文件可能需要编辑一下,才会出现文件描述符
  2. 基于vi机制,我们观察到的是swp文件

下面这个例子更好。

会话1:

bash 复制代码
$ exec 4< testfile
$ echo $$
15252

会话2:

bash 复制代码
$ cd /proc/15252/fd
$ ls
0  1  2  255  4
$ ls -l 4
lr-x------. 1 vagrant vagrant 64 Apr 21 02:36 4 -> /home/vagrant/tlpi-book/ex/testfile

测试完成后,会话1:

bash 复制代码
$ exec 4<&-
相关推荐
hj2862511 小时前
Linux基础知识day04
linux·运维·服务器
奇妙之二进制1 小时前
zmq源码分析之signaler_t
linux·服务器·网络
輝太くん2 小时前
haproxy
linux
俩个逗号。。2 小时前
Ubuntu 动画全部消失
linux·ubuntu
liuyao_xianhui2 小时前
Linux开发工具结尾 _make
linux·运维·服务器·数据结构·哈希算法·宽度优先·1024程序员节
天疆说2 小时前
在 Ubuntu 22.04 上安装 Ghostty 终端
linux·运维·ubuntu
buhuizhiyuci2 小时前
熟练使用Linux编译工具(gcc, g++, make, makefile)
linux·运维·服务器
草莓熊Lotso2 小时前
从 LLM 底层原理到 LangChain 全链路打通:大模型应用开发新征程
linux·运维·服务器·人工智能·langchain
cyber_两只龙宝2 小时前
【Oracle】Oracle数据库的登录验证
linux·运维·数据库·sql·云原生·oracle