笔记和练习博客总目录见:开始读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
我的代码和原书代码核心的点是一致的,原书代码比我好的点在于:
- 提示比较清晰
- 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
代码就不写了,和上例差不多,但:
- 可以指定pid,例如当前运行程序的pid。
- 可以考虑写递归函数,直到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
有两点注意:
- 文件可能需要编辑一下,才会出现文件描述符
- 基于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<&-