Linux 系统由于其开放性的优势,近年来在易用性,稳定性和市场占有率上获得了一定的进步。Linux 提供了一个特殊而强大的伪文件系统 proc。在设计之初,proc 的目的是用户能够方便快捷的访问进程。经过长时间的发展,proc 文件系统已经可以提供查询系统状态,例如 CPU 状态,更改系统参数等多方面的功能。
在绝大多数的 Linux 发行版中,/proc 文件系统在系统启动时缺省挂载到目录 /proc,作为操作系统和应用进程的接口。用户和应用程序可以通过 proc 得到系统的信息,并可以改变内核的某些参数。/proc 目录中包含了许多特殊文件以允许对驱动和内核信息进行高层访问。只要用户或应用程序有正确的访问权限,它们就可以通过读写这些文件来获得信息或设置参数。由于 Linux 系统的状态,如进程占用的资源,是动态改变的,所以用户或应用程序读取 proc 文件时,proc 文件系统是动态从系统内核读出所需信息并提交的。当系统重启或者电源关闭的时候,/proc 系统中原有的数据和信息将全部丢失。但在下次系统启动时会重新建立。下面会介绍与 CPU 直接相关的两个 /proc 文件,cpuinfo 和 loadavg。
文件 /proc/cpuinfo 存放了 Linux 系统的 CPU 信息。随着 Linux 发行版,CPU 的类型、设置的不同,该文件的内容会有比较大的差异。有些发行版本的 cpuinfo 文件包含了诸如 CPU 步进 (stepping),制造厂商等详细的 CPU 硬件信息。而在一部分发行版中,该文件仅包含了每个处理器的基本信息,比如时钟频率。需要用户注意的是,在使用了 SMT 技术的系统上,/proc/cpuinfo 显示的是逻辑处理器的个数和属性。
清单 7. 在 IBM JS BladeCenter 上读取 /proc/cpuinfo
# cat /proc/cpuinfo
processor : 0
CPU : POWER6 (raw), altivec supported
clock : 4005.000000MHz
revision : 3.1 (pvr 003e 0301)
processor : 1
CPU : POWER6 (raw), altivec supported
clock : 4005.000000MHz
revision : 3.1 (pvr 003e 0301)
processor : 2
CPU : POWER6 (raw), altivec supported
clock : 4005.000000MHz
revision : 3.1 (pvr 003e 0301)
processor : 3
CPU : POWER6 (raw), altivec supported
clock : 4005.000000MHz
revision : 3.1 (pvr 003e 0301)
processor : 4
CPU : POWER6 (raw), altivec supported
clock : 4005.000000MHz
revision : 3.1 (pvr 003e 0301)
processor : 5
CPU : POWER6 (raw), altivec supported
clock : 4005.000000MHz
revision : 3.1 (pvr 003e 0301)
processor : 6
CPU : POWER6 (raw), altivec supported
clock : 4005.000000MHz
revision : 3.1 (pvr 003e 0301)
processor : 7
CPU : POWER6 (raw), altivec supported
clock : 4005.000000MHz
revision : 3.1 (pvr 003e 0301)
clock : 4005.000000MHz
revision : 3.1 (pvr 003e 0301)
timebase : 512000000
platform : pSeries
model : IBM,7998-61X
machine : CHRP IBM,7998-61X
Linux 支持众多的硬件类型,并且有很多有差异的发行版。没有统一的命令可以得到或查询 SMT 的状态。但在基于 Power 核心的 IBM 服务器上,可以使用 ppc_64 命令。
清单 8. 使用 ppc_64 命令获得 SMT 的设置
# ppc64_cpu --smt
smt is on
这条命令同样可以用来改变 SMT 的设置,详细用法请参阅命令说明。
在了解到 SMT 被开启以后分析 /proc/cpuinfo 文件的内容。可以看到系统中共有八个逻辑处理器 (Logical CPU),所以物理处理器的个数是 8 / 2 = 4。它们都是 IBM pSeries 系列的 Power 6 处理器,支持 Altivec 指令集,运行于 4GHz,修订版本为 3.1,基准时钟频率 (time base) 为 512MHz。在文件最后的内容表示了机器的硬件类型 7998-61X。查询 IBM 网站可知这是 IBM BladeCenter JS22 的刀片服务器。
因为 IBM 的微分区技术构建于 Linux 系统之下,所以为了获得微分区的被分配的物理处理器个数,需要访问 HMC(Hardware Management Console) 或 AMM(Advanced Management Module)。具体使用可参阅本文的 AIX 部分或者 IBM 的使用说明。
从 2.6.20 开始,Linux 内核引入了 KVM (Kernel-based Virtual Machine) 作为标准的虚拟化解决方案。在 KVM 中 /proc/cpuinfo 的内容会与宿主机不同,一些情况下可以帮助用户知道当前的系统是否是 KVM 下的虚拟环境
清单 9. 在 KVM 的虚拟机中读取 /proc/cpuinfo
# cat /proc/cpuinfo
processor : 0
vendor_id : GenuineIntel
CPU family : 6
model : 2
model name : QEMU Virtual CPU version 0.9.1
stepping : 3
CPU MHz : 2660.129
cache size : 2048 KB
fdiv_bug : no
hlt_bug : no
f00f_bug : no
coma_bug : no
fpu : yes
fpu_exception : yes
CPUid level : 2
wp : yes
flags : fpu de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36
clflush mmx fxsr sse sse2 nx lm up pni
bogomips : 5326.47
在模式名 (model name) 中可以知道,本机使用了基于 QEMU 模拟器的虚拟处理器。
IBM 的 System x 系列服务器使用了不同的硬件类型,用户可以仅通过读取 /proc/cpuinfo 的内容获得包括 SMT 等的系统设置。
获得系统处理器配置的代码示例
下面的例子代码适用于使用 Power CPU 或者 Intel CPU 的 Linux 系统,并在 Redhat 企业服务器版和 Suse 企业服务器版上测试通过。它首先运行命令 arch 判断当前系统的运行平台,并读取 /proc/cpuinfo 文件。在 Power 平台上调用特有的 ppc64_cpu 命令获得 SMT 配置,并读取 /proc/ppc64/lparcfg 文件以获得 CPU 的运行模式;在 System x 系列服务器上则是通过解析文件 /proc/cpuinfo 的内容来获得 SMT 配置。代码的最后依次输出了系统中 CPU 资源的数量、配置、运行模式和类型。
清单 10. 获取系统 CPU 资源配置的代码示例
# 得到 /proc/cpuinfo 文件的内容
my $cpu_file = /proc/cpuinfo;
open FILE, <$cpu_file or die Failed to open $cpu_file.\n;
my @contents = <FILE>;
close FILE;
my ($smt, $dedicate);
# Power 服务器上运行的 Linux 发行版有自己的命令和配置文件
my $arch = `arch`;
if ($arch =~ /ppc64/){
# 得到 Power 服务器上的 SMT 配置
my $output = `ppc64_cpu --smt`;
if ($output =~ m/off/){
$smt = off;
}elsif ($output =~ m/on/){
$smt = on;
}else{
die Command ppc64_cpu does not work well. Please reinstall powerpc-utils
to fix this issue.\n;
}
# 从 lparcfg 文件读出处理器运行模式
my $cfg_file = /proc/ppc64/lparcfg;
open FILE, <$cfg_file or die Failed to open $cfg_file.\n;
my @cfgs = <FILE>;
close FILE;
foreach my $line (@cfgs){
if ($line =~ /shared_processor_mode=0/){
$dedicate = 1; # dedicated CPU
last;
}
}
}
# 解析 /proc/cpuinfo 文件的内容
my $proc_num = 0;
my $line_num = 0;
my (@physical_ids, $latest_proc_line_num);
foreach my $line (@contents){
$line_num ++;
chomp $line;
# 得到虚拟处理器的个数
if ($line =~ /processor\s+: \d/){
$proc_num ++;
$latest_proc_line_num = $line_num;
}
if ($arch =~ /86/ && $line =~ /physical id/ && !grep (/^\Q$line\E$/, @physical_ids)){
push @physical_ids, $line;
}
}
# 得到 x86 构架系统的 SMT 配置
my $phy_proc_num = scalar(@physical_ids);
if ($arch =~ /86/ && ($proc_num > $phy_proc_num)){
$smt = on;
}elsif ($arch =~ /86/ && ($proc_num == $phy_proc_num)){
$smt = off;
}
# 得到 Power 服务器的虚拟处理器个数
my $vir_proc_num;
if ($arch =~ /ppc64/ && $smt eq on){
$vir_proc_num = $proc_num/2;
}elsif ($arch =~ /ppc64/ && $smt eq off){
$vir_proc_num = $proc_num;
}
# 输出处理器个数
if ($arch =~ /86/){
print There exist $phy_proc_num physical processors and $proc_num logical
processors in the system.\n;
}elsif ($arch =~ /ppc64/){
print There exist $vir_proc_num virtual processors and $proc_num logical
processors in the system.\n;
}
# 输出 SMT 配置
if ($smt eq on){
print SMT is $smt on this machine.\n;
}else{
print SMT is $smt on this machine. This means potential performance issue in some
applications. Please make sure thats the configuration you want.\n;
}
# 输出处理器运行模式
if ($arch =~ /ppc64/ && $dedicate){
print System processors are dedicated.\n;
}elsif ($arch =~ /ppc64/ && !$dedicate){
print System processors are shared.\n;
}
# 输出 /proc/cpuinfo 中的处理器关键信息
for (my $i = $latest_proc_line_num; $i < scalar(@contents); $i ++){
print $contents[$i]\n;
使用 Linux 系统接口分析处理器的工作状态
因为 Linux 系统特有的开放性,用户可以自主的添加,定义或者改变系统原有的功能。有很多开源项目提供了基于图形的或者命令行的工具,帮助用户更加方便的识别、监控甚至改变系统中的处理器的运行状态。考虑到通用的原则,本文仅介绍常见 Linux 发行版本中已经集成的接口或者工具。
系统处理器负载的查询和分析
查看 /proc/stat 文件获得 CPU 状态
/proc/stat 的内容包含了系统中的各项基本信息,例如 CPU 状态。用户可以详细了解系统中每个处理器自系统启动以来的运行状态,并以此分析和评估应用环境的工作是否正常。
清单 11. 读取 /proc/stat 获得 CPU 的使用情况
# cat /proc/stat
cpu 507853 325 148706 190270813 190032 152 884 0 0
cpu0 178463 111 68890 95035802 93558 34 96 0 0
cpu1 329389 213 79816 95235010 96474 117 787 0 0
intr 93566218 3310 2 0 0 2 0 3 0 30 0 0 0 4 0 1623929 10015567 2440 20098 0 0 0 0 0 0 0
3712917 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0
…
…
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
ctxt 202868862
btime 1242974927
processes 356350
procs_running 1
procs_blocked 0
命令输出的最初几行包含了系统中所有 CPU 的活动信息,在 kernel2.6.29 下的 /proc/stat 中,每行内容的处理器名后有九个数字,而早期的 kernel 会缺失一些内容,比如与虚拟化相关的信息。所有这些数值都是从系统启动开始累计到当前时刻的,并以 0.01 秒为单位。以下是这些数值的解释
user: 用户模式占用的 CPU 时间
nice: 低优先级用户模式所占用的 CPU 时间
system: 系统核心态占用的 CPU 时间
idle:cpu 空闲时间
iowait:硬盘 IO 等待时间 ( 从 kernel 2.5.41 开始添加此项信息 )
irq:硬中断时间 ( 丛 kernel 2.6.0-test4 开始添加此项信息 )
softirq:软中断时间(同上)
steal:在虚拟环境下,处理其他作业系统占用的 cpu 时间(丛 kernel 2.6.11 开始添加此项信息)
guest: 作业系统运行虚拟 cpu 占用 cpu 的时间(丛 kernel2.6.24 开始添加此项信息)
根据 /proc/stat 文件的内容,用户可以自行计算任意时间段内 CPU 的占用率;
CPU 时间 =user+system+nice+idle+iowait+irq+softirq+steal+guest
选取两个时间点 T1 和 T2, CPU 在 T1 和 T2 时间段中的使用时间可以如下计算:
Total=(user2+system2+nice2+idle2+iowait2+irq2+softirq2+steal2+guest2)-( user1+system1+nice1+idle1+iowait1+irq1+softirq1+steal1+guest1)
CPU 空闲百分比:
idle = (idle2 - idle1) / Total * 100%
CPU 占用百分比:
occupy = (1 - idle) * 100%
除了最初的 CPU 行以外,/proc/stat 文件还有其他与系统性能相关的一些参数。其中 ctxt 是系统启动以来 CPU 发生的上下文交换的次数。
读取 /proc/loadavg 文件获得 CPU 状态
文件 /proc/loadavg 给出了特定时间间隔内运行队列的平均进程数。同时也给出了其他的一些状态参数值供内部和外部的系统调用使用。命令 uptime 就读取了该文件以获得 CPU 上的进程负载。
清单 12. 读取 /proc/loadavg 获得处理器上的进程负载
# cat /proc/loadavg
5.86 5.78 5.89 12/840 22261
命令输出前三项分别表示最近 1 分钟,最近 5 分钟及最近 15 分钟运行队列中的平均进程数,第四项包括由斜线隔开的两个数值,前者表示当前正由内核调度的进程和线程的数目,后者表示系统当前存活的进程数目;第五个值表示此文件被查看前,最近一个由内核创建的进程的 PID。
利用 ps 命令
系统处理器的工作是处理任务。而在现代操作系统中,任务是以进程的形式表现的。所以定位分析系统处理器的问题,也需要用到进程相关的接口或指令。ps 命令是分析 CPU 状态的重要工具,它读取 /proc 文件系统中各进程的信息,并且加以显示。用户首先需要定位感兴趣的进程,这可以通过 ps 命令来确定,也可以结合其他系统工具。很多情况下,用户仅需关注某个或某几个进程。
清单 13. 使用 ps 选出占用 CPU 时间最多的五个进程
[root@bldlnx02 ~]# ps -e -o pcpu,pid,user,sgi_p,cmd |grep -v PID| sort -k 1| tail -5
0.0 8 root * [khelper]
0.0 9 root * [kacpid]
0.1 21949 root * sshd:
csm@pts/0
0.2 21951 root * -bash
99.9 12392 root * /usr/bin/Xvnc -inetd -query 127.0.0.1 -once -depth 24
-desktop VNC Session Manager -rfbauth /root/.vnc/passwd -geometry 640x480
系统中通常会有多进程的程序运行。当这些进程占用了较多的 CPU 时间时,可以单独列出该程序的所有进程。多数情况下,用户也需要进程的启动时间,状态,父进程号 (PPID),占用的内存来分析系统和进程的状态。
清单 14. 通过进程名使用 ps 命令返回进程的主要属性
# ps -C nfsd -o cmd,pCPU,start_time,user,ppid,size,stat
CMD %CPU START USER PPID SZ STAT
[nfsd] 0.0 May12 root 1 0 D
[nfsd] 0.0 May12 root 1 0 D
[nfsd] 0.0 May12 root 1 0 D
[nfsd] 0.0 May12 root 1 0 D
[nfsd] 0.0 May12 root 1 0 S
[nfsd] 0.0 May12 root 1 0 S
[nfsd] 0.0 May12 root 1 0 D
[nfsd] 0.0 May12 root 1 0 D
在以上输出中各个字段分别表示如下含义:
CMD 进程命令名
%CPU 进程占用的 CPU 百分比
START 进程启动的时间
USER 进程的属主
PPID 父进程号
SZ 进程占用的内存空间
STAT 进程状态
ps 命令的输出结果表示系统中共有八个 nfs 服务进程,是根用户在一天以前启动的。当前这些进程没有使用 CPU 时间或内存。并且分别处于不同的状态。ps 命令可以接受的状态种类共有七种,仅介绍清单 15 中的两个作为示例
D 无法中断的休眠状态(通常 IO 的进程)
S 处于休眠状态
可以看到有六个 nfs 服务进程处于无法中断的休眠状态,这表示 NFS 服务出现了问题。用户可以通过检查物理存储可用性,查看 /var/log/messages 文件或 sysrq-T 系统 trace 等手段来进一步定位和排查错误。
获得各处理器利用率和耗用处理器时间最多的进程的代码示例
本段的最后给出一段例子代码,可以获得一段时间内的 CPU 利用率,并且找出系统中占用 CPU 较多的进程,已在 Redhat 企业服务器和 Suse 企业服务器上测试通过。代码首先选取两个相隔一分钟的两个时间段,分别读取文件 /proc/stat 的内容,获得 CPU 在两个时刻的进程占用时间等数值来计算 CPU 利用率。并且读取文件 /proc/loadavg 获得处理器上的集成负载。在负载较高的情况下,调用命令 ps 找出消耗 CPU 最多的几个进程,及其所在的处理器(processor)。
清单 15. 获得各处理器的利用率和耗用处理器时间最多的进程代码示例
# 两次读取 /proc/stat 文件的内容
my $file = /proc/stat;
open FILE, <$file or die Failed to open file $file.\n;
my @fir_contents = <FILE>;
close FILE;
sleep 60;
open FILE, <$file or die Failed to open file $file.\n;
my @sec_contents = <FILE>;
close FILE;
# 把 /proc/stat 文件的内容存入一个散列
my (%cpuinfo);
my $cpu_num = 0;
my $line_num = 0;
foreach my $line (@fir_contents, @sec_contents){
$line_num ++;
if ($line =~ m/^cpu/) {
# 判断当前数据来自第一次读取还是第二次读取
my $info_time;
if ($line_num <= scalar(@fir_contents)) {
$info_time = first;
}else {
$info_time = second;
}
# 向散列写入数据
chomp $line;
my ($cpu, $user, $nice, $sys, $idle, $io, $irq, $softirq, $steal, $guest) =
split /\s+/, $line;
$cpuinfo{$info_time}{$cpu}{user} = $user;
$cpuinfo{$info_time}{$cpu}{nice} = $nice;
$cpuinfo{$info_time}{$cpu}{sys} = $sys;
$cpuinfo{$info_time}{$cpu}{idle} = $idle;
$cpuinfo{$info_time}{$cpu}{io} = $io;
$cpuinfo{$info_time}{$cpu}{irq} = $irq;
$cpuinfo{$info_time}{$cpu}{softirq} = $softirq;
$cpuinfo{$info_time}{$cpu}{$steal} = $steal;
$cpuinfo{$info_time}{$cpu}{guest} = $guest;
$cpu_num ++;
}
}
# 读取散列中的数据以计算 CPU 利用率
my (%cpu_data, %cpu_usage);
foreach my $info_time (keys %cpuinfo)
{
foreach my $cpu (keys %{$cpuinfo{$info_time}}) {
$cpu_data{$cpu}{$info_time}{total} = 0;
$cpu_data{$cpu}{$info_time}{idle} = $cpuinfo{$info_time}{$cpu}{idle};
foreach my $key (keys %{$cpuinfo{$info_time}{$cpu}}) {
$cpu_data{$cpu}{$info_time}{total} += $cpuinfo{$info_time}{$cpu}{$key};
}
}
}
my $check_proc;
foreach my $cpu (keys %cpu_data){
my $idle_interval = $cpu_data{$cpu}{second}{idle} -
$cpu_data{$cpu}{fisrt}{idle};
my $total_interval = $cpu_data{$cpu}{second}{total} -
$cpu_data{$cpu}{fisrt}{total};
$cpu_usage{$cpu} = (1 - $idle_interval/$total_interval);
if ($cpu_usage{$cpu} > 0.5) {$check_proc = 1;}
}
# 从 /proc/loadavg 读取处理器上的进程负载
$file = /proc/loadavg;
open FILE, <$file or die Failed to open file $file.\n;
my @contents = <FILE>;
close FILE;
my ($load1, $load5, $load15, $proc_num, $pid);
foreach my $line (@contents) {($load1, $load5, $load15, $proc_num, $pid) = \n
split /\s+/, $line;}
if ($load1 > 5) {$check_proc = 1;}
# 如果处理器空闲,退出
if (!$check_proc){
print CPU load on this machine is normal.\n;
exit 0;
}
# 如果处理器繁忙,找出占用 CPU 时间的进程
my @output = `ps -e -o pcpu,pid,user,sgi_p,cmd |grep -v PID| sort -k 1| tail -10`;
foreach my $line (@output){
$line =~ s/^\s+//g;
my ($cpu, $pid, $user, $proc, $cmd) = split /\s+/, $line;
if ($cpu < 5) {next;}
# 输出进程号和进程占用的 CPU 百分比
print Process $pid of user $user takes $cpu% CPU time on processor $proc.\n;
}
exit 0;
在得到需要关注的进程以后,用户可以进一步查看进程的应用类型和状态。详情可以参考本系列文章之进程管理篇。
小结
在本世纪的第一个十年,计算机中的 CPU 暂时结束了高主频的竞赛,转而向多核心发展。为了真正发挥多核心 CPU 的运算效能,需要将计算任务进行科学的拆分,并且根据不同运算核心的状态有选择的进行分配。这同时需要操作系统和应用程序的良好支持。虽然并行运算的研究已经进行了几十年,但由于其复杂性,实际应用中还会存在系统负载过高,处理器负载不均,程序同步等待时间长等各种问题。应用本文上述的各种工具和手段,可以帮助我们准确的判断异常情况的根源,并采取相应的对策。随着多核系统软硬件技术的发展,程序员将可以花更少的时间写出在更多应用场景中具有更高运算效能的程序,并在将来逐渐解决这些常见的问题。
该贴由koei转至本版2014-5-2 16:13:53