Linux的基本学习(十三)——进程管理(上)

Linux的基本学习(十三)——进程管理(上)

 

前言

承接上面的内容,我们来继续学习Linux操作系统

 

 

 

 

 

 


进程(Process)

触发任何一个事件时,系统都会将它定义成为一个进程,并且给予这个进程一个ID,称为PID,同时根据触发这个进程的用户与相关属性关系,给予这个PID一组有效的权限设置。

进程与程序(process & program)

进程与程序

执行一个程序或命令就可以触发一个事件而获得一个PID。

我们说过,系统应该只认识二进制文件,那么当我们要让系统工作的时候,当然就是需要启动一个二进制文件,这个二进制文件就是程序(program)。

每个进程都有三组权限,每组都有r、w、x的权限,所以不同的用户身份执行这个程序的时候,系统给予的权限也都不相同。

程序一般是放在物理磁盘中(辅存/外存),然后通过用户的执行来触发。触发后会加载到内存中称为一个个体,那就是进程。为了让操作系统可以管理这个进程,进程会给予执行者权限/属性等参数,以及进程所需要的脚本或数据等,最后再给予一个PID。操作系统通过这个PID来判断该进程是否具有执行权限,它是重要的。

例如我们使用操作系统的时候,通常就是利用连接程序或直接在主机上面登录,然后获取我们的shell,那么,我们通常获取的是/bin/bash,即同一时间的每个人登录都是执行/bin/bash,不过,每个人获取的权限会根据自己的身份而不同:

当我们登陆并执行bash时,系统已经给了我们一个PID,这个PID就是根据登录者的UID/PID(etc/passwd)而来。我们知道,/bin/bash是一个程序,当dmtsai登录后,它获取一个PID为2234的进程,这个进程的User/Group都是dmtsai,而当这个进程执行其他作业时,例如上面提到的touch这个命令时,那么由这个进程衍生出来的其他进程在一般情况下,也会沿用这个进程的相关权限。

让我们对程序与进程做个总结:

  • 程序(program):通常为二进制程序,放置在存储媒介中(如硬盘、光盘、软盘、磁带等),以物理文件的形式存在。
  • 进程(process):程序被触发后,执行者的权限与属性、程序的代码与所需数据等都会被加载到内存中,操作系统给予这个内存中的单元一个标识符(PID),可以说进程就是一个正在运行中的程序。

子进程与父进程

在上面的说明,我们提到所谓的衍生出来的进程,那是什么?当我们登录系统后,会获取一个bash的shell,然后我们用这个bash提供的接口去执行另外一个命令,例如touch等,那些另外执行的命令也会被触发成为PID,那个后来所产生的PID就是【子进程】,而在我们原本的bash环境下,就称为【父进程】

在终端中执行bash进入子进程再使用ps命令查看进程:

[luoluo@study ~]$ bash
[luoluo@study ~]$ ps -l
F S   UID    PID   PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
0 S  1000   3351   3346  0  80   0 -  6197 -      pts/0    00:00:00 bash
0 S  1000   3379   3351  0  80   0 -  6198 -      pts/0    00:00:00 bash
0 R  1000   3407   3379  0  80   0 - 11244 -      pts/0    00:00:00 ps
[luoluo@study ~]$ 

第一个bash的PID和第二个bash的PPID都是3351,因为第二个bash是来自于第一个bash所产生的。(PPID也就是Parent PID,即父进程的PID)

有的时候人们关闭进程后,那个进程又会自己启动,这一般就是两种情况:要么是crontab例行性计划任务,要么就是一个父进程在维持它的启动(这个时候擒贼先擒王,我们应该杀了它的父进程)。

fork and exec:程序调用的流程

其实子进程与父进程之间的关系还挺复杂的,最大的复杂点在于进程之间的调用。Linux的程序调用通常称为fork-and-exec的流程。进程都会借由父进程以复制(fork)的方式产生一个一模一样的子进程,然后被复制出来的子进程再以exec的方式来执行实际要执行的进程,最终称为一个子进程。

fork:叉、分叉、分支

整个流程如图

系统先以fork的方式复制一个与父进程相同的临时进程,这个进程与父进程唯一的差别就是PID不同。但是这个临时进程还会多一个PPID的参数,PPID就是父进程的进程标识符。

然后临时进程开始以exec的方式加载实际要执行的进程,新的进程名为qqq,最终子进程的进程代码就会变成qqq了。

系统或网络服务:常驻在内存的进程

有一些进程不同于我们使用的ls、touch等命令所引发的进程,这些进程是一直存在于系统的,例如系统每分钟都会去扫描/etc/crontab以及相关的配置文件来执行计划任务,那么这个计划任务是谁负责的呢?是由crond这个进程管理的。我们将它的后台启动并一直不断地运行,即【常驻在内存中的进程】

常驻在内存中的进程通常都是负责一些系统所提供的功能以服务用户的各项任务,因此这些常驻进程就被我们称为:服务(daemon)。系统的服务非常多,不过主要分成系统本身所需要的服务(crond、atd、rsyslogd等)和负责网络连接的服务(apache、named、postfix等)。网络服务比较有趣的地方在于,这些进程被执行后,它会启动一个可以负责网络监听的端口(port),以提供外部客户端的连接请求。

 

Linux的多人多任务环境

多人环境

Linux最棒的地方就在于它的多人多任务环境了。

在Linux系统上面有许多不同的账号,每种账号都有其特殊的权限,只有一个账号具有至高无上的权利——root(系统管理员)。除了root之外,其他人都必须要受一些限制,而每个人进入Linux的环境设置都可以随着每个人的喜好来设置,这就是因为每个人登录后获得的shell的PID不同。

多任务操作

目前CPU的频率可高达几个GHz,这代表CPU每秒可以运行1000000000这么多次命令。我们的Linux可以让CPU在各个任务间切换,也就是说每个任务都仅占CPU的几个命令次数,所以CPU每秒都能够在各个进程之间切换。

CPU切换进程的任务,与这些任务进入到CPU运行的调度(CPU调度,非crontab计划任务)会影响到系统的整体性能。目前Linux使用的多任务切换操作是非常棒的一个机制,几乎可以将PC的性能整个压榨出来。由于性能非常好,因此当多人同时登陆系统时,其实会感受到整台主机好像就为了你存在一般,这就是多人多任务环境。

