一、进程和线程
进程和线程是什么,勿需多言。在面试或者稍微复杂的程序开发中,都是不可避免的。什么进程是资源最小的分配单元,线程是运行调度的最小单元。相信只要大家明白其中的意思,如何表述,就看具体的侧重的角度了。
但不管如何描述进程和线程,都会有一个至关重要的问题,也就是实际开发经常遇到的一个问题,如何对进程和线程进行管理?特别是很多情况对进程和线程的运行状态或者运行异常需要处理。
二、Linux下对进程和线程的管理控制
在不同的平台上,可能相关的处理的手段有所不同。但基本原理都差不多。本文将以Linux平台为基础进行相关的问题分析。
- 进程的管理
在Linux平台上,原来是只有进程的概念的。而进程既是资源分析的最小单元也是任务运行的最小单元(Posix库才正式引入了线程,当然前面也有轻量级进程的线程,但在内核中始终是轻量级进程的概念)。进程很重,每个进程的调度和运行都占用相当多的资源,这也是为什么现代的操作系统向线程甚至协程发展的原因。进程有自己唯一的标识ID,以此ID获取句柄,就可对进程进行全面的管理。
在内核中,sched.c和sched.h文件中支持了对进程控制管理的大部分相关的数据封装和函数支持。包括创建、执行、终止等状态的管理以及相关状态调度。在前面的内核分析的相关文章中有对此部分的详细说明,但可能相关的代码已经较老,可自行下载最新的内核相关源码进行对照比较分析。
在Linux的终端内,可以使用ps、top、pstree命令或相关的资源管理软件对相关的线程的状态等进行查看和管理,也可能通过kill等命令对其进行杀死,也可以使用nice或bg/fg等调整其优先级和前后台作业。 - 线程的管理
线程是包含在进程之中的,也可以理解进程是线程的容器。是真正执行任务的单元。线程有自己的运行状态,这其中包括寄存器和栈(是否还记得线程默认的栈的大小)等。同一进程的线程可以互相访问,这也是常见的线程同步的技术点所在。与进程一样,每个线程都有自己的线程ID,通过这个唯一的ID,可以获取线程的句柄。通过此句柄就可以实现对线程的管理和相关状态的获取。
两样在源码sched.c和sched.h文件中有对线程管理的相关数据结构和函数处理,有兴趣的可以深入分析一下。而在上层所谓的各种运行状态和接口其实都是直接或间接的对其封装和引用。
在Linux的终端内,可以使用ps -eLf(使用相关的参数还可以查看更多的线程状态)或相关的软件对线程的状态等进行监控。另外Htop也是一个不错的终端命令,对线程管理的更清晰明确。也可能pidstat这个工具来查看指定的线程的应用状态。当然和进程一样也可以使用相关相同的命令来杀死和操作前后台作业及优先级
下面看几个常用的命令:
ps -ef|grep 进程名 #可以使用通配符 ps -aux|grep 进程名 ps -eo ruser,pid,ppid,lwp,psr,args -L | grep <PID> #查看线程在哪个CPU上运行。 ps -Lf <PID> #显示进程ID、线程ID和线程数量(NLWP) pidstat -t -p <PID> #监控特定线程三、相关的API分析
在Linux平台上提供了对进程和线程操作的一系列的API接口。主要有:
- 进程和线程属性的接口
如进程的ID获取接口getpid(),会话管理getsid()和setsid()以及进程组管理getpgid()和setpgid();线程的ID获取接口gettid()。由于在Linux内核中线程和进程几乎可以看成一回事,所以其专有的接口较少。 - 进程和线程信息的接口
进程和线程信息接口的查询相对较少,主要还是通过文件系统进行处理。这会在下面的相关内容中进行分析。当然也可以借助其它一些库如procps来达到相关的目的 - 进程线程应用的接口
进程相关的接口有fork(),clone()、nice(),sched_*一系列调度函数接口以及ptrace()进程跟踪和调试接口。而线程中由大多数与进程相同,但也提供了一些具体如CPU亲和性sched_setaffinity()和 sched_getaffinity()等接口
四、详细信息的获取
虽然说上面的接口可以覆盖大多数的应用场景,但在一些细节控制上,其实Linux提供了更丰富的机制。在前面的学习中知道,Linux中一切皆文件,那么其对进程和线程的管理以及状态控制,都可以通过文件进行。
在Linux系统中,可以通过下面的方式获取相关进程和线程的属性和状态:
- 进程属性和状态
进程的相关内容管理在/proc//stat(机器解析的进程统计数据,用于ps等工具)、proc//status(信息更完整,供人直接查看的进程状态和内存等信息)以及/proc/self/status,它指向当前进程的proc//status。
这里面的内容很全面,而且很好理解,就不再贴上来占用空间和详细分析说明了 - 线程属性和状态
同样也可以在/proc下获取相关线程的内容,/proc/[pid]/task/[tid]/status(获取指定线程的状态信息)、/proc/thread-self(指向目前正在访问/proc的线程自身的信息)与 /proc/tid/(等同于/proc//task//,即直接通过tid进行访问)。
同样,不再将具体的内容复制上来,有兴趣可以自行去查看一下
通过上述的文件内容读取,可以获取进程和线程的属性如ID、名称、状态以及内存占用大小、CPU占用率及IO状态等等。也就是说,在Linux中,关于进程和线程相关的内容都在/proc目录下,大家可以手工的打开相关的文件来查看一下,这样就可以更好的理解相关的详细内容。
五、例程
下面给出一个线程监控的例子:
#include <cstring> #include <dirent.h> #include <filesystem> #include <fstream> #include <iostream> #include <string> #include <tuple> constexpr std::string_view fs_path_proc("/proc/"); std::tuple<bool, pid_t> ActivityMonitor::getAppPidFromProcfsByName(const std::string_view appName) { bool ret = false; pid_t pid = -1; DIR *pDirStream{nullptr}; struct dirent *pDirEntry{nullptr}; // open /proc pDirStream = opendir(fs_path_proc.data()); if (!pDirStream) { LOG(ERROR) << "opendir err,ret:" << ret; return {ret, pid}; } // traverse all directories under /proc while ((pDirEntry = readdir(pDirStream)) != nullptr) { // ignore non numeric values if (pDirEntry->d_type != DT_DIR || !isdigit(pDirEntry->d_name[0])) { continue; } std::string cmdPath = fs_path_proc.data() + std::string(pDirEntry->d_name) + "/cmdline"; std::ifstream cmdFile(cmdPath); std::string cmdLineStr; // read info std::getline(cmdFile, cmdLineStr); cmdFile.close(); // find process name if (cmdLineStr.find(appName) != std::string::npos) { pid = std::stoi(pDirEntry->d_name); ret = true; break; } } closedir(pDirStream); return {ret, pid}; }上面的代码只是给出了/proc路径下通过名字进行获取ID的最终函数,没有给出全部的监控过程。大家有兴趣可以自行扩展一下。
六、总结
通过上面的分析可以知识,在Linux平台上,可以把对进程和线程的管理分为三层即内核层,负责底层的管理操作能力;用户态层,通过各种库或上层应用接口提供了对进线程的系统调用和管理;最后是监控管理层,通过文件系统或相关库为上层提供了进程和线程的整体状态的监控能力。
在明白了上述的情况后,开发者就可以有针对性的根据实际的需求来确定使用哪种机制来实现其目标。
C++编程实践—进程信息管理控制
一、进程和线程
进程和线程是什么,勿需多言。在面试或者稍微复杂的程序开发中,都是不可避免的。什么进程是资源最小的分配单元,线程是运行调度的最小单元。相信只要大家明白其中的意思,如何表述,就看具体的侧重的角度了。
但不管如何描述进程和线程,都会有一个至关重要的问题,也就是实际开发经常遇到的一个问题,如何对进程和线程进行管理?特别是很多情况对进程和线程的运行状态或者运行异常需要处理。
二、Linux下对进程和线程的管理控制
在不同的平台上,可能相关的处理的手段有所不同。但基本原理都差不多。本文将以Linux平台为基础进行相关的问题分析。
- 进程的管理
在Linux平台上,原来是只有进程的概念的。而进程既是资源分析的最小单元也是任务运行的最小单元(Posix库才正式引入了线程,当然前面也有轻量级进程的线程,但在内核中始终是轻量级进程的概念)。进程很重,每个进程的调度和运行都占用相当多的资源,这也是为什么现代的操作系统向线程甚至协程发展的原因。进程有自己唯一的标识ID,以此ID获取句柄,就可对进程进行全面的管理。
在内核中,sched.c和sched.h文件中支持了对进程控制管理的大部分相关的数据封装和函数支持。包括创建、执行、终止等状态的管理以及相关状态调度。在前面的内核分析的相关文章中有对此部分的详细说明,但可能相关的代码已经较老,可自行下载最新的内核相关源码进行对照比较分析。
在Linux的终端内,可以使用ps、top、pstree命令或相关的资源管理软件对相关的线程的状态等进行查看和管理,也可能通过kill等命令对其进行杀死,也可以使用nice或bg/fg等调整其优先级和前后台作业。 - 线程的管理
线程是包含在进程之中的,也可以理解进程是线程的容器。是真正执行任务的单元。线程有自己的运行状态,这其中包括寄存器和栈(是否还记得线程默认的栈的大小)等。同一进程的线程可以互相访问,这也是常见的线程同步的技术点所在。与进程一样,每个线程都有自己的线程ID,通过这个唯一的ID,可以获取线程的句柄。通过此句柄就可以实现对线程的管理和相关状态的获取。
两样在源码sched.c和sched.h文件中有对线程管理的相关数据结构和函数处理,有兴趣的可以深入分析一下。而在上层所谓的各种运行状态和接口其实都是直接或间接的对其封装和引用。
在Linux的终端内,可以使用ps -eLf(使用相关的参数还可以查看更多的线程状态)或相关的软件对线程的状态等进行监控。另外Htop也是一个不错的终端命令,对线程管理的更清晰明确。也可能pidstat这个工具来查看指定的线程的应用状态。当然和进程一样也可以使用相关相同的命令来杀死和操作前后台作业及优先级
下面看几个常用的命令:
ps -ef|grep 进程名 #可以使用通配符 ps -aux|grep 进程名 ps -eo ruser,pid,ppid,lwp,psr,args -L | grep <PID> #查看线程在哪个CPU上运行。 ps -Lf <PID> #显示进程ID、线程ID和线程数量(NLWP) pidstat -t -p <PID> #监控特定线程三、相关的API分析
在Linux平台上提供了对进程和线程操作的一系列的API接口。主要有:
- 进程和线程属性的接口
如进程的ID获取接口getpid(),会话管理getsid()和setsid()以及进程组管理getpgid()和setpgid();线程的ID获取接口gettid()。由于在Linux内核中线程和进程几乎可以看成一回事,所以其专有的接口较少。 - 进程和线程信息的接口
进程和线程信息接口的查询相对较少,主要还是通过文件系统进行处理。这会在下面的相关内容中进行分析。当然也可以借助其它一些库如procps来达到相关的目的 - 进程线程应用的接口
进程相关的接口有fork(),clone()、nice(),sched_*一系列调度函数接口以及ptrace()进程跟踪和调试接口。而线程中由大多数与进程相同,但也提供了一些具体如CPU亲和性sched_setaffinity()和 sched_getaffinity()等接口
四、详细信息的获取
虽然说上面的接口可以覆盖大多数的应用场景,但在一些细节控制上,其实Linux提供了更丰富的机制。在前面的学习中知道,Linux中一切皆文件,那么其对进程和线程的管理以及状态控制,都可以通过文件进行。
在Linux系统中,可以通过下面的方式获取相关进程和线程的属性和状态:
- 进程属性和状态
进程的相关内容管理在/proc//stat(机器解析的进程统计数据,用于ps等工具)、proc//status(信息更完整,供人直接查看的进程状态和内存等信息)以及/proc/self/status,它指向当前进程的proc//status。
这里面的内容很全面,而且很好理解,就不再贴上来占用空间和详细分析说明了 - 线程属性和状态
同样也可以在/proc下获取相关线程的内容,/proc/[pid]/task/[tid]/status(获取指定线程的状态信息)、/proc/thread-self(指向目前正在访问/proc的线程自身的信息)与 /proc/tid/(等同于/proc//task//,即直接通过tid进行访问)。
同样,不再将具体的内容复制上来,有兴趣可以自行去查看一下
通过上述的文件内容读取,可以获取进程和线程的属性如ID、名称、状态以及内存占用大小、CPU占用率及IO状态等等。也就是说,在Linux中,关于进程和线程相关的内容都在/proc目录下,大家可以手工的打开相关的文件来查看一下,这样就可以更好的理解相关的详细内容。
五、例程
下面给出一个线程监控的例子:
#include<cstring>#include<dirent.h>#include<filesystem>#include<fstream>#include<iostream>#include<string>#include<tuple>constexpr std::string_viewfs_path_proc("/proc/");std::tuple<bool,pid_t>ActivityMonitor::getAppPidFromProcfsByName(conststd::string_view appName){bool ret=false;pid_tpid=-1;DIR*pDirStream{nullptr};structdirent*pDirEntry{nullptr};// open /procpDirStream=opendir(fs_path_proc.data());if(!pDirStream){LOG(ERROR)<<"opendir err,ret:"<<ret;return{ret,pid};}// traverse all directories under /procwhile((pDirEntry=readdir(pDirStream))!=nullptr){// ignore non numeric valuesif(pDirEntry->d_type!=DT_DIR||!isdigit(pDirEntry->d_name[0])){continue;}std::string cmdPath=fs_path_proc.data()+std::string(pDirEntry->d_name)+"/cmdline";std::ifstreamcmdFile(cmdPath);std::string cmdLineStr;// read infostd::getline(cmdFile,cmdLineStr);cmdFile.close();// find process nameif(cmdLineStr.find(appName)!=std::string::npos){pid=std::stoi(pDirEntry->d_name);ret=true;break;}}closedir(pDirStream);return{ret,pid};}上面的代码只是给出了/proc路径下通过名字进行获取ID的最终函数,没有给出全部的监控过程。大家有兴趣可以自行扩展一下。
六、总结
通过上面的分析可以知识,在Linux平台上,可以把对进程和线程的管理分为三层即内核层,负责底层的管理操作能力;用户态层,通过各种库或上层应用接口提供了对进线程的系统调用和管理;最后是监控管理层,通过文件系统或相关库为上层提供了进程和线程的整体状态的监控能力。
在明白了上述的情况后,开发者就可以有针对性的根据实际的需求来确定使用哪种机制来实现其目标。