说到查询进程的启动时间,你的第一反应肯定是ps -p $pid -o lstart
,但是ps
命令通常会通过访问 proc
文件系统来收集进程的信息,它本身是一个外部命令,执行时需要进行进程管理、格式化输出等额外的操作,这会消耗更多的 CPU 和 I/O 资源。每次执行 ps
命令时,它都会启动一个新的进程来执行命令,并且在执行过程中会对多个系统资源进行访问,可能会影响系统性能,尤其是高频繁调用时。
而直接读取 /proc/$pid/stat
文件是内核级别的操作,效率高,不需要额外的进程管理或命令解析开销。通过直接访问 /proc
文件系统,可以快速获得进程信息,尤其是在需要高频率查询进程信息时,它比 ps
命令更具优势。
在 Linux 系统中,/proc/[pid]/stat
文件包含了关于进程的各种信息,其中包括进程的启动时间。你可以通过解析 stat
文件中的某些字段来计算进程的启动时间,并将其转换为 date
格式。
如何通过/proc获取进程启动时间
步骤一:从 /proc/uptime
获取进程的启动时间
uptime_seconds =awk '{print $1}' /proc/uptime
获取系统的运行时间(单位:秒)(从os启动到现在有多少秒 )
1.1 获取启动时间(jiffies)
首先,读取 /proc/[pid]/stat
文件中的第 22 个字段,这个字段表示进程的启动时间(jiffies)。每个 jiffie
是系统时钟的一个单位,通常等于 1/100 秒(在某些系统上,可能是 1/250 秒,但大多数 Linux 系统使用 1/100 秒),得到进程自系统启动以来的时钟滴答(jiffies)数。
步骤二:获取系统的时钟频率
系统时钟频率(也称为 CLK_TCK)指的是每秒钟的时钟滴答数。在大多数 Linux 系统上,时钟频率为 100(即每秒 100 个 jiffies)。你可以通过 getconf CLK_TCK
来获取当前系统的时钟频率。
2.1 获取时钟频率
你可以使用 getconf CLK_TCK
命令来获取系统的时钟频率:
clk_tck=$(getconf CLK_TCK)
步骤三:计算进程启动的时间
要计算进程的启动时间,可以使用以下公式:
process_start_time_seconds = (启动的 jiffies) / (每秒的时钟滴答数)
$uptime_seconds - $process_start_time_seconds
$uptime_seconds - $process_start_time_seconds
这个结果将给出从系统启动以来,进程启动的秒数。
系统启动时间和进程启动时间的参考时间点是一致的:它们都是相对于系统的启动时刻。
利用这个共同的时间点,通过两者之间的差值就可以计算出进程的启动时间。
/proc/uptime
提供的是系统的总运行时间,因此它可以作为整个系统的“基准时钟”
而/proc/$pid/stat
提供的是进程在该系统上的启动时刻(相对于系统启动),所以通过它们的差值来计算进程的启动时间
步骤四:转换为实际日期格式
使用 date
命令将秒数转换为人类可读的日期格式。
absolute_start_time_seconds=$(echo "$(date +%s) - $uptime_seconds + $process_start_time_seconds" | bc)
示例代码
以下是一个脚本,演示了如何通过 /proc/[pid]/stat
获取进程的启动时间,并将其转换为实际的日期和时间格式:
#!/bin/bash# 输入进程的 PID
pid=$1# 获取系统的运行时间(单位:秒)
uptime_seconds=$(awk '{print $1}' /proc/uptime)# 获取进程的启动时间(jiffies)
start_jiffies=$(awk '{print $22}' /proc/$pid/stat)# 获取系统的时钟频率(每秒的时钟滴答数)
clk_tck=$(getconf CLK_TCK)# 计算进程的启动时间(单位:秒)
process_start_time_seconds=$(echo "$start_jiffies / $clk_tck" | bc)# 计算进程的绝对启动时间
absolute_start_time_seconds=$(echo "$(date +%s) - $uptime_seconds + $process_start_time_seconds" | bc)# 转换为日期格式
start_date=$(date -d @$absolute_start_time_seconds "+%Y-%m-%d %H:%M:%S")echo "Process $pid started at: $start_date"
解释:
awk '{print $22}' /proc/$pid/stat
:读取进程的启动时间(jiffies)。getconf CLK_TCK
:获取系统的时钟频率(即每秒的 jiffies 数量)。awk '{print $1}' /proc/uptime
:读取系统的 uptime(系统启动后的总时间,单位为秒)。bc
:用于浮点运算计算进程启动的秒数。absolute_start_time_seconds=$(echo "$(date +%s) - $uptime_seconds + $process_start_time_seconds" | bc)
:计算进程的绝对启动时间。用当前时间戳(date +%s)减去系统启动时间,再加上进程的启动时间,得到进程的绝对启动时间。start_date=$(date -d @$absolute_start_time_seconds "+%Y-%m-%d %H:%M:%S")
:将绝对启动时间转换为可读的日期格式。
步骤五:运行脚本
假设你有一个进程的 PID,可以像下面这样运行脚本:
./get_process_start_time.sh 12345
这个命令会输出类似下面的内容:
Process 12345 started at: 2024-11-01 10:35:15
总结:
/proc/[pid]/stat
中的第 22 个字段提供了进程的启动时间(以 jiffies 为单位)。- 使用
getconf CLK_TCK
获取系统时钟频率,确定每秒的 jiffies 数量。 - 使用
/proc/uptime
来获取系统的启动时间,从而计算进程的启动时间。 - 使用
date
命令将计算出的秒数转换为标准的日期时间格式。
这种方法能够有效地查询进程的启动时间并将其转换为可读的格式。
上述讲解如果获取pid的启动时间,但是实际场景我们只知道进程的名称,如何通过进程名称拿到pid呢,你的第一反应肯定是ps -ef | grep 进程名称,nonono,我们说明不建议使用ps,那么是否有其他方式呢?
pgrep
和 /proc/$pid/cmdline
获取进程 PID
1. pgrep
命令
简介
pgrep
是一个用来查找匹配指定条件的进程,并返回它们的 PID(进程 ID)的命令。它能够快速定位进程,并返回一个或多个 PID。
常见用法:
pgrep <pattern>
:通过进程名称(或匹配模式)查找 PID。pgrep -f <pattern>
:在整个命令行中查找进程,而不仅仅是进程名称。pgrep -o <pattern>
:只返回最早的 PID。pgrep -n <pattern>
:只返回最近的 PID。
示例:
# 查找所有名为 'python' 的进程 PID
pgrep python# 查找所有包含 'python /usr/bin/cinder-volume' 命令的进程
pgrep -f "python /usr/bin/cinder-volume"# 获取名为 'cinder-api' 的进程 PID
pgrep cinder-api
解析:
pgrep
会返回匹配到的进程的 PID。如果没有匹配到,返回空值。- 默认情况下,
pgrep
只会匹配进程名称(argv[0]
),即进程的命令名。 - 使用
-f
选项时,pgrep
会匹配进程的完整命令行(包括命令行参数),而不仅仅是进程名称。 pgrep
可以根据进程名、父进程 ID、用户、会话等信息进行过滤,支持正则表达式。
优点:
- 简洁高效:
pgrep
是专门用来查询进程 PID 的工具,使用简单,能够快速找到进程。 - 支持正则:可以用正则表达式灵活匹配进程。
- 无需手动处理
/proc
目录:pgrep
内部实现已经处理了/proc
的细节,用户只需要关心进程名称或命令行即可。
缺点:
- 可能有误匹配:如果进程名中有多个类似的进程,可能会返回多个 PID。例如,如果有两个进程名称相同的进程,
pgrep
会返回它们的所有 PID,可能需要后续处理来精确筛选。 - 限制性:
pgrep
仅通过进程名称或者命令行来匹配进程,无法像/proc
目录那样查询其他进程信息(例如启动时间、内存占用等)。
2. /proc/$pid/cmdline
方式
简介
Linux 系统中每个进程在 /proc
目录下都有一个对应的目录,目录名即为进程的 PID。每个进程目录下都有一个 cmdline
文件,记录了启动该进程时的命令行参数。通过读取 cmdline
文件,结合进程的 PID,可以知道该进程是如何启动的。
常见用法:
- 通过
/proc/[pid]/cmdline
来获取进程的命令行信息。 - 通过遍历
/proc
目录来查找符合条件的进程。
示例:
# 获取进程 1234 的命令行信息
cat /proc/1234/cmdline# 查找所有进程的命令行,并筛选出包含 'python' 的进程
for pid in /proc/[0-9]*; doif [[ -f $pid/cmdline && $(cat $pid/cmdline) == *"python"* ]]; thenecho "Found PID: $(basename $pid)"fi
done
解析:
/proc/$pid/cmdline
文件包含该进程启动时的命令行字符串,多个参数之间用 null 字符 (\0
) 分隔。- 读取
cmdline
文件可以了解进程的完整命令行,包括程序名称和所有传递的命令行参数。 - 可以通过遍历
/proc
目录下的每个进程目录(/proc/[pid]
)来检查进程的命令行,查找符合条件的进程。
优点:
- 详细信息:
cmdline
包含完整的命令行参数,可以获取进程启动时的完整命令(包括路径和参数)。 - 精确控制:通过直接操作
/proc
,可以灵活地过滤和查找进程,支持更复杂的查询条件。 - 无需依赖外部工具:通过直接访问
/proc
,可以不依赖于pgrep
等外部命令。
缺点:
- 性能问题:遍历
/proc
目录可能会导致较高的 I/O 开销,尤其是在系统进程较多时。 - 需要处理
null
字符:/proc/$pid/cmdline
中的命令行参数由null
字符分隔,因此需要特别处理。例如,使用cat
会输出\0
字符,可能需要进一步处理。 - 复杂性较高:与
pgrep
命令相比,通过/proc
查找进程需要手动遍历/proc
目录并对cmdline
文件进行解析,代码实现稍复杂。
3. pgrep
vs /proc/$pid/cmdline
性能:
pgrep
在实现上已经做了很多优化,直接使用命令行参数匹配,通常会比遍历/proc
目录查找 PID 更高效。/proc/$pid/cmdline
方法需要遍历系统中的所有进程,并读取每个进程的cmdline
文件,对于大量进程的系统来说性能开销较大。
精确度:
pgrep
适合简单的匹配任务,如果仅仅是通过进程名查找进程 PID,pgrep
更简洁。/proc/$pid/cmdline
方法则能提供更精确的控制,尤其在进程名称不唯一的情况下,或者需要检查进程的启动参数时,/proc
方式更灵活。
易用性:
pgrep
作为一个专门的命令,使用起来非常简便。它提供了正则表达式支持,可以快速查找进程。/proc/$pid/cmdline
需要编写脚本来遍历/proc
目录并解析cmdline
文件,使用起来相对复杂。
使用场景:
pgrep
:如果只是通过进程名或命令行模式查找 PID,pgrep
是最简单直接的工具,适合大部分简单任务。/proc/$pid/cmdline
:如果你需要深入解析进程的启动命令,或者进程名不唯一需要通过命令行参数进行更精确的筛选,/proc
方式更适用。
总结
pgrep
是一个简单、快速、有效的工具,适合根据进程名称或命令行模式快速查找进程 PID,适用于大多数场景。/proc/$pid/cmdline
提供了更高的灵活性和精确度,适合需要访问进程命令行参数的场景,但性能开销较大,使用起来也更为复杂。
两者根据具体需求选择,通常如果只是查找进程 PID,pgrep
更加高效和简洁;而如果需要深入获取进程的命令行信息,/proc
方式更适合。
/proc/uptime
/proc/uptime
是 Linux 系统中的一个伪文件,用于提供系统启动以来的运行时间(包括空闲时间和总运行时间)。它位于 /proc
文件系统中,是一个很有用的文件,可以用来获取系统的启动时间、空闲时间等相关信息。
/proc/uptime
文件格式
/proc/uptime
文件的内容通常如下所示:
12345.67 8901.23
这里有两个值,用空格分开:
-
第一个值:系统的总运行时间,单位是秒。
- 这个值表示系统自启动以来的时间(包括系统空闲时间),即从系统启动到当前时间的总时长。
-
第二个值:系统空闲时间,单位也是秒。
- 这个值表示自系统启动以来,所有 CPU 的空闲时间的累计时间(包括系统空闲和用户空闲的时间)。
/proc/uptime
详细解析
-
第一个字段:系统的总运行时间(uptime)
- 意义:这是系统从启动时刻开始,到当前时刻所经过的总时间(包括空闲时间和非空闲时间)。可以理解为整个系统的“活跃时间”。
- 单位:秒。
- 例如:
12345.67
,表示系统从启动到当前的时间是 12345.67 秒。
-
第二个字段:系统空闲时间(idle time)
- 意义:这个字段表示从系统启动到当前时刻,所有 CPU 核心空闲的累计时间。换句话说,这个时间表示系统中 CPU 在空闲状态下的总时间。
- 单位:秒。
- 例如:
8901.23
,表示自系统启动以来,CPU 处于空闲状态的时间为 8901.23 秒。
如何使用 /proc/uptime
获取系统信息
-
系统运行时间:第一个字段的值表示系统的总运行时间,可以用来计算系统的“活跃”时间。
-
系统空闲时间:第二个字段的值表示系统的空闲时间,可以用来了解系统的负载和空闲情况。
计算系统的“活动”时间
/proc/uptime
提供的第一个字段可以直接表示系统自启动以来的运行时间。这个时间是总运行时间,包括了 CPU 在空闲时和活动时的时间。因此,如果需要查看系统的运行状态、负载、空闲情况时,uptime
是一个非常重要的参考。
示例
- 查看系统总运行时间和空闲时间
cat /proc/uptime
假设返回的内容为:
12345.67 8901.23
- 系统总运行时间:12345.67 秒
- 系统空闲时间:8901.23 秒
- 查看系统的活动时间
如果你想了解系统的活动时间,可以通过以下公式计算:
active_time=$(awk '{print $1 - $2}' /proc/uptime)
echo "Active time: $active_time seconds"
这里,$1
是系统总运行时间,$2
是空闲时间。通过计算这两者的差值,你就可以得到系统的“活动时间”。
计算系统启动时间
/proc/uptime
中的第一个字段还可以帮助你计算系统的启动时间。假设当前时间戳是 $(date +%s)
(即从1970年1月1日到当前的秒数),系统的启动时间戳可以通过以下公式计算:
startup_time=$(echo "$(date +%s) - $(awk '{print $1}' /proc/uptime)" | bc)
startup_date=$(date -d @$startup_time)
echo "System startup time: $startup_date"
这里,我们首先使用 date +%s
获取当前时间戳,再用 /proc/uptime
中的第一个字段(系统总运行时间)减去,得到系统的启动时间戳,然后用 date -d @$startup_time
将时间戳转换为可读的日期格式。
应用场景
-
系统监控:你可以利用
/proc/uptime
来查看系统是否在长时间运行,尤其是当你想监控一个系统的健康状况时,了解系统是否重启或者空闲状态。 -
负载分析:通过对比系统的空闲时间和总运行时间,可以推测系统的负载状态。较高的空闲时间可能表明系统负载较轻,较低的空闲时间则可能表明系统负载较高。
-
计算系统启动时间:如上所述,
/proc/uptime
提供了计算系统启动时间的关键信息,尤其是在系统管理和调试中很有用。
总结
/proc/uptime
是一个非常有用的文件,它提供了系统的总运行时间和空闲时间。- 可以使用该文件的信息来监控系统的负载、计算系统的启动时间等。
- 理解
/proc/uptime
中的两个字段的含义有助于你更好地分析和理解系统的状态。
/proc/$pid/stat
/proc/$pid/stat
是 Linux 中一个非常重要的文件,它包含了当前进程的各种状态和资源使用情况。每个进程都有一个对应的 /proc/$pid/stat
文件,其中包含了该进程的多种信息,包括它的运行时间、内存使用情况、CPU 使用情况等。
/proc/$pid/stat
文件格式
/proc/$pid/stat
文件的内容通常包括多达 50 个字段,每个字段用空格分隔。这里简要介绍最常用的字段,重点讲解 utime
和 stime
字段。
/proc/$pid/stat
中的字段
pid (comm) state ppid pgrp session tty_nr tpgid flags minflt cminflt majflt cmajflt utime stime cutime cstime priority nice num_threads itrealvalue starttime vsize rss rsslim startcode endcode startstack kstkesp kstkeip signal blocked sigignore sigcatch wchan nswap cnswap exit_signal processor rt_priority policy delayacct_blkio_ticks guest_time cguest_time
- pid: 进程的 PID(进程ID)。
- comm: 进程的名称(括号中的内容)。
- state: 进程的状态,常见的有:
R
: 运行S
: 睡眠D
: 不可中断的睡眠Z
: 僵尸T
: 停止W
: 内存交换中
- ppid: 父进程的 PID。
- pgrp: 进程组 ID。
- session: 会话 ID。
- tty_nr: 进程的终端设备号。
- tpgid: 进程组的进程号。
- flags: 进程标志位,表示进程的特性。
- minflt: 进程自启动以来发生的最小页面错误次数。
- cminflt: 进程的子进程自启动以来发生的最小页面错误次数。
- majflt: 进程自启动以来发生的重大页面错误次数。
- cmajflt: 进程的子进程自启动以来发生的重大页面错误次数。
- utime: 进程在用户态的 CPU 时间(单位:jiffies,时钟滴答)。表示进程在用户空间执行的时间,不包括内核空间的时间。
- stime: 进程在内核态的 CPU 时间(单位:jiffies,时钟滴答)。表示进程在内核空间执行的时间,不包括用户空间的时间。
- cutime: 子进程在用户态的 CPU 时间(单位:jiffies)。
- cstime: 子进程在内核态的 CPU 时间(单位:jiffies)。
- priority: 进程的调度优先级。
- nice: 进程的 nice 值,影响进程的优先级。
- num_threads: 进程的线程数。
- itrealvalue: 进程的定时器过期时间。
- starttime: 进程启动时的时间(单位:jiffies)。
- vsize: 进程的虚拟内存大小(单位:字节)。
- rss: 进程的常驻内存集大小(单位:页面数)。
- rsslim: 进程的最大内存限制(单位:字节)。
- startcode: 进程代码段的起始地址。
- endcode: 进程代码段的结束地址。
- startstack: 进程堆栈段的起始地址。
- kstkesp: 进程内核栈的栈指针。
- kstkeip: 进程内核栈的指令指针。
- signal: 进程接收的信号。
- blocked: 进程阻塞的信号。
- sigignore: 进程忽略的信号。
- sigcatch: 进程捕获的信号。
- wchan: 进程阻塞时的内核函数地址。
- nswap: 进程交换出去的页面数量。
- cnswap: 子进程交换出去的页面数量。
- exit_signal: 进程退出时发送的信号。
- processor: 进程运行的 CPU 核心编号。
- rt_priority: 进程的实时优先级。
- policy: 进程的调度策略。
- delayacct_blkio_ticks: 进程的块 I/O 延迟时间(单位:时钟滴答)。
- guest_time: 进程在模拟的虚拟机中运行的时间(单位:jiffies)。
- cguest_time: 子进程在模拟的虚拟机中运行的时间(单位:jiffies)。
utime
和 stime
字段的意义
-
utime:表示进程在用户空间执行的时间。它是进程执行用户代码所消耗的 CPU 时间,单位为 jiffies(时钟滴答)。每个 jiffy 的长度取决于系统的时钟频率(
getconf CLK_TCK
)。通常情况下,utime
可以反映进程的 CPU 使用量(除去内核部分)。 -
stime:表示进程在内核空间执行的时间。它是进程执行内核代码所消耗的 CPU 时间,单位也为 jiffies。通常,这个时间会比较短,除非进程进行大量的内核空间操作。
这两个字段分别表示用户态和内核态的 CPU 时间,它们的合计给出了进程的总 CPU 使用时间。
为什么 utime
和 stime
不等于进程的启动时间?
utime
和 stime
字段并不是表示进程的启动时间,而是表示进程在用户态和内核态的 CPU 时间消耗。它们指的是从进程启动以来,进程实际占用 CPU 时间的总和。
utime
:表示进程在用户态(执行用户代码)消耗的 CPU 时间。stime
:表示进程在内核态(执行系统调用、内核代码)消耗的 CPU 时间。
utime
和 stime
:表示的是进程的 CPU 使用时间,累积了进程在用户态和内核态的时间。它并不能直接告诉你进程的启动时间,而是进程执行时消耗的 CPU 时间。
utime
和 stime
的计算不考虑进程的 等待时间,比如:如果进程在等待 I/O 操作、睡眠、被暂停等,它的 CPU 时间 不会增加,但它的 实际启动时间(通过 ps -p $pid -o lstart
获取)仍然可以准确反映进程的启动时刻。