多重登陆环境的七个终端界面

在Linux当中,默认提供了六个命令行登陆界面,以及一个图形界面,你可以使用Alt+F1-F7来切换不同的终端界面,而且每个终端界面的登录者还可以不同。

特殊的进程管理操作

老实说,Linux系统几乎不会宕机,因为它可以在任何时候,将某个被困住的进程杀掉,然后再重新执行该进程而不用重新启动。

如果我在Linux下以命令行界面登录,在屏幕当中显示错误信息后就挂了,动也不能动,该如何是好?这个时候我们可以跳到其他终端界面(Alt+Fn),登录账号后用ps -aux找出刚刚的错误进程,然后kill了它,然后回到刚才的终端界面,就又恢复正常。

bash环境下的任务管理(job control)

我们在一个bash下可以执行多个任务,示例:

cp file1 file2 &

这一串命令,重点在于最后的那个&,它表示将file1这个文件复制给file2,且放置于后台中执行,也就是说执行这一个命令之后,在这一个终端界面仍然可以做其他任务。而当这个命令执行完毕后,系统将会在你的终端界面显示完成的消息。

多人多任务的系统资源分配问题考虑

多人多任务确实有很多的好处,但其实也有管理上的难题,因为用户越来越多会导致你管理上的困扰。另外,由于用户日渐增多,当用户达到一定的人数后,通常你的机器便需要升级了,因为CPU的计算能力与内存的大小可能就会不够用了。

举个例子,按书中鸟哥的例子:鸟哥之前的网站管理得有点不太好,因为使用了一个很复杂的人数统计程序,这个程序会一直读取MySQL数据库的数据,偏偏因为流量大,造成MySQL处于高负载状态。在这样的情况下,当鸟哥要去编写网页时,或要去使用讨论区的资源时,速度就非常慢,就来终于将这个程序废弃,用自己写的一个小程序来替换,这样才让CPU的负载(loading)整个降下来,用起来顺畅多了。

 


任务管理(job control)

这个任务管理(job control)是用在bash环境下的,也就是说:当我们登陆系统获取bash shell之后,在单一终端下同时执行多个任务的操作管理。

什么是任务管理

执行任务管理的操作中,其实每个任务都是目前bash的子进程,即彼此之间是有相关性的,但是在任务管理上是具有独立性的,我们无法用任务管理的方式由tty1的环境去管理tty2的bash。

以下内容中,我们假设只有一个终端,因此可以出现提示字符让你操作的环境就称为前台(foreground),至于其他任务就可以放入后台(background)去暂停或执行(后台任务是不可以使用Ctrl+C去终止的)。

总之,要执行bash的任务管理必须要注意到的限制是:

  1. 这些任务所触发的进程必须来自于你shell的子进程(只管理自己的bash)
  2. 前台:可以控制与执行命令的这个环境称为前台。
  3. 后台:可以自动执行的任务,你无法使用Ctrl+C去终止,可使用bg、fg调用该任务
  4. 后台中执行的进程不能等待terminal或shell的输入

job control的管理

如前面所述,bash只能够管理自己的任务而不能管理其他bash的任务,所以即使你是root也不能将别人bash下面的job拿过来执行。此外,在后台里面的任务状态又可以分为【暂停】和【运行】。那实际是指job control的命令有哪些?

直接将命令丢到后台中【执行】的&

上面也提到过,利用符号&,就可以将任务丢到后台中。

[root@study ~]# tar -zpcf /tmp/etc.tar.gz /etc &
[1] 3408
[root@study ~]# tar: 从成员名中删除开头的“/”
# 在中括号中的号码为任务号码(job number),该号码与bash的控制有关
# 后续的14432则是这个任务在系统中的PID,至于后续出现的数据是tar执行的数据流
# 由于我们没有加上数据流重定向,所以会影响界面,不过不会影响前台的操作

仔细一看,输入一个命令后,在该命令的最后面加上一个&代表将该命令丢到后台中,此时bash会给予这个命令一个任务号码(job number),就是那个1,后面那个 3408 则是该命令所触发的PID。而且,有趣的是,我们可以继续操作bash。

过一会,命令行会显示一行信息:

[1]+ Done tar -zpcf /tmp/etc.tar.gz /etc

就代表【1】这个任务已经完成。

另外注意一点就是注意命令的流向,如果你的命令有很多的信息要输出,那么它依然是输出在你的命令行上,而且还不能被Ctrl+C中断,想想很头大,所以建议执行命令的时候写清楚流向:

tar -zpcvf /tmp/etc.tar.gz /etc >tmp/log.txt 2>&1 &

这样就不会影响前台(foreground)的操作了!

将目前的任务丢到后台中暂停:Ctrl+z

想想一个情况:如果我正在使用vim,却发现有个文件不知道放在哪里了,需要到bash环境中执行查找,此时是否要结束vim呢?不需要,只要暂时将vim丢到后台当中等待即可。

利用命令Ctrl+Z即可。

 

查看目前的后台任务状态:jobs

jobs [-lrs]
-l:除了列出job number与命令串之外,同时列出PID的号码
-r:仅列出正在后台run的任务
-s:仅列出正在后台暂停(pause)的任务

一定要分清出,jobs不是查看进程,是查看系统后台的任务。

如果有显示后台任务的话,前两个一般会加上一个+/-,+代表最近被放入后台的任务,-代表第二个最近的。当我们使用fg命令,+的任务就会被拿回前台继续执行。

将后台任务拿到前台来处理:fg

刚刚提到的都是将任务丢到后台当中去执行,那么有没有可以将后台任务拿到前台来处理的呢?有,就是那个fg(foreground)。

fg %jobnumber/[-]
%jobnumber:jobnumber是任务号码(数字)
            ,注意:%是可有可无的
空:取出+的任务
-:取出-的任务

让任务在后台下的状态变成运行中:bg

我们知道,Ctrl+Z可以将目前的任务丢到后台下面去【暂停】,那么如何让一个任务在后台下面【运行】呢?我们可以在下面这个案例当中来测试。

下面的命令后面多了一个 & 代表命令已经被移动到后台了

管理后台中的任务:kill

