Linux性能分析——Uptime请高手详细讲解一下?
2 个回答
uptime命令看起来非常简单,就一行显示信息,但是要想完全弄明白,还是需要花些功夫的。
uptime 10:34:55 up 904 days, 23:11, 1 user, load average: 0.04, 0.06, 0.08
来翻看一下man手册
uptime gives a one line display of the following information. The current time, how long the system has been running, how many users are currently logged on, and the system load averages for the past 1, 5, and 15 minutes.
uptime提供一行显示以下信息:
- 当前时间(10:34:55)
- 系统运行时间(系统运行多久了)(up 904 days)
- 当前登录用户数(1 user)
- 过去1,5和15分钟的系统load average(load average: 0.04, 0.06, 0.08)
System load averages is the average number of processes that are either in a runnable or uninterruptable state. A process in a runnable state is either using the CPU or waiting to use the CPU. A process in uninterruptable state is waiting for some I/O access, eg waiting for disk. The averages are taken over the three time intervals. Load averages are not normalized for the number of CPUs in a system, so a load average of 1 means a single CPU system is loaded all the time while on a 4 CPU system it means it was idle 75% of the time.
什么是System load average(系统平均负载)?
- 系统平均负载是处于runnable或uninterruptable状态的进程数。R+D状态的进程数。
- 处于runnable状态的进程,正在使用CPU或正在等待使用CPU。
- 处于uninterruptable状态的进程,正在等待某些I/O访问,比如等待磁盘。
- 平均负载没有针对系统中CPU的数量进行归一化,因此平均负载为1表示单个CPU系统始终处于满载状态,而在4 CPU系统上则意味着75%的时间处于空闲状态。
从man资料可以看出,实际上系统平均负载包括了(R+D)状态的进程。所以, Load avaerage与CPU使用率并不是完全同步的 :
- CPU密集型进程 ——使用大量CPU会导致平均负载升高,此时这两者是一致的;(大量浮点运算或乘除运算等)
- I/O密集型进程 ——等待I/O也会导致平均负载升高,但CPU使用率不一定高;(可能Load average虚高但是CPU不忙)
- 大量等待CPU的进程调度 也会导致平均负载升高,此时的CPU使用率也会比较高。
再深入学习一下Linux的进程状态,这样才能对Load average统计的进程数有个更深刻的理解。Linux系统状态(来自man ps)
- D uninterruptible sleep (usually IO)
- 不可中断睡眠(通常IO,也叫Disk sleep状态)
- R running or runnable (on run queue)
- 运行(在运行队列上)
- S interruptible sleep (waiting for an event to complete)
- 可中断睡眠状态(等待事件去完成)
- T stopped by job control signal
- 被job control信号停止(SIGSTOP停止,SIGCONT恢复)
- t stopped by debugger during the tracing
- 正在被调试(gdb attach)
- W paging (not valid since the 2.6.xx kernel)
- X dead (should never be seen)
- 死亡
- Z defunct ("zombie") process, terminated but not reaped by its parent
- 僵尸状态
网上找的状态变迁图:
重点说一下,Load average统计的不可中断睡眠状态。
D 不可中断睡眠状态
- 不可中断,指的并不是CPU不响应外部硬件的中断,而是指进程不响应异步信号。绝大多数情况下,进程处在睡眠状态时,总是应该能够响应异步信号的。但是uninterruptible sleep 状态的进程不接受外来的任何信号,因此无法用kill杀掉这些处于D状态的进程,无论是”kill”, “kill -9″还是”kill -15″。
- 处于uninterruptible sleep状态的进程通常是在等待IO,比如磁盘IO,网络IO,其他外设IO,如果进程正在等待的IO在较长的时间内都没有响应,那么就被ps看到了,同时也就意味着很有可能有IO出了问题,可能是外设本身出了故障,也可能是比如挂载的远程文件系统已经不可访问了
- uninterruptible状态存在的意义就在于,内核的某些处理流程是不能被打断的。如果响应异步信号,程序的执行流程中就会被插入一段用于处理异步信号的流程(这个插入的流程可能只存在于内核态,也可能延伸到用户态),于是原有的流程就被中断了。
- 在进程对某些硬件进行操作时(比如进程调用read系统调用对某个设备文件进行读操作,而read系统调用最终执行到对应设备驱动的代码,并与对应的物理设备进行交互),可能需要使用uninterruptible状态对进程进行保护,以避免进程与设备交互的过程被打断,造成设备陷入不可控的状态。这种情况下的task_uninterruptible状态总是非常短暂的,通过ps命令基本上不可能捕捉到。
- 当处于uninterruptibly sleep状态时,只有当进程从system调用返回时,才通知signal
Executing process requests some service from kernel,
\ kernel suspends process and moves it to a parking queue
\ Kernel completes the service,
\ \ kernel moves suspended process to one of the runnable queues
\ \
\ \ Suspended process moves to top of runnable queue,
\ \ \ kernel resumes the process execution
\ \ \
Running --;--> Parked --;--> Runnable --;--> Running
简单的说, uninterruptible sleep状态,就是内核觉得调用不可中断,避免进程与设备的交互过程被打断,造成设备陷入不可控的状态 。
如何制造uninterruptible sleep状态呢?
用stress命令产生I/O压力
15:55:28#stress -i 1 --timeout 30
stress: info: [10892] dispatching hogs: 0 cpu, 1 io, 0 vm, 0 hdd
再用ps命令查看状态
15:57:36#ps aux|grep stress
root 10892 0.0 0.0 7320 956 pts/8 S+ 15:57 0:00 stress -i 1 --timeout 30
root 10893 45.5 0.0 7320 96 pts/8 D+ 15:57 0:01 stress -i 1 --timeout 30
T 停止状态(T, stop),被SIGSTOP信号停止。
下面命令演示如何使用jobs和kill命令让进程迁入停止状态
15:41:49#./wqtest &
[1] 8840
15:41:58#ps aux|grep wqtest
root 8840 0.0 0.0 4204 736 pts/8 S 15:41 0:00 ./wqtest
root 8861 0.0 0.0 11756 2236 pts/8 S+ 15:42 0:00 grep --color=auto wqtest
15:42:03#jobs
[1]+ Running ./wqtest &
15:42:07#kill -STOP 8840
[1]+ Stopped ./wqtest
15:42:14#ps aux|grep wqtest
root 8840 0.0 0.0 4204 736 pts/8 T 15:41 0:00 ./wqtest
root 8906 0.0 0.0 11756 2240 pts/8 S+ 15:42 0:00 grep --color=auto wqtest
15:42:20#kill -CONT 8840
15:42:28#ps aux|grep wqtest
root 8840 0.0 0.0 4204 736 pts/8 S 15:41 0:00 ./wqtest
root 8935 0.0 0.0 11756 2232 pts/8 S+ 15:42 0:00 grep --color=auto wqtest
15:42:33#jobs
[1]+ Running ./wqtest &
t 跟踪状态(t, trace),被gdb等调试器调试中
下面命令演示如何使用gdb命令让进程迁入跟踪状态
15:46:43#gdb -p 8840
GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.3) 7.7.1
...........
Loaded symbols for /lib64/ld-linux-x86-64.so.2
0x00007f019b5f3d30 in __nanosleep_nocancel () at ../sysdeps/unix/syscall-template.S:81
81 ../sysdeps/unix/syscall-template.S: No such file or directory.
(gdb)
15:39:46#ps aux|grep wqtest
root 8840 0.0 0.0 4204 740 pts/8 t 15:41 0:00 ./wqtest
root 9489 0.0 0.0 11756 2236 pts/9 S+ 15:46 0:00 grep --color=auto wqtest
再来看看稍微有点不好理解的僵尸进程。
Z defunct ("zombie") process
On Unix and Unix-like computer operating systems, a zombie process or defunct process is a process that has completed execution (via the exit system call) but still has an entry in the process table: it is a process in the "Terminated state". This occurs for child processes, where the entry is still needed to allow the parent process to read its child's exit status: once the exit status is read via the wait system call, the zombie's entry is removed from the process table and it is said to be "reaped". A child process always first becomes a zombie before being removed from the resource table. In most cases, under normal system operation zombies are immediately waited on by their parent and then reaped by the system – processes that stay zombies for a long time are generally an error and cause a resource leak.
关于僵尸进程的一些解释:
- 在Linux进程的状态中,僵尸进程是非常特殊的一种,它是已经结束了的进程,但是没有从进程表中删除。太多了会导致进程表里面条目满了,进而导致系统崩溃,倒是不占用其他系统资源。
- 它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间。
- 进程在退出的过程中,处于TASK_DEAD状态。在这个退出过程中,进程占有的所有资源将被回收,除了task_struct结构(以及少数资源)以外。于是进程就只剩下task_struct这么个空壳,故称为僵尸。
- 之所以保留task_struct,是因为task_struct里面保存了进程的退出码、以及一些统计信息。而其父进程很可能会关心这些信息。比如在shell中,$?变量就保存了最后一个退出的前台进程的退出码,而这个退出码往往被作为if语句的判断条件。当然,内核也可以将这些信息保存在别的地方,而将task_struct结构释放掉,以节省一些空间。但是使用task_struct结构更为方便,因为在内核中已经建立了从pid到task_struct查找关系,还有进程间的父子关系。释放掉task_struct,则需要建立一些新的数据结构,以便让父进程找到它的子进程的退出信息。
- 子进程在退出的过程中,内核会给其父进程发送一个信号,通知父进程来“收尸”。 父进程可以通过wait系列的系统调用(如wait4、waitid)来等待某个或某些子进程的退出,并获取它的退出信息。然后wait系列的系统调用会顺便将子进程的尸体(task_struct)也释放掉。
- 这个信号默认是SIGCHLD,但是在通过clone系统调用创建子进程时,可以设置这个信号。如果他的父进程没安装SIGCHLD信号处理函数调用wait或waitpid()等待子进程结束,又没有显式忽略该信号,那么它就一直保持僵尸状态,子进程的尸体(task_struct)也就无法释放掉。
- 如果这时父进程结束了,那么init进程自动会接手这个子进程,为它收尸,它还是能被清除的。但是如果如果父进程是一个循环,不会结束,那么子进程就会一直保持僵尸状态,这就是为什么系统中有时会有很多的僵尸进程。
- 当进程退出的时候,会将它的所有子进程都托管给别的进程(使之成为别的进程的子进程)。托管的进程可能是退出进程所在进程组的下一个进程(如果存在的话),或者是1号进程。所以每个进程、每时每刻都有父进程存在。除非它是1号进程。1号进程,pid为1的进程,又称init进程。
- linux系统启动后,第一个被创建的用户态进程就是init进程。init进程不会被暂停、也不会被杀死(这是由内核来保证的)。它在等待子进程退出的过程中处于task_interruptible状态,“收尸”过程中则处于task_running状态。它有两项使命:
- 执行系统初始化脚本,创建一系列的进程(它们都是init进程的子孙);
- 在一个死循环中等待其子进程的退出事件,并调用waitid系统调用来完成“收尸”工作;
简单的说 ,僵尸状态是每个子进程完成执行后都会进入的状态,为的是让父进程查看子进程的退出信息,如果父进程及时回收,那么子进程就完全退出了。如果父进程没回收,子进程就会一直处于僵尸状态;或者父进程先于子进程退出,那么就会由init进程(全民父母)代为回收。
构造一个zombie进程的条件
- 父进程没有处理SIGCHLD信号,也没用wait()系列函数等待子进程结束,并且创建子进程之后父进程进入大循环。此时当子进程执行完后,会成为zombie进程。
- 父进程没有处理SIGCHLD信号,也没没wait()系列函数,而是直接结束了。当父进程结束后,init会自动接手子进程,待子进程执行完成后,先进入zombie瞬态,然后马上会被init回收。
来看几个代码例子:
1、父进程正常回收的代码例子
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
pid_t pc, pr;
int i = 0;
for(i=0;i<4;i++)
pc=fork();
if(pc<0){
/*如果fork出错 */
printf("Erroroccured on forking.\n");
}else if(pc==0){
/*如果是子进程* */
printf("child process start to sleep%d.\n", i);
/*睡眠x秒 * */
sleep(10-i);
printf("child process exiting%d.\n", i);
exit(0);
/*如果是父进程 */
int j = 0;
pr=waitpid(-1,NULL, WNOHANG);
/*使用了WNOHANG参数,waitpid不会在这里等待 * */
if(pr ==0){
/*如果没有收集到子进程 * */
printf("Nochild exited,%d\n",pr);
sleep(1);
}else{
printf("wait child %d,j=%d\n", pr, j);
}while(j<4);
/*没有收集到子进程,就回去继续尝试 * */
if(j==4)
printf("successfully get child %d\n", pr);
printf("someerror occured\n");
}
执行结果
child process start to sleep0.
child process start to sleep1.
Nochild exited,0
child process start to sleep2.
child process start to sleep3.
Nochild exited,0
Nochild exited,0
Nochild exited,0
Nochild exited,0
Nochild exited,0
Nochild exited,0
child process exiting3.
wait child 16612,j=0
Nochild exited,0
child process exiting2.
wait child 16611,j=1
Nochild exited,0
child process exiting1.
wait child 16610,j=2
Nochild exited,0
child process exiting0.
wait child 16609,j=3
successfully get child 16609
2、父进程先走,由init进程代为回收的例子
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
pid_t pc, pr;
int i = 0;
for(i=0;i<4;i++)
pc=fork();
if(pc<0){
/*如果fork出错 */
printf("Erroroccured on forking.\n");
}else if(pc==0){
/*如果是子进程
printf("child process start to sleep%d.\n", i);
sleep(10-i);
printf("child process exiting%d.\n", i);
/*睡眠10秒
exit(0);
/*如果是父进程 */
int j = 0;
pr=waitpid(-1,NULL, WNOHANG);
////使用了WNOHANG参数,waitpid不会在这里等待
if(pr ==0){
//如果没有收集到子进程
printf("Nochild exited,%d\n",pr);
sleep(1);
}else{
printf("wait child %d,j=%d\n", pr, j);
} while(j<4);
//没有收集到子进程,就回去继续尝试
if(j==4)
printf("successfully get child %d\n", pr);
printf("someerror occured\n");
sleep(5);
printf("parents exit\n");
}
执行结果:
child process start to sleep0.
child process start to sleep1.
child process start to sleep2.
child process start to sleep3.
parents exit
child process exiting3.
child process exiting2.
child process exiting1.
child process exiting0.
使用ps命令可以查看,当父进程退出后,fork出来的子进程的父进程号PPID变成了1(也就是init进程)
ps -ef |grep zombie
root 17252 15192 0 09:20 pts/6 00:00:00 ./zombie_by_init
root 17253 17252 0 09:20 pts/6 00:00:00 ./zombie_by_init
root 17254 17252 0 09:20 pts/6 00:00:00 ./zombie_by_init
root 17255 17252 0 09:20 pts/6 00:00:00 ./zombie_by_init
root 17256 17252 0 09:20 pts/6 00:00:00 ./zombie_by_init
root 17263 15999 0 09:20 pts/8 00:00:00 grep --color=auto zombie
ps -ef |grep zombie
root 17253 1 0 09:20 pts/6 00:00:00 ./zombie_by_init
root 17254 1 0 09:20 pts/6 00:00:00 ./zombie_by_init
root 17255 1 0 09:20 pts/6 00:00:00 ./zombie_by_init
root 17256 1 0 09:20 pts/6 00:00:00 ./zombie_by_init
root 17276 15999 0 09:20 pts/8 00:00:00 grep --color=auto zombie
3、父进程没退出也没有回收,产生zombie进程的例子
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
pid_t pc, pr;
int i = 0;
for(i=0;i<4;i++)
pc=fork();
if(pc<0){
/*如果fork出错 */
printf("Erroroccured on forking.\n");
}else if(pc==0){
/*如果是子进程
printf("child process start to sleep%d.\n", i);
sleep(10-i);
printf("child process exiting%d.\n", i);
/*睡眠10秒
exit(0);
/*如果是父进程 */
int j = 0;
pr=waitpid(-1,NULL, WNOHANG);
////使用了WNOHANG参数,waitpid不会在这里等待
if(pr ==0){
//如果没有收集到子进程
printf("Nochild exited,%d\n",pr);
sleep(1);
}else{
printf("wait child %d,j=%d\n", pr, j);
} while(j<4);
//没有收集到子进程,就回去继续尝试
if(j==4)
printf("successfully get child %d\n", pr);
printf("someerror occured\n");
printf("parents in sleep\n");
sleep(1000);
}
用ps命令查看进程状态,可以看到子进程执行结束后,由于父进程还在sleep没有来回收,所以都变成了僵尸进程。
ps aux|grep zombie
root 17793 0.0 0.0 4204 644 pts/6 S+ 09:24 0:00 ./zombie_err
root 17794 0.0 0.0 4204 80 pts/6 S+ 09:24 0:00 ./zombie_err
root 17795 0.0 0.0 4204 80 pts/6 S+ 09:24 0:00 ./zombie_err
root 17796 0.0 0.0 4204 80 pts/6 S+ 09:24 0:00 ./zombie_err
root 17797 0.0 0.0 4204 80 pts/6 S+ 09:24 0:00 ./zombie_err
ps aux|grep zombie
root 17793 0.0 0.0 4204 644 pts/6 S+ 09:24 0:00 ./zombie_err
root 17794 0.0 0.0 0 0 pts/6 Z+ 09:24 0:00 [zombie_err] <defunct>
root 17795 0.0 0.0 0 0 pts/6 Z+ 09:24 0:00 [zombie_err] <defunct>
root 17796 0.0 0.0 0 0 pts/6 Z+ 09:24 0:00 [zombie_err] <defunct>
root 17797 0.0 0.0 0 0 pts/6 Z+ 09:24 0:00 [zombie_err] <defunct>
避免产生zombie进程的方法
- 父进程通过调用wait()/waitpid()等待子进程结束。(这会导致父进程被挂起)
- 如果父进程很忙,不想被挂起,可以通过signal函数设置SIGCHLD信号的handler,在handler中调用wait()/waitpid()。(子进程结束时,内核会向父进程发送SIGCHLD信号,父进程对该信号的默认处理是忽略。)
- 父进程通过signal(SIGCHLD, SIG_IGN)显示忽略SIGCHLD信号,这样子进程结束后,内核不在向父进程发送SIGCHLD信号,内核负责回收子进程。
- 两次fork()。父进程fork()出子进程,子进程fork()出孙进程后退出,孙进程结束运行后,由于其父进程已经退出,因此由init进程接管。init进程会负责该进程的销毁工作。
如何清除僵尸进程呢?
- kill命令无法杀死僵尸进程,但是我们可以kill僵尸进程的父进程,这样僵尸进程就会变成孤儿进程,然后由init进程接管。
ps -e -o ppid,stat | grep Z|cut -d " " -f1 | xargs kill -9
kill -HUP `ps -A -ostat,ppid | grep -e '^[Zz]' | awk '{print $2}'`
往期回顾:
万字长文分析Ext_4使用的JBD2模块死锁
一文搞懂Linux下内核定时器(Timer)
详谈红黑树的理解与推导
一文搞懂Linux内核虚拟内存之 物理内存
知识梳理!简述Linux内核MMU工作原理
前言:在Linux系统中,一切都可以通过命令行命令来控制。从小任务到大任务,您都可以找到一个简单的命令来完成您的工作。如果您是 Linux 新手,并且对系统管理感兴趣,那么您需要对命令行有扎实的了解。在本文中,我们将使用一些易于理解的示例来讨论此命令的基础知识。最后附上uptime代码实现。
如何使用 uptime 命令
我们知道,uptime 命令为您提供系统启动(或运行)的时间。除了系统的运行时间,您还可以获得系统的其他详细信息,包括当前时间、运行会话的用户数以及过去 1、5 和 15 分钟的系统平均负载。
uptime [options]
uptime 的基本用法非常简单——只需输入命令名称并按回车键即可。
通过命令行运行 Linux 系统的 uptime 命令,会输出以下信息。
- 系统的当前时间。
- 系统的总正常运行时间。
- 当前正在运行系统的活动用户。
- 过去 1、5 和 15 分钟内可用的系统负载的平均值。
“up”表示系统正在运行。
这里的 系统负载平均值 是处于可运行状态或可运行状态的平均进程数不间断状态。处于可运行状态的进程要么正在使用 CPU,要么正在等待使用中央处理器。处于不可中断状态的进程正在等待某些 I/O 访问,例如等待磁盘。
如何查看uptime帮助信息
uptime 命令带有各种选项。要检查选项,我们可以运行 help 命令。
除了 help 命令,您还可以运行 man 命令来检查“uptime”命令的使用情况。
如何让工具以漂亮的格式显示时间
如果您只想知道系统已启动的时间,并且以更易于阅读的格式知道,请使用 -p 命令行选项。
uptime -p
您可以获得非常清晰的输出,以天数、小时数、分钟数和秒数的格式显示正常运行时间。
我们还可以使用“-s”选项在命令行界面上运行以下命令。
显示的信息来看,检查系统首次启动的确切时间,而不是自启动以来花费的时间。
【文章福利 】小编推荐自己的Linux内核源码交流群:【点击链接加入群聊 869634926 】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前50名可进群领取,并额外赠送一份价值798的内核资料包(含视频教程、电子书、实战项目及代码)!
学习直通车: Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈
内核资料领取: Linux内核源码技术学习路线+视频教程内核源码
uptime代码实现
void getloadavg(char *loadavg, size_t nelem)
char command[512] = {0};
snprintf(command, sizeof(command), "%s |%s %s|%s -F: '%s'", "w","grep", "load", "awk","{print $4}");
FILE *fp = popen(command, "r");
if (fp == NULL)
printf("popen failed!\n");
return;
fread(loadavg, nelem-1, 1, fp);
fclose(fp);
static void print_uptime(void)
FILE *fp;
time_t time_now;
time_t uptime = 0;
long int updays;
int uphours;
int upmins;
struct tm *tmn;
struct utmp *u;
int utmp_num = 0;
char sz_loadavg[64] = {0};
fp = fopen ("/proc/uptime", "r");
if (fp != NULL)
char buf[BUFSIZ];
char *b = fgets (buf, BUFSIZ, fp);
if (b != NULL)
char *end_ptr;
double upsecs = strtod (buf, &end_ptr);
//printf("buf: %s\n",buf);
//printf("end_ptr: %s\n",end_ptr);
if (buf != end_ptr)
uptime = (0 <= upsecs && upsecs < TYPE_MAXIMUM (time_t)
? upsecs : -1);
fclose (fp);
while((u = getutent()))
if(u->ut_type == USER_PROCESS)
++utmp_num;
//printf("%d %s %s %s \n", u->ut_type, u->ut_user, u->ut_line, u->ut_host);
time_now = time (NULL);
if (uptime == 0)
printf("couldn't get boot time\n");
updays = uptime / 86400;
uphours = (uptime - (updays * 86400)) / 3600;
upmins = (uptime - (updays * 86400) - (uphours * 3600)) / 60;
tmn = localtime (&time_now);
//printf("uphours: %d\n",uphours);
//printf("upmins: %d\n",upmins);
printf("%2d:%2d:%2d\t", tmn->tm_hour, tmn->tm_min, tmn->tm_sec); /* 这将打印当前时钟时间 */
printf("up %2d:%2d\t",uphours,upmins);