每天学五分钟 Liunx 0011 | 服务篇:进程
1. 进程
程序放在硬盘中,在运行它的时候加载到内存,在内存里程序以进程的方式运行,进程有唯一的 ID ,叫 PID。
写个简单的 Hellow world 程序,让它产生 PID:
[root@test]$ cat test.cpp #include <stdio.h> #include <unistd.h> int main(void) { int i = 0; for(i==0;i<100;i++) { sleep(2); printf("Hello World\n"); } } [root@test]$ gcc test.cpp -o test.out [root@test]$ ./test.out & [1] 35166 [root@test]$ Hello World Hello World ^C [root@test]$ Hello World ps Hello World -lf F S UID PID PPID C PRI NI ADDR SZ WCHAN STIME TTY TIME CMD 4 S root 34824 34817 0 80 0 - 28750 do_wai 22:49 pts/15 00:00:00 -bash 0 S root 35166 34824 0 80 0 - 1056 hrtime 22:51 pts/15 00:00:00 ./test.out 0 R root 35174 34824 0 80 0 - 38356 - 22:51 pts/15 00:00:00 ps -lf [root@test]$ Hello World
执行命令 ./test.out ,Hello World 程序被执行,产生了一个 PID 为 35166 的进程。
上面示例中用到了 ps -lf 命令,该命令会查看当前终端的进程信息。各参数的意思是:
F:进程标志(Process Flags),说明进程权限。常见号码有: 4 表示进程权限为 root,1 表示该进程只可以复制(fork),不能被执行。
S:进程状态。S 表示 sleep,该进程正在睡眠状态 idle,可以被唤醒(signal)。R 表示 running,即该进程正在运行。T 表示 stop,该进程出于停止状态,可能是后台暂停或者 traced 状态。Z 表示 zombie 僵尸进程,该进程已经终止但是还常驻在内存中。D 表示该进程处于不可被唤醒的睡眠状态,如等待 I/O 的情况。
UID: 执行该进程的用户身份。
PID: 进程 ID。
PPID: 父进程的 ID。
C:表示 CPU 的使用率,单位为百分比。
PRI:Priority,表示该进程执行的优先级,系统动态调整,不可设置。
NI: Nice,加权的优先级系数,可设置;PRI/NI 数值越小表示该进程越快被 CPU 执行。
ADDR:内存相关。表示进程在内存的哪个部分,running 的进程一般显示 “-”。
SZ:内存相关。表示进程用掉多少内存。
WCHAN:表示目前进程是否正在运行,“-”表示正在运行。
STIME: 进程的启动时间。
TTY: 登陆用户的终端机位置,远程登陆则显示 pts/n 。
TIME: 进程的运行时间。
CMD: 什么命令触发了该进程。
可以看出,进程 35166 的父进程是 34824 即 bash 的进程。
在远程客户端登陆主机服务端时,主机会提供一个 bash 进程给远端,这样客户端就可以以 tty 终端来访问服务端主机了。在 bash 进程上执行命令时,bash 进程就成了执行命令产生进程的父进程。比如:
[root@test]$ ps -lf F S UID PID PPID C PRI NI ADDR SZ WCHAN STIME TTY TIME CMD 4 S root 34824 34817 0 80 0 - 28750 do_wai 22:49 pts/15 00:00:00 -bash 0 R root 38815 34824 0 80 0 - 38356 - 23:30 pts/15 00:00:00 ps -lf [root@test]$ bash [root@test]$ ps -lf F S UID PID PPID C PRI NI ADDR SZ WCHAN STIME TTY TIME CMD 4 S root 34824 34817 0 80 0 - 28750 do_wai 22:49 pts/15 00:00:00 -bash 4 S root 38838 34824 0 80 0 - 28715 do_wai 23:30 pts/15 00:00:00 bash 0 R root 38882 38838 0 80 0 - 38356 - 23:30 pts/15 00:00:00 ps -lf [root@test]$ exit exit [root@test]$ ps -lf F S UID PID PPID C PRI NI ADDR SZ WCHAN STIME TTY TIME CMD 4 S root 34824 34817 0 80 0 - 28750 do_wai 22:49 pts/15 00:00:00 -bash 0 R root 38900 34824 0 80 0 - 38356 - 23:30 pts/15 00:00:00 ps -lf
在 bash 进程 34824 上执行 bash 命令产生一个新的 bash 进程 38838,它的父进程正是 34824,并且父进程是处于 sleep 状态的。
执行 Hello World 程序使用的是 ./test.out & 命令,在命令后加 & 号会将当前工作转到后台执行,同时终端界面会打印 [1] 35166,[1] 表示该工作的 ID,35166 表示产生的进程 PID。
不过,即使工作转到后台了,程序执行的输出还是会打印到终端上。可以加个输入流,将执行的正确信息流到 std.out 中,错误信息流到 std.err 中:
[root@test]$ ./test.out 1> std.out 2> std.err & [1] 41371 [root@test]$ ps -lf F S UID PID PPID C PRI NI ADDR SZ WCHAN STIME TTY TIME CMD 4 S root 34824 34817 0 80 0 - 28750 do_wai Apr21 pts/15 00:00:00 -bash 0 S root 41371 34824 0 80 0 - 1056 hrtime 00:00 pts/15 00:00:00 ./test.out 0 R root 41379 34824 0 80 0 - 38356 - 00:00 pts/15 00:00:00 ps -lf [root@test]$ cat std.out [root@test]$
数据流到 std.out 中,但是 cat std.out 却没有记录。将 Hello World 更改下重新执行:
[root@test]$ cat test.cpp #include <stdio.h> #include <unistd.h> int main(void) { int i = 0; for(i==0;i<10;i++) { sleep(2); printf("Hello World\n"); } } [root@test]$ gcc test.cpp -o test.out [root@test]$ cat std.out [root@test]$ ./test.out 1> std.out 2> std.err & [1] 41775 [root@test]$ ps -lf F S UID PID PPID C PRI NI ADDR SZ WCHAN STIME TTY TIME CMD 4 S root 34824 34817 0 80 0 - 28750 do_wai Apr21 pts/15 00:00:00 -bash 0 S root 41775 34824 0 80 0 - 1056 hrtime 00:04 pts/15 00:00:00 ./test.out 0 R root 41779 34824 0 80 0 - 38356 - 00:04 pts/15 00:00:00 ps -lf [root@test]$ ps -lf F S UID PID PPID C PRI NI ADDR SZ WCHAN STIME TTY TIME CMD 4 S root 34824 34817 0 80 0 - 28750 do_wai Apr21 pts/15 00:00:00 -bash 0 R root 41870 34824 0 80 0 - 38356 - 00:05 pts/15 00:00:00 ps -lf [1]+ Done ./test.out > std.out 2> std.err [root@test]$ cat std.out Hello World Hello World Hello World ... [root@test]$
这次 std.out 就有输出了,对比上面两个例子可以发现,当进程执行完之后才会把数据写到 std.out 中,而不是执行一行写一行,系统会有个数据缓冲区,进程在执行的时候会将数据写到缓冲区,在进程结束之后在取出缓冲区中的数据,写到文件 std.out 中。
当然,除了 ps -lf 命令可以查看进程信息,还有诸如 ps aux 和 ps -lA 命令也可以查看进程信息。还有不少 ps 加各种选项参数的命令也也可以查看进程信息,但是最常用的还是 ps aux , ps - lA 和 ps -lf 这几个:
[root@test:/home/robot] # ps aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.4 0.0 171620 11604 ? Ss 14:16 0:26 /usr/lib/systemd/systemd --switched-root --system --deserialize 17 systemd+ 591 0.0 0.0 19152 6400 ? Ss 14:17 0:00 /usr/lib/systemd/systemd-networkd avahi 761 0.0 0.0 9656 6244 ? Ss 14:17 0:04 avahi-daemon: running [test root 766 0.0 0.0 6028 2152 ttyS0 Ss+ 14:17 0:00 /sbin/agetty -o -p -- \u --keep-baud 115200,38400,9600 ttyS0 vt220 root 767 0.0 0.0 6388 1604 tty1 Ss+ 14:17 0:00 /sbin/agetty -o -p -- \u --noclear tty1 linux root 777 0.0 0.0 614464 18100 ? Ssl 14:17 0:04 python3 -u /opt/nokia/libexec/StorageUtils/script/lsa/local_storage_agent.py avahi 778 0.0 0.0 8944 432 ? S 14:17 0:00 avahi-daemon: chroot helper _test+ 779 0.0 0.0 164384 5784 ? Ssl 14:17 0:01 /opt/nokia/bin/eventdaemon --global root 784 0.0 0.0 0 0 ? Z 14:17 0:00 [cat] <defunct> root 785 0.0 0.0 7672 1196 ? S 14:17 0:00 avahi-publish -a test.local _test+ 787 0.0 0.0 26736 16288 ? Ss 14:17 0:00 /opt/nokia/test-acl/bin/ipsetmanager ... [root@test:/home/robot] # ps -lA F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD 4 S 0 1 0 0 80 0 - 42905 SyS_ep ? 00:00:26 systemd 0 S 0 531 525 0 80 0 - 1455 hrtime ? 00:00:00 schedmon 4 S 0 534 1 0 80 0 - 1879 hrtime ? 00:00:00 hypertracerolcd 4 S 0 550 1 0 80 0 - 205661 core_s ? 00:00:15 python3 4 S 192 591 1 0 80 0 - 4788 SyS_ep ? 00:00:00 systemd-network ...
ps aux 和 ps -lA 都可以查看系统运行的所有进程,不同的是输出信息不同,ps -lA 的输出信息和 ps -lf 一致,而 ps aux 的输出信息是 %CPU / %MEM / VSZ / RSS / STAT 等等。
各参数的意思分别是:
%CPU:进程使用 CPU 的资源百分比;
%MEM:进程占用物理内存百分比;
VSZ:进程使用的虚拟内存量(KB);
RSS:进程占用的固定内存量(KB);
STAT:进程目前的状态。有的进程会同时有多个字符,它们代表的意思是:
D 不可中断,Uninterruptible sleep (usually IO)
R 正在运行,或在队列中的进程
S 处于休眠状态
T 停止或被追踪
Z 僵尸进程
X 死掉的进程
< 高优先级
N 低优先级
L 有些页被锁进内存
s 包含子进程
+ 位于后台的进程组
l 多线程,克隆线程 multi-threaded (using CLONE_THREAD, like NPTL pthreads do)
除了 ps 命令查看进程信息的命令,还有 pstree 命令可查看各进程的依赖关系:
[root@test]$ pstree -p systemd(1)─┬─abrt-dbus(45043)─┬─{abrt-dbus}(45044) │ └─{abrt-dbus}(45046) ├─agetty(2037) ├─agetty(2039) ├─atd(1989) ├─auditd(604)─┬─audispd(19274)─┬─sedispatch(19275) │ │ └─{audispd}(19276) │ └─{auditd}(605) ├─automount(2029)─┬─{automount}(2030) │ ├─{automount}(2031) │ └─{automount}(2056) ...
其中 -p 选项可查看各进程的 PID,可以看到有个 PID 为 1 的 systemd 进程,它是 Liunx 的启动的第一个进程是 systemd 进程,是所有进程的进程之王,它下面管理着很多子进程,这些子进程又会生出多个子子进程。如 PID 为 2029 的 automount 进程,它的父进程是 systemd ,它也是 2030/2031/2056 的父进程。2030/2031/2056 它们几个是兄弟进程。
ps 和 pstree 执行的结果是静态的,而使用 top 命令可以动态查看进程的变化情况:
[root@test]$ top -H top - 20:49:51 up 264 days, 3:07, 14 users, load average: 0.13, 0.08, 0.02 Threads: 567 total, 1 running, 565 sleeping, 0 stopped, 1 zombie %Cpu(s): 0.2 us, 0.1 sy, 0.0 ni, 99.6 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 37077464 total, 12919928 free, 2649664 used, 21507872 buff/cache KiB Swap: 1020 total, 0 free, 1020 used. 33678848 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 44644 root 20 0 12.7g 335660 21284 S 0.7 0.9 0:04.60 java 43794 root 20 0 12.7g 323332 21332 S 0.7 0.9 0:06.44 java 1959 fluentd 20 0 1382452 28436 4896 S 0.3 0.1 42:04.92 fluentd
top 的输出信息有好几行,从上到下分别表示:
第一行: 当前时间;系统开机到现在过了多长时间(看看 264 天没关机,这是 windows 能比的了哒);当前登陆系统的用户人数;系统在 1/5/15 分钟的平均工作负载,分别是 0.13/0.08/0.02,越小表示系统越闲置,若高于 1 表示系统 load 偏高。
第二行: 当前进程的总量,以及进程的总体运行状态。
第三行: CPU 的整体负载。us 表示 CPU 消耗在用户空间(user space)的时间百分比;sy 表示 CPU 消耗在内核空间(kernel space)的时间百分比;<关于用户空间和内核空间的文章可看这里>ni(niceness) 表示 CPU 消耗在 nice 进程(低优先级)的时间百分比;id(idle) 表示 CPU 消耗在闲置进程的时间百分比,值越低表示 CPU 越忙,这里 id 是 99.6 ,看来 CPU 不忙啊。wa(wait) CPU 等待外部 I/O 的时间百分比,在等待 I/O 的时间里 CPU 不能干其它事情;hi(hardware interrupt) CPU 响应硬件中断请求的时间百分比;si(software interrupt) CPU 响应软件中断请求的时间百分比;
第四行:物理内存 Mem 使用情况;
第五行:虚拟内存 swap 使用情况;
第六行: PR(Priority) 进程的优先级执行顺序,越小表示越早被执行,该值由系统指定,用户不能更改;NI(nice)与优先级相关,用户可更改。
2. 工作管理
将程序/命令转到后台执行的形式都属于工作管理的范畴,顾名思义,工作管理就是管理 Liunx 系统下各个工作的。使用 & 将命令转到后台执行,搭配流输入将数据转到文件中,从而避免在终端界面显示,前面已经演示过了。
& 命令将工作转到后台,工作是出于运行状态的。而直接使用快捷键 Ctrl + Z 会将工作转到后台并处于暂停状态:
[root@test test]# vi log.txt [1]+ Stopped vi log.txt [root@test test]# ps -lf F S UID PID PPID C PRI NI ADDR SZ WCHAN STIME TTY TIME CMD 0 S root 107543 107542 0 80 0 - 6374 - 15:58 pts/0 00:00:00 -bash 0 T root 108848 107543 0 80 0 - 9126 - 15:58 pts/0 00:00:00 vi log.txt 0 R root 109559 107543 0 80 0 - 14894 - 15:58 pts/0 00:00:00 ps -lf [root@test test]#
命令 vi log.txt 产生的进程被转到后台,并且它的状态是 T stopped。
jobs 命令可以查看当前工作的情况,fg 命令可将当前工作从后台转到前台,并且该工作是出于运行状态:
[root@test test]# jobs [1]+ Stopped vi log.txt [root@test test]# fg %1 vi log.txt [root@test test]# jobs [1]- Stopped vi log.txt [2]+ Stopped vi abc [root@test test]# fg vi abc
注意,fg 后加 job 编号即表示将该 job 从后台转到前台,前面要加 % 号。jobs 的输出 job 编号后有 + 和 - 号,+ 表示默认调用的工作,不加编号的 fg 命令会调用 + 所在的那个 job。
fg 命令是将工作从后台转到前台。bg 命令会将后台出于暂停状态的工作转为运行状态,同样的,bg 后需加 % 和工作编号:
[root@test]$ ./test.out Hello World ^Z [1]+ Stopped ./test.out [root@test]$ ps -lf F S UID PID PPID C PRI NI ADDR SZ WCHAN STIME TTY TIME CMD 4 S root 49612 49605 0 80 0 - 28750 do_wai 00:13 pts/15 00:00:00 -bash 0 T root 49854 49612 0 80 0 - 1056 do_sig 00:14 pts/15 00:00:00 ./test.out 0 R root 49880 49612 0 80 0 - 38356 - 00:14 pts/15 00:00:00 ps -lf [root@test]$ bg %1 [1]+ ./test.out & Hello World [root@test]$ Hello World Hello World ^C [root@test]$ Hello World Hello World Hello World
上面的例子中,程序转到后台执行时会循环打印 Hello World,使用 Ctrl + C 也没办法将终止程序,还是会继续打印。此时就要用到 kill 命令杀掉执行该程序的进程或者杀掉该工作。
kill -l 命令可查看 kill 能够使用的信号:
[root@test]$ kill -l 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAX
通常使用的最多的信号就是 1/9/15。1 SIGHUP 表示重新读取参数的配置文件,9 SIGKILL 强制删除工作/进程,15 SIGTERM 以正常方式结束一个工作/进程,如果一个工作/进程不能以正常方式结束掉,还是要使用 9 强制结束的。
不加信号的 kill 默认使用的是 15 SIGTERM 这个信号。如果要 kill 工作的话,需要在工作编号前加 % 号,默认 kill 的是进程的 PID,下面用两种方式 kill Hello World 程序产生的进程:
# kill -9 %n [root@test]$ ./test.out Hello World Hello World ^Z [1]+ Stopped ./test.out [root@test]$ kill -9 %1 [1]+ Killed ./test.out # kill -9 PID [root@test]$ ./test.out Hello World ^Z [1]+ Stopped ./test.out [root@test]$ ps -lf F S UID PID PPID C PRI NI ADDR SZ WCHAN STIME TTY TIME CMD 4 S root 49612 49605 0 80 0 - 28750 do_wai 00:13 pts/15 00:00:00 -bash 0 T root 50944 49612 0 80 0 - 1056 do_sig 00:28 pts/15 00:00:00 ./test.out 0 R root 50950 49612 0 80 0 - 38356 - 00:28 pts/15 00:00:00 ps -lf [root@test]$ bg %1 [1]+ ./test.out & Hello World [root@test]$ Hello World Hello World [1]+ Killed ./test.out [root@tu-qiaolin-683ci-1 ~]$ kill -9 50944 # exec at another tty
使用 & 和 Ctrl + Z 将工作放到后台运行时,如果当前终端关闭了那么相应的工作也会被停掉(当前终端 bash 是 & 和 Ctrl + Z 的父进程,父进程关掉了,相应的子进程也会随之结束),使得工作不能继续。Liunx 提供了 nohup 命令可以将工作放到系统后台运行,即使当前终端关闭了,工作还是可以继续运行(当前终端关闭了,nohup 会将 systemd 作为自己的父进程继续执行),举例:
[root@test]$ cat test.cpp #include <stdio.h> #include <unistd.h> int main(void) { int i = 0; for(i==0;i<20;i++) { sleep(2); printf("Hello World\n"); } } [root@test]$ gcc test.cpp -o test.out [root@test]$ nohup ./test.out & [1] 50772 [root@test]$ nohup: ignoring input and appending output to ‘nohup.out’ [root@test]$ ps -lf F S UID PID PPID C PRI NI ADDR SZ WCHAN STIME TTY TIME CMD 4 S root 45024 45018 0 80 0 - 28752 do_wai 20:39 pts/15 00:00:00 -bash 0 S root 50772 45024 0 80 0 - 1056 hrtime 21:41 pts/15 00:00:00 ./test.out 0 R root 50777 45024 0 80 0 - 38356 - 21:41 pts/15 00:00:00 ps -lf [root@test]$ exit logout ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Session stopped Last login: Thu Apr 23 23:48:09 2020 from 10.140.119.177 [root@tu-qiaolin-683ci-1 ~]$ ps -lA | grep 50772 0 S 0 50772 1 0 80 0 - 1056 hrtime ? 00:00:00 test.out [root@tu-qiaolin-683ci-1 ~]$ cat test/nohup.out Hello World Hello World Hello World ... [root@tu-qiaolin-683ci-1 ~]$
退出了当前终端,工作还是继续在系统后台运行,且 nohup 产生的进程被 systemd 进程给接管了。
(完)
芝兰生于空谷,不以无人而不芳。