ProcessInfo
在 ProcessInfo.h
文件中,存在一个命名空间 ProcessInfo
,其中声明了大部分进程需要使用到相关信息的函数,比如进程的 PID、UID、EUID,主机名,进程状态,程状态等等。
在 ProcessInfo.cc
的 detail
命名空间定义了如下几个函数供以使用。在此之前,先来了解一些必要的知识。
在 Linux 系统上,有一个 /proc
目录,该目录是一种文件系统,即 proc 文件系统。与其他常见的文件系统不同的是,该文件系统是一种伪文件系统,即虚拟文件系统。它用来存储当前内核运行状态的一系列特殊文件,用户可以通过这些文件查看有关系统硬件以及当前正在运行进程的信息,甚至可以通过更改其中某些文件来改变内核的运行状态。常见的目录如下:
/proc/[pid]
: 每个运行的进程都存在该目录proc/[pid]/fd
: 当前进程所打开的文件描述符的符号链接。proc/[pid]/task
: 每个线程一个子目录,目录名为线程ID。proc/[pid]/task/[tid]/stat
: 线程状态信息。
proc/[pid]/status
: 可读性好的进程相关信息。proc/[pid]/stat
: 进程状态信息,用于ps(1)
命令。proc/[pid]/exe
: 符号链接到启动进程的完整命令。
/proc/self
: 链接到了当前进程所在的目录
此外,目录项结构体 dirent
的定义如下:
struct dirent {
ino_t d_ino; /* i节点的数量 */
off_t d_off; /* 在目录文件中的偏移 */
unsigned short d_reclen; /* 记录的长度 */
unsigned char d_type; /* 文件类型 */
char d_name[256]; /* 文件名,最长 255 字符 */
};
在 detail
命名空间中,通过 __thread
关键字来修饰变量 t_numOpendFiles
和 t_pids
,前者是为了记录当前进程所打开的文件描述符数目,后者是一个指向动态数组的指针,该数组中存储了进程中所有的线程id。同时这两个变量在各个线程之间互不影响。
在命名空间中除了上述变量,还有如下几个函数:
int fdDirFilter(const struct dirent* d) {
if (::isdigit(d->d_name[0])) {
++t_numOpenedFiles;
}
return 0;
}
函数 fdDirFilter()
是为了过滤 /proc/self/fd
目录下的文件,该目录存放了当前进程所打开的文件描述符的符号链接,每个符号链接的名称与描述符的数值相匹配,因此用 isdigit()
函数来判断文件名是否符合要求。
int taskDirFilter(const struct dirent* d) {
if (::isdigit(d->d_name[0])) {
t_pids->push_back(atoi(d->d_name));
}
return 0;
}
函数 taskDirFilter()
是为了过滤 /proc/self/task
目录下的文件,该目录存放了当前进程中所有的线程 ID,由于线程 ID 均为数字,因此用 isdigit()
来判断文件名是否符合要求。
int scanDir(const char *dirpath, int (*filter)(const struct dirent *)) {
struct dirent** namelist = NULL;
int result = ::scandir(dirpath, &namelist, filter, alphasort);
assert(namelist == NULL);
return result;
}
函数 scanDir()
是为了扫描指定路径下的所有文件,其第二个参数是一个函数指针,用来表明所使用的文件过滤函数,例如上面所定义的 fdDirFilter()
和 taskDaskFilter()
。该函数内部使用 scandir(3)
来扫描指定目录,alphasort(3p)
指定该函数依照字母顺序排序目录结构。
除此之外,还定义了三个全局变量: g_startTime
、g_clockTicks
、g_pageSize
,其中,g_startTime
用来存储进程的启动时间,精度是微秒;g_clockTicks
用来存储每秒钟的时钟数;g_pageSize
则用来存储系统页面的大小,单位是字节。
在 ProcessInfo.h
文件中声明了如下函数,它们分别用于获取用户ID和有效用户ID:
uid_t uid();
uid_t euid();
在 Linux 系统中,我们熟知的用户 ID 包括 RUID、EUID、SUID,它们分别用来表示实际用户 ID、有效用户 ID、设置用户 ID,它们的定义如下:
-
RUID
(Real User ID):- 进程的实际所有者。
- 它用于信号传输。当一个进程的 RUID、EUID 与另一个进程的 RUID、SUID 相同时,一个非特权进程可以向另一个进程发出信号。
-
EUID
(Effective User ID):- 通常情况下等于 UID。
- EUID 由配置 SetUID 权限的可执行文件更改。
- EUID 临时存储另一个账户的 UID。
- 根据 EUID 中存储的 UID 确定进程的权限。
-
SUID
(Saved set-user-ID):- SUID 用于进程权限降低后恢复。
- 当进程的权限更改为较低时,以前的 EUID 存储在 SUID。
- 然后,当降低的权限恢复到原来的权限时,SUID 存储在 EUID。
总结来说,它们的去区别如下:
RUID
: 用于识别进程的真正所有者并影响其发送信号的权限。仅当发送者的 RUID 或 EUID 与接收者的 RUID 或 SUID 匹配时,非特权进程才能向另一个进程发送信号。子进程从父进程继承凭据,因此它们可以相互发送信号。EUID
: 用于大多数访问权限检查,并作为进程创建的文件的所有者。非特权进程只能将其 EUID 更改为其 SUID 或 RUID。SUID
: 当以提升的权限运行的进程需要暂时降低其权限时,使用 SUID。该进程将其 EUID(通常是 root)更改为非特权的 UID,并将其特权 EUID 复制到 SUID。稍后,该进程可以通过将其 EUID 重置回保存的 UID 来恢复其提升的权限。
对于如下四个函数:
string procStatus();
string procStat();
string threadStat();
string exePath();
它们内部均使用 FileUtil::readFile()
函数读取指定的文件,以获取其文件内容。
int ProcessInfo::maxOpenFiles() {
struct rlimit rl;
if (::getrlimit(RLIMIT_NOFILE, &rl)) {
return openedFiles();
} else {
return static_cast<int>(rl.rlim_cur);
}
}
函数 maxOpenFiles()
是为了查询进程允许打开的最大文件数目。其中,结构体 rlimit
的定义如下:
struct rlimit {
rlim_t rlim_cur;
rlim_t rlim_max;
};
rlim_cur
称为软上限,用来表示内核强加给相应资源的限制值;rlim_max
则称为硬上限,用来表示软限制的最大值。
如果 getrlimit(2)
调用成功,则返回软限制,否则调用 opendFiles()
返回目前打开的文件数量。
Exception
Exception.h
文件中声明了类类型 Exception
,它继承于 std::exception
,主要用于异常处理,提供异常信息和堆栈信息。
其构造函数和析构函数定义如下:
Exception::Exception(string what)
: message_(std::move(msg))
, stack_(CurrentThread::stackTrace(false)) {}
Exception::~Exception() noexcept override = default;
该类内部定义了两个成员变量 message_
和 stack_
,分别用于存储异常信息和堆栈信息。
而在构造函数中的成员初始化列表中,使用 std::move
减少了不必要的开销,提高了程序效率。
而对于析构函数中的 noexcept
关键字,则是为了告诉编译器被修饰的函数不会发生异常,有利于编译器对程序做更多的优化操作。它会在编译期完成声明和检查工作,主要解决的问题是减少运行时的开销。如果不添加该关键字,那么编译器需要额外生成一些代码来包裹原始代码,当出现异常时可以抛出一些相关的堆栈信息,其中包含错误位置、错误原因、调用顺序、层级路径等信息。在添加该关键字后,编译器就无需生成这些额外代码,减少生成文件的大小,间接的优化了程序的运行效率。
FileUtil
在 FileUtil
命名空间中主要定义了两个类类型:ReadSmallFile
和 AppendFile
。前者用于读取小文件,后者用于追加日志。
ReadSmallFile
适用于小文件的读取,最大支持 64 KB。内部定义了三个成员变量:fd_
、err_
、buf_
,分别用于存储要打开的文件描述符、错误码以及文件内容缓冲区。
成员函数 readToString()
和 readToBuffer()
分别用于将文件内容读入 string 或 const char* 中,以兼容 C 和 C++ 风格的字符串。
在类外,则定义了全局模板函数 readFile()
,用于简化 ReadSmallFile
类的操作,方便使用。