我们已经学会让一个任务在后台继续工作、也可以拿回前台来,那么想要将任务直接删除呢?或是将该任务重新启动呢?这个时候就需要给予该任务一个信号(signal),让它知道怎么做才好。此时,kill这个命令就派上用场了。

kill -signal %jobnumber
kill -l
-l:这个是L的小写,列出目前能够使用的信号有哪些?
signal:代表给予后面接的那个任务什么样的指示,用man 7 signal可知
  -1:重新读取一次参数的配置文件
  -2:代表由键盘输入Ctrl+C同样的操作
  -9:立即强制删除一个任务
  -15:以正常的进程方式终止一项任务

注意,上面案例中,vim的任务无法被结束,因为它无法被kill正常终止,也尽量不要强制删除vim,否则vim编辑文件.filename.swp会遗留在系统中,对于vim,建议fg取回前台再正常退出。

特别留意:-9这个信号通常是在【强制删除一个不正常的任务】时所用的,-15则是以正常的步骤结束一个任务(15是默认值),两者并不相同。

另外,kill后面接的数字默认是PID,如果想要管理bash任务,就要使用%+数字这种方式了,这点也要特别留意才行。

 

脱机管理问题

要注意的是,我们在任务管理当中提到的【后台】指的是在终端模式下可以避免Ctrl+C中断的一个情景,你可以说那个是bash后台,不是放到系统的后台中。所以,任务管理的后台依旧与终端有关。在这样的情况下,如果你是以远程登录的方式连接到你的Linux主机,并且将任务以&的方式放到后台中,请问,在任务尚未结束的情况下你脱机了,该任务还会继续执行吗?答案是否,不会继续执行,会被中断(因为你的bash没了,自然bash的后台的任务也就没了)。

那怎么办呢?如果我的任务需要执行一大段时间,我又不能放置在后台下面,那该如何处理?首先,你可以考虑前一张的at来处理,at是将任务方式到系统后台而与终端无关。如果不想使用at,你也可以尝试使用nohup这个命令来处理。这个nohup可以在脱机或注销系统后,还能让任务继续执行。它的语法有点像:

nohup [命令与参数]    <==在终端前台中任务
nohup [命令与参数]  &   <==在终端后台中任务

很简单的命令吧!上述命令需要注意的是,nohup并不支持bash内置的命令,因此你的命令必须是外部命令才行。我们来尝试玩一下下面的任务吧!

首先写一个延迟执行输出的脚本——sleep500.sh:

然后如下命令:

chmod a+x sleep500.sh
nohup ./sleep500.sh &
exit

然后我们重新登录一下,然后可以使用pstree去查看你的进程,你会发现sleep500.sh仍然在执行中,并不会被中断。由于我们的进程最后会输出一个信息,但是nohup其实与终端无关,因此这个信息最后会被定向到【~/nohup.out】(这也是为什么在上述的命令中,当你出现nohup后,会出现一个提示信息)。

 


进程管理

查看进程

既然进程这么重要,那么我们如何查看系统上面正在运行当中的进程呢?很简单,可以利用静态的ps或是动态的top命令,还可以利用pstree来查看进程树之间的关系。

ps:将某个时间点的进程运行情况取下来

常用的:

PS aux 查看系统所有的进程
ps -lA 也是能够查看所有系统的进程

命令细解:

ps axjf
-A:所有的进程均显示出来,与-e具有同样的效果
-a:不显示与终端有关的所有进程
-u:有效使用者(effective user)相关的进程
x:通常与a这个参数一起使用,可列出较完整的信息
基础格式规划:
l:较长较详细的将PID的信息列出
j:任务的格式(job format)
-f:做一个更为完整的输出

接下来我们需要记住两种比较常用的命令参数:

仅查看自己的bash相关进程:ps  -l

[luoluo@study ~]$ ps -l
F S   UID    PID   PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
0 S  1000   3409   3404  0  80   0 -  6197 -      pts/0    00:00:00 bash
0 R  1000   3673   3409  0  80   0 - 11244 -      pts/0    00:00:00 ps

系统整体的进程是非常多的,但使用ps -l仅会列出与你的操作环境(bash)有关的进程,即最上层的父进程会是你自己的bash而没有扩展到systemd(后续会介绍)这个进程中。那么ps -l显示来的数据有哪些?我们就来观察看看:

  • F:代表这个进程标识(proccess flags),说明这个进程的权限,常见号码有:
    • 4:表示此进程的权限是root
    • 1:表示此子进程仅执行复制(fork)而没有实际执行(exec)
  • S:代表这个进程的状态(STAT),主要的状态有:
    • R(Running):该进程正在运行中
    • S(Sleep):该进程目前正在睡眠状态(idle),但可以被唤醒(signal)
    • D:不可被唤醒的睡眠状态,通常这个进程可能在等待I/O的情况(ex>打印)
    • T(Stop):停止状态,可能是在任务控制(后台暂停)或跟踪(traced)状态
    • Z(Zombie):僵尸状态,进程已经终止但却无法被删除至内存外
  • UID/PID/PPID:代表 此进程被UID所拥有/进程的PID号码/此进程的父进程的PID号码
  • C:代表CPU使用率,单位是百分比
  • PRI/NI:Priority/Nice的缩写,代表此进程被CPU所执行的优先级,数值越小代表越快被CPU执行,这个我们后面会细说
  • ADDR/SZ/WCHAN:都与内存有关,ADDR是kernel function,指出该进程在内存的哪个部分,如果是个running的进程,一般就会显示【-】;SZ代表此进程用掉多少内存;WCHAN表示目前进程是否运行,同样的,若为【-】表示正在运行中
  • TTY:登录者的终端位置,若为远程登录则使用动态终端接口名称(pts/n)
  • TIME:使用的CPU时间,注意,是此进程实际花费CPU运行的时间,而不是系统时间
  • CMD:造成此进程的触发进程的命令是什么

所以上述示例信息就可以翻译成:bash的进程属于UID为1000的用户,状态是睡眠,之所以为睡眠,因为它触发了ps(状态为run)。此进程的PID为3409,优先执行顺序为80,执行bash的终端接口为pts/0(图形化tty下的终端),运行状态为等待(wait)。

查看系统所有进程:ps aux

[luoluo@study ~]$ ps aux
USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root          1  1.5  0.6 179360 13836 ?        Ss   11:01   0:02 /usr/lib/syste
root          2  0.0  0.0      0     0 ?        S    11:01   0:00 [kthreadd]
………………………………………………
root       3213  0.3  0.4 323420  9520 ?        Ssl  11:04   0:00 /usr/libexec/b
luoluo     3225  0.0  0.3 226696  7348 ?        Ssl  11:04   0:00 /usr/libexec/g
luoluo     3259  7.2  2.5 581456 53976 ?        Ssl  11:04   0:00 /usr/libexec/g
luoluo     3269  1.0  0.2  24788  5284 pts/0    Ss   11:04   0:00 bash
luoluo     3303  0.0  0.1  57396  3876 pts/0    R+   11:04   0:00 ps aux

你会发现ps -l与ps aux显示的项目并不相同,在ps aux显示的项目中,各字段的意义为:

  • USER:该进程属于所属用户的账号
  • PID:该进程的进程ID
  • %CPU:该进程使用掉的CPU资源百分比
  • %MEM:该进程所占用的物理内存百分比
  • VSZ:该进程使用掉的虚拟内存量(KB)
  • RSS:该进程占用的固定的内存量(KB)
  • TTY:该进程是在哪个终端上面运行,若与终端无关则显示?(问号)?另外,tty1-tty6是本机上面的登录进程,若为pts/0等,则表示是由网络连接进入主机的进程
  • STAT:该进程目前的状态,状态显示与ps -l的S标识相同(R/S/T/Z)
  • START:该进程被触发启动的时间
  • TIME:该进程实际使用CPU运行时间
  • COMMAND:该进程的实际命令是什么

一般来说,ps aux会依照PID的顺序来排列显示。

另外我们还要学会筛选进程:

[luoluo@study ~]$ ps aux | egrep '(cron|rsyslog)'
root       1294  0.0  0.1  36304  3388 ?        Ss   11:01   0:00 /usr/sbin/crond -n
root       1745  0.0  0.5 208748 11960 ?        Ssl  11:02   0:00 /usr/sbin/rsyslogd -n
luoluo     3598  0.0  0.1  12908  3028 pts/0    D+   11:21   0:00 /bin/sh /usr/bin/egrep --color=auto (cron|rsyslog)

这样我们就筛出来了系统进程中的 cron或者rsyslog字段的进程

我们还应该了解一下什么是僵尸进程(Zombie)

造成僵尸进程的原因在于该进程应该已经执行完毕,或是应该要终止了,但是该进程的父进程却无法完整地将该进程结束掉,而造成该进程一直存在内存当中。如果你发现在某个进程的CMD后面接上了<defunct>时,就代表该进程是僵尸进程

系统不稳定的时候就容易造成所谓的僵尸进程,可能是因为程序写得不好,或是用户的操作习惯不良等造成的。如果你发现系统中很多僵尸进程时,要记得,要找出该进程的父进程,然后好好做个追踪,好好进行主机的环境优化,看看有什么地方需要改善,不要只是直接将它kill掉。不然的话,万一它一直产生,那可就麻烦了。

事实上,通常僵尸进程都已经无法管理,而直接交给systemd这个进程来负责,偏偏systemd是系统第一个执行的进程,它是所有进程父进程(用pstree看一下你就明白了)。我们是无法杀掉该进程的(杀了他,系统就死掉了),所以,如果产生僵尸进程,而系统过一阵子还没有办法通过内核非经常性的特殊处理来将该进程删除时,那你只好通过reboot的方式来将进程kill掉。

top:动态查看进程的变化

相对于ps是选取一个时间点的进程状态,top则可以持续检测进程运行的状态。使用方式如下:

top [-d 数字] | top [-bnp]
-d:后面可以接秒数,就是整个进程界面更新的秒数。默认是5秒
-b:以批量的方式执行top,还有更多的参数可以使用,
      通常会搭配数据流重定向来将批量的结果输出为文件
-n:与-b搭配,意义是,需要执行几次top的输出结果
-p:指定某些个PID来执行查看检测而已
在top执行过程中可以使用的按键命令:
    ?:显示在top当中可以输入的按键命令
    P :以CPU的使用排序显示
    M :以Memory的使用排序显示
    N :以PID来排序
    T :由该进程使用的CPU时间积累(TIME+)排序
    k  :给予某个PID一个信号(signal)
    r  :给予某个PID重新制订一个nice值
    q :退出top的按键

其实top的功能非常多,可以用的按键也非常多,可以参考一下man top的部分说明文件,鸟哥仅列出一些自己常用的选项而已。接下来我们实际查看一下如何使用top与top的界面

top - 19:17:17 up 18 min,  1 user,  load average: 0.99, 0.81, 0.50
Tasks: 262 total,   1 running, 261 sleeping,   0 stopped,   0 zombie
%Cpu(s): 72.4 us, 26.1 sy,  0.0 ni,  0.0 id,  0.0 wa,  1.5 hi,  0.0 si,  0.0 st
MiB Mem :   2058.2 total,    100.5 free,   1209.7 used,    747.9 buff/cache
MiB Swap:   1024.0 total,   1024.0 free,      0.0 used.    673.1 avail Mem 

   PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND        
  2989 root      20   0  631540  85452  24832 S  91.0   4.1   0:12.96 packagekitd    
  2835 luoluo    20   0 2687684 284980 117740 S   5.5  13.5   0:14.47 gnome-shell 

top也是个非常不错的进程查看工具,但与ps的静态结果输出不同,top这个进程可以持续地检测整个系统的进程任务状态。在默认情况下,每次更新资源信息是5秒,不过,可以使用 -d 来进行修改。top主要分为两部分界面,上面的界面为整个系统的资源使用状态,基本上总共有六行,显示的内容依次是:

  • top……:这一行显示的信息分别是:
    • 目前的时间
    • 开机到目前为止所经过的时间
    • 已经登录到系统的用户人数
    • 系统在1、5、15分钟的平均任务负载。我们之前聊过batch任务方式为负载小于0.8就是这个负载。代表的是1、5、15分钟,洗系统平均要负责运行几个进程(任务)的意思。数值越小代表系统越闲置,若高于1就要注意你的系统进程是否太过频繁了。
  • Tasks……:显示的是目前进程的总量与个别进程是在什么状态(Running、Sleeping、Stopped、Zombie)。需要注意的是最后的zombie那个数字。
  • %Cpu……:显示的是CPU的整体负载,每个项目可使用?来查看。需要注意的是wa项目,那个项目代表的是I/O wait,通常你的系统会变慢都是 I/O 产生的问题比较大。因此这里要注意这个项目耗用CPU的资源。另外,如果是多内核的设备,可以按下数字键【1】来切换不同CPU负载率。
  • 第四行、第五行:表示目前的物理内存与虚拟内存(Mem/Swap)的使用情况。再次重申,要注意的是swap的使用量要尽量的少,如果swap被占用的多,代表系统的物理内存实在不足。
  • 第六行:这个是当在top进程中输入命令时,显示状态的地方
  • top的下半部分:
    • PID:每个进程的ID
    • USER:该进程所属的用户
    • PR:Priority(优先)的简写,进程的优先执行顺序,越小则越早被执行
    • NI:Nice的简写,与Priority有关,也是越小则越早被执行
    • %CPU:CPU的使用率
    • %MEM:内存的使用率
    • TIME+:CPU使用时间的累加

top默认使用CPU使用率(%CPU)作为排序的依据

如果你想要将top信息执行两次,然后将结果输出到一个文件中,则可以这样做:

 top -b -n 2 > /tmp/top.txt

这个命令很有趣,可以帮助你将某个时段top查看到的结果存成文件,可以在系统后台执行。由于是后台执行,与终端的屏幕大小无关,因此可以得到全部的进程界面。

如果想要查看本bash的进程情况:

//$$是当前bash的PID(我的是3301)
echo $$
top -d 2 -p 3301

如果要修改NICE就按r,其他的就慢慢自己琢磨吧!

pstree

pstree [-A|U] [-up]
-A:各进程数之间的连接以ASCII字符来连接
-U:各进程数之间的连接以Unicode的字符来连接
       在某些终端界面下可能会有错误
-p:并同时列出每个进程的PID
-u:并同时列出每个进程的所属账号名称
查看进程树,同时显示PID和user
[luoluo@study ~]$ pstree -Aup

systemd(1)-+-ModemManager(948)-+-{ModemManager}(994)
           |                   `-{ModemManager}(1077)
           |-NetworkManager(1271)-+-{NetworkManager}(1283)
           |                      `-{NetworkManager}(1284)
           |-VGAuthService(930)
           |-accounts-daemon(1095)-+-{accounts-daemon}(1096)
           |                       `-{accounts-daemon}(1098)
           |-alsactl(937)
           |-atd(1301)
           |-auditd(905)-+-sedispatch(907)
           |             |-{auditd}(906)
           |             `-{auditd}(908)
           |-avahi-daemon(949,avahi)---avahi-daemon(1080)
           |-bluetoothd(932)
           |-boltd(3194)-+-{boltd}(3195)
           |             `-{boltd}(3197)
           |-colord(2459,colord)-+-{colord}(2471)
           |                     `-{colord}(2473)

在括号( )内的即是PID以及该进程的owner,一般来说,如果该进程的拥有者与父进程相同就不会列出,但是如果与父进程不一样,那就会列出该进程的拥有者。

如果要找出线程之间的相关性,那么这个pstree真是好用到不行。直接输入pstree就可以查到进程相关性,还会使用线段将相关性进程连接起来。这对处理僵尸进程会很有帮助。

所有的进程都是依附在systemd这个进程下面的,它是由Linux内核所主动调用的第一个进程(PID是1)。

进程的管理

进程之间是可以相互控制的。举例来说,你可以关闭、重新启动服务器软件,服务器软件本身是个进程,你既然可以让它关闭或启动,当然也就可以控制该进程。那么进程是如何相互管理的呢?其实是通过给予该进程一个信号(signal)去告知该进程你想要让它做什么,因此这个信号就很重要。

我们也在本章之前的bash任务管理当中提到过,要给予某个已经存在于后台中的任务某些操作时,直接给予一个信号给该任务号码即可。kill -l 查看所有的信号。

 

代號名稱內容
1SIGHUP启动被终止的进程,可让该 PID 重新读取自己的配置文件,类似重新启动
2SIGINT相当于用键盘輸入 [ctrl]-c 来中断一个程序的进行
9SIGKILL代表强制中断一个程序的执行,如果该程序执行到一半,
那么尚未完成的部分可能会有『半成品』产生,类似 vim会有 .filename.swp 保留下來。
15SIGTERM以正常的結束程序来終止该程序。由于是正常的终止,
所以后续的操作会將他完成。不过,如果该程序已经发生问题,就是无法使用正常的方法终止时,输入这个 signal 也是没有用的。
19SIGSTOP相当于用键盘輸入 [ctrl]-z 來暂停一个进程

上面仅是常见的信号而已,详情可以kill -l或者man 7 signal。

那么我们如何发送一个信号给某个进程呢?就通过kill或killall。

kill  -signal  PID

kill可以帮助我们将这个信号传送给某个任务(%jobnumber)或是某个PID(直接输入数字)。要再次强调的是:kill后面直接加数字与加上%number的情况是不同的,这个很重要(fg命令后面%可有可无,但是kill可就得区分了)。因为任务管理中有1号任务,但是PID1号则是专指【systemd】这个进程。你怎么可以将systemd关闭呢?关闭systemd,你的系统就宕掉了,所以务必记得那个%是专门用于进行任务管理的。我们就活用一下kill与刚刚上面提到的ps来做个简单的练习。

以ps找出rsyslogd这个进程的PID后,再使用kill传送信息,使得rsyslogd可以重新读取配置文件。

由于需要重新读取配置文件,因此signal是1号。至于找出rsyslogd的PID可以这样做:

[luoluo@study ~]$ ps aux | grep 'rsyslogd' | grep -v 'grep' | awk '{print $2}'
1745

awk命令忘记了吗?根据空格分组,如例就是打印标准输入中的以空格为界的第二个字段。

另外,命令中的grep -v  ‘grep’,是不要把本条命令(有grep)也算进去。

接下来就是实际使用kill -1 PID,因此:

[luoluo@study ~]$ sudo kill -SIGHUP 1745

我的这个进程的PID是1745,所以我这里写的是1745。

怎么确认有没有重新启动syslog呢?,可以参考日志文件的内容,使用如下命令查看:

[luoluo@study ~]$ sudo tail -5 /var/log/messages
Jul  1 18:55:53 study journal[4625]: gtk_widget_get_mapped: assertion 'GTK_IS_WIDGET (widget)' failed
Jul  1 18:55:53 study journal[4625]: gdk_window_is_visible: assertion 'GDK_IS_WINDOW (window)' failed
Jul  1 18:55:53 study journal[4625]: gtk_widget_set_opacity: assertion 'GTK_IS_WIDGET (widget)' failed
Jul  1 18:55:53 study journal[4625]: gtk_widget_queue_draw: assertion 'GTK_IS_WIDGET (widget)' failed
Jul  1 18:56:48 study rsyslogd[1745]: [origin software="rsyslogd" swVersion="8.37.0-13.el8" x-pid="1745"
                                    x-info="http://www.rsyslog.com"] rsyslogd was HUPed

以此为例,如果你将来想要将哪个莫名其妙的用户踢掉的话,就可以使用如上的逻辑,或者pstree找到进程,然后kill -9 删掉他的bash,这样就OK了。

killall -signal 命令名称

由于kill后面必须加上PID(或是job number),所以,通常kill都会配合ps、pstree等命令,因为我们必须要找到相对应的那个进程的PID。但是如此一来,很麻烦,有没有可能利用【执行命令的名称】来给予信号的呢?举例来说,能不能直接将rsyslogd这个进程给予一个SIGHUP的信号?当然可以,用killall。

killall [-iIe] [command name]
-i:interactive即互动的意思,若需要删除时,会提示
-e:exact的意思,表示后面接的command name要一致
       但整个完整的命令不能超过15个字符
-I:命令名称(可能含参数)忽略大小写

使用示例:

//强制终止所有已httpd启动的进程
killall -9 httpd
//依次询问每个bash进程是否需要被终止运行
killall -i -9 bash
//要注意,如果没有-i,所有bash进程都会被杀掉

总之,要删除某个进程,我们可以使用PID或是启动该进程的命令名称,而如果要删除某个服务呢?呵呵!最简单的方式就是利用killall,因为它可以将系统当中所有以某个命令名称启动的进程全部删除。

 

进程的执行顺序

我们知道Linux多人多任务的环境,由top命令的输出结果我们也发现,系统同时间有非常多的进程在运行,只是绝大部分的进程都在休眠(Sleeping)状态。想一想,如果所有的进程同时被唤醒,那么CPU应该要先处理哪个进程呢?也就是说,哪个进程被执行的优先级比较高?这就要考虑到进程的优先级(Priority)与CPU调度。

CPU调度与前一章的计划任务并不一样。CPU调度是指的是每个进程被CPU运行的规则,而计划任务则是将某个进程安排在某个时间再交由系统执行。CPU调度与操作系统较具有相关性。

Priority与Nice

我们前面学的ps  -l命令后面有一个PRI/NI字段,就是指的Priority和Nice

Priority是优先、优先事项的意思,但是在Linux进程中,这个值越低越优先

我们知道CPU一秒可以运行多达数G的指令次数,通过内核的CPU调度可以让各进程被CPU切换运行,因此每个进程在一秒钟内或多或少都会被CPU执行部分的指令。

如果进程都是集中在一个队列中等待CPU的运行,而不具有优先级之分,那么任务进程就像存储在一个队列结构中一样,必须按顺序执行,可以如果我有比较紧急的任务,这样的执行显然是不太合理的。

所以有了优先级的存在,例如A、B两个任务,A的Priority比较低(优先级高),那么可能CPU在一点时间内会运行A任务两次,而B任务仅一次,当然这只是一个例子。

不过,我们要知道PRI值是由内核动态调整的,用户无法直接调整PRI的值。

所以,如果我们要调整优先级,那么我们就要通过Nice属性了,一般来说,NI与PRI的关系如下:

PRI(new)=PRI(old)+nice

如果原本的PRI是50,我们设置的NI是5,那么就会让PRI变成55。

因为PRI是系统动态决定的,所以,虽然nice值可以影响PRI,但最终的PRI仍是要经过系统分析后才会决定的。另外,nice值是有正负的,当nice是负值的时候,该进程的PRI就会更优先。

此外,你必须注意到:

  1. nice值的调整范围是-20~19
  2. root可随意调整自己或他人进程的nice值,且范围是-20~19
  3. 一般用户仅可调整自己进程的nice值,且范围是0~19(避免一般用户抢占资源)
  4. 一般用户仅可将nice值越调越高,例如本来是5,未来只能调到大于5

那么如何给与某个进程一个nice值呢?

  • 一开始执行进程就立即给与一个特定的nice值:用nice命令
  • 调整某个已经存在的PID的nice值:用renice命令

nice:新执行的命令即给予新的nice值

nice [-n 数字] command
-n:后面接一个数值,数值的范围-20~19
[root@bat ~]# nice -n -5 vi &
[1] 4994
[root@bat ~]# ps -l
F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
4 S     0  3862  3860  0  80   0 -  1713 -      pts/1    00:00:00 bash
4 T     0  4994  3862  0  75  -5 -  1700 -      pts/1    00:00:00 vi
4 R     0  5011  3862  0  80   0 -  1624 -      pts/1    00:00:00 ps
[1]+  Stopped                 nice -n -5 vi
[root@bat ~]# kill -9 %1   //关闭vi这个工作
[1]+  Stopped                 nice -n -5 vi
[root@bat ~]# ps -l
F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
4 S     0  3862  3860  0  80   0 -  1713 -      pts/1    00:00:00 bash
4 R     0  5259  3862  0  80   0 -  1624 -      pts/1    00:00:00 ps
[1]+  已杀死               nice -n -5 vi

如同前面所说,nice用来调整进程的执行优先级,这里只是一个执行的示例罢了。通常什么时候要将nice值调大?举例来说,一般是系统的后台任务中,某些比较不重要的进程在运行:例如备份任务。由于备份任务相当地消耗系统资源,这个时候就可以将备份命令的nice值调大一些,可以使系统的资源分配得更为公平。

 

renice:已存在进程的nice重新调整

renice [number] PID
PID:某个进程的PID
[root@bat ~]# ps -l
F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
4 S     0  3862  3860  0  80   0 -  1733 -      pts/1    00:00:00 bash
4 R     0  7389  3862  0  80   0 -  1624 -      pts/1    00:00:00 ps
[root@bat ~]# renice -5 3862
3862: old priority 0, new priority -5
[root@bat ~]# ps -l
F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
4 S     0  3862  3860  0  75  -5 -  1733 -      pts/1    00:00:00 bash
4 R     0  7545  3862  0  75  -5 -  1623 -      pts/1    00:00:00 ps

如果要调整的是已经存在的某个进程的话,就要使用renice了。使用的方法很简单,renice后面接上数值及PID即可。因为后面接的是PID,所以你务必要以ps或其他进程的命令去找出PID才行。

上面的例子我们也能看出来一个事情就是修改一个进程的nice后,基于这个进程的子进程的nice也都会改变,整个nice值可以在父进程——>子进程之间进行传递,另外,除了renice后,其实那个top命令同样可以调整nice值。

 

查看系统资源信息

除了系统的进程之外,我们还必须就系统的一些资源进行检查。举例来说,我们使用top可以看到很多系统的资源对吧!那么,还有没有其他工具可以查看?当然有,下面这些工具命令可以玩一玩。

free:查看内存使用情况

free [-b|-k|-m|-g|-h] [-t] [-s N -c N]
-b:直接输入free时,显示的单位是KBytes,
     我们可以使用b(Bytes)、m(MBytes)、
     k(KBytes)、g(GBytes)来显示单位,
     也可以直接让系统自己指定单位(-h)
-t:在输出的最终结果,显示物理内存、swap的总量
-s:可以让系统不断刷新显示数据
-c:与-s同时处理,让free列出几次的意思
[root@bat mail]# free -m
             total       used       free     shared    buffers     cached
Mem:          1894        682       1211          6         34        366
-/+ buffers/cache:        281       1612
Swap:         2087          0       2087

仔细看看,我的系统当中有1894MB左右的物理内存,我的swap(交换分区)有1GB左右。

Mem那一行显示的是物理内存的量,Swap则是内存交换分区的量。total是总量,used是已被使用的量,free是剩余可用的量。后面的shared、buffers、cached则是在已被使用的量当中,用来作为缓冲及缓存的。

上例可以看出用了不少的cached,因为系统将一些我们平时使用的文件进行了缓存,等待下次运行的时候就可以更快速的取出。也就是说,系统是很有效率地将所有的内存用光光,目的为了让系统的读写性能加速。

Linux系统为了提高系统性能,会将最常使用的或是最近使用到的文件数据缓存(cache)下来,这样未来系统要使用该文件时,就可以直接由内存中查找取出,而不需要重新读取硬盘,速度上面当然就加快了。因此,物理内存被用光是正常的。

uname:查看系统与内核相关信息

uname [-asrmpi]
-a:所有系统相关的信息,包括下面的数据
-s:系统内核名称
-r:内核的版本
-m:本系统的硬件架构
-p:CPU的类型
-i:硬件的平台
[root@bat mail]# uname -a
Linux bat.com 2.6.32-642.el6.i686 #1 SMP Wed Apr 13 00:50:26 EDT 2016 i686 i686 i386 GNU/Linux

按我的信息:我的Linux主机使用的内核名称为Linux,而主机名bat.com,内核的版本是2.6.32-642.el6.i686,该内核版本建立的日期为2016-2-13,适用于i686及以上等级的硬件架构平台。

uptime:查看系统启动时间与任务负载

这个命令很单纯,就是显示出目前系统已经运行的时间,以及1、5、15分钟内的平均负载情况。还记得top吧,没错,这个uptime显示的就是top界面的最上面那一行

[root@bat mail]# uptime
 00:35:19 up 57 min,  3 users,  load average: 0.73, 0.78, 0.92

netstat:追踪网络或socket文件

这个命令Windows里也经常使用

netstat经常被用于网络监控方面,不过,在进程管理方面也是需要了解的。基本上,netstat的输出分为两大部分,分别是网络与系统自己的进程相关性部分。

netstat -[atunlp]
-a:将目前系统上所有的连接、监听、socket信息都列出来
-t:列出tcp网络封包的信息
-u:列出udp网络封包的信息
-n:不以进程的服务名称,以端口号来显示
-l:列出目前正在网络监听的服务
-p:列出网络服务的进程PID

使用netstat查看

[luoluo@study ~]$ netstat
Active Internet connections (w/o servers)  //与网络连接相关的部分
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 study.cool:49872        47.102.210.158:https    TIME_WAIT  
tcp        0      0 study.cool:59436        47.102.210.158:http     TIME_WAIT  
tcp        0      0 study.cool:43798        150.109.116.197:http    TIME_WAIT  
tcp        0      0 study.cool:58914        203.208.41.98:http      TIME_WAIT  
tcp        0      0 study.cool:57470        183.203.69.10:http      TIME_WAIT  
Active UNIX domain sockets (w/o servers)    //与本机进程的相关性(非网络)
Proto RefCnt Flags       Type       State         I-Node   Path
unix  2      [ ]         DGRAM                    48383    /run/user/1000/systemd/notify
unix  3      [ ]         DGRAM                    12762    /run/systemd/notify
unix  2      [ ]         DGRAM                    12764    /run/systemd/cgroups-agent
unix  9      [ ]         DGRAM                    12778    /run/systemd/journal/socket
unix  2      [ ]         DGRAM                    37365    /run/user/42/systemd/notify
unix  22     [ ]         DGRAM                    12790    /run/systemd/journal/dev-log
unix  3      [ ]         STREAM     CONNECTED     50770    /run/dbus/system_bus_socket
unix  3      [ ]         STREAM     CONNECTED     48616    /run/dbus/system_bus_socket
unix  3      [ ]         STREAM     CONNECTED     45759    
unix  3      [ ]         STREAM     CONNECTED     45621    /run/user/42/bus

这个结果我只复制了一部分

上面的结果显示了两个部分,分别是网络的连接以及Linux上面的socket进程相关性部分。我们先来看看因特网连接情况的部分:

  • Proto:网络的封包协议(Protocol),主要分为TCP与UDP封包,相关数据请参考服务器篇
  • Recv-Q:非由用户进程连接到此socket的复制的总Bytes数
  • Send-Q:非由远程主机传送过来的acknowledge总Byte数
  • Local Address:本地端的IP:PORT情况
  • Foreign Address:远程端的IP:PORT情况
  • State:连接状态,主要有建立(ESTABLISED)及监听(LISTEN)

除了网络上的连接之外,Linux系统上面的进程还可以沟通两个进程之间的信息,那就是Linux上面的socket文件(socket file),socket文件可以沟通两个进程之间的信息,因此进程可以获取对方传送过来的数据。由于有socket文件,因此类似X Window这种需要通过网络连接的软件,目前新的发行版就以socket来进行窗口接口的连接沟通了。上表汇总socket文件的输出字段有:

  • Proto:一般就是Linux
  • RefCnt:连接到此socket的进程数量
  • Flags:连接的标识
  • Type:socket存取的类型。主要有确认连接的STREAM与不需确认的DGRAM两种
  • State:若为CONNECTED则表示多个进程之间已经建立连接
  • Path:连接到此socket的相关进程的路径,或是相关数据输出的路径

上面就是netstat的基本使用功能。

我们还可以利用netstat去看看我们的哪些进程启动了哪些网络后门呢?

[root@study ~]# netstat -tulnp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 0.0.0.0:111             0.0.0.0:*               LISTEN      1/systemd           
tcp        0      0 192.168.122.1:53        0.0.0.0:*               LISTEN      1910/dnsmasq        
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      1252/sshd           
tcp        0      0 127.0.0.1:631           0.0.0.0:*               LISTEN      1243/cupsd          
tcp6       0      0 :::111                  :::*                    LISTEN      1/systemd           
tcp6       0      0 :::22                   :::*                    LISTEN      1252/sshd           
tcp6       0      0 ::1:631                 :::*                    LISTEN      1243/cupsd          
udp        0      0 0.0.0.0:5353            0.0.0.0:*                           933/avahi-daemon: r 
udp        0      0 0.0.0.0:46739           0.0.0.0:*                           933/avahi-daemon: r 
udp        0      0 192.168.122.1:53        0.0.0.0:*                           1910/dnsmasq        
udp        0      0 0.0.0.0:67              0.0.0.0:*                           1910/dnsmasq        
udp        0      0 0.0.0.0:111             0.0.0.0:*                           1/systemd           
udp6       0      0 :::5353                 :::*                                933/avahi-daemon: r 
udp6       0      0 :::46700                :::*                                933/avahi-daemon: r 
udp6       0      0 :::111                  :::*                                1/systemd   

除了可以列出监听网络的界面与状态之外,最后一个栏位还能够显示此服务的PID以及进程的命令名称

例如我们关闭某网络进程,就可以:

[root@study ~]# kill -9 933

dmesg:分析内核产生的信息

系统在启动的时候,内核会去检测系统的硬件,你的某些硬件到底有没有被识别,就与这个时候的检测有关。使用命令dmesg可以查看内核产生的信息(只要是内核产生的信息,都会被记录到内存的某个保护区域中,dmesg可以将这个区域的信息读出来)。

dmesg查看到的信息会非常的多,所以建议使用 | more 来使界面暂停。

vmstat:检测系统资源变化

如果你想要动态地了解一下系统资源的运行,那么这个vmstat确实可以玩一玩。vmstat可以检测【CPU/内存/磁盘I/O状态】等,如果你想要了解一个繁忙的系统到底是哪个环节最累人,可以使用vmstat分析看看。

vmstat [-a] [延迟 [总计检测次数]]
vmstat [-fs]
vmstat [-S 单位]
vmstat [-d]
vmstat [-p 分区]
-a:使用inactive/active(活动与否)替换buffer/cache的内存输出信息
-f:开机到目前为止,系统复制(fork)的进程数
-s:将一些事件导致的内存变化情况列表说明
-S:让显示的数据有单位,例如K/M替换Bytes的容量
-d:列出磁盘的读写总量统计表
-p:后面列出分区,可显示该分区的读写总量统计表

 

统计目前主机的CPU状态,每秒一次,共计三次
[root@study ~]# vmstat 1 3
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 2  0  50432 336608    116 648236    2   20   498    49  121  257  3  1 94  2  0
 0  0  50432 336548    116 648276    0    0     0     0   68  144  1  0 99  0  0
 0  0  50432 336548    116 648276    0    0     0     0   84  186  0  1 99  0  0

利用vmstat甚至可以执行追踪。

你可以类似地使用 vmstat 5,代表每5秒更新一次,永无止境。

上面的字段都是什么意思呢,我们来详细说一下:

  • 进程字段(procs)
    • r:等待运行中的进程数量
    • b:不可被唤醒的进程数量

这两个值越多,系统越繁忙

  • 内存字段(memory)
    • swpd:虚拟内存被使用的容量
    • free:未被使用的内存容量
    • buff:用于缓冲存储器
    • cache:用于高速缓存
  • 内存交换分区(swap)的项目
    • si:由磁盘中将进程取出的容量
    • so:由于内存不足而将没用到的进程写入到磁盘的swap的容量

如果si/so的数值太大,表示内存中的数据常常得在磁盘与内存之间传输,系统性能会变差

  • 磁盘读写(I/O)的项目
    • bi:由磁盘读入的区块数量
    • bo:写入到磁盘的区块数量
  • 系统(system)的项目
    • in:每秒被中断的进程次数
    • cs:每秒执行的事件切换次数

这两个数值越大,代表系统与外部设备的沟通越频繁。这些接口设备包括磁盘、网卡等

  • CPU的项目
    • us:非内核层的CPU使用状态
    • sy:内核层所使用的CPU状态
    • id:闲置的状态
    • wa:等待I/O所耗费的CPU状态
    • st:被虚拟机所使用的CPU状态(2.6.11后才支持)

 

 


 

 

商业转载 请联系作者获得授权,非商业转载 请标明出处,谢谢

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

轻舟在过

您的支持是我创作的最大动力~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值