Linux进程控制和管理
1.1 什么是进程?
进程是 UNIX/Linux 用来表示正在运行的程序的一种抽象概念,所有系统上面运行的的数据都会以进程的形态存在。
1.2 进程的组成部分
一个进程由一个地址空间和内核内部的一组数据公同组成,地址空间是由内核标记出来供进程使用的一组内存页面(页面是管理内存的单位,页面大小通常是 1KB 或 8KB)。它包含进程正在执行的代码、库、进程变量、进程栈以及进程正在运行时内核所需要的各种其他信息。
内核的内部数据结构记录了有关每个进程的各种信息,其中非常重要的一些信息有:
- 进程的属主;
- 进程的信号掩码(一个记录,确定要封锁哪些信号);
- 进程已打开的文件和网络端口的信息;
- 进程执行的优先级;
- 进程的当前状态(睡眠状态、停止状态、可运行状态等等);
- 进程的地址空间映射。
1.3 子进程与父进程
每个进程都有一个唯一的 PID(Process ID),进程必须克隆自身去创建一个新进程。克隆出的进程能够把它正在运行的那个程序替换成另一个不同的程序。
当一个进程被克隆时,原来的进程就叫做父进程 PPID(Parent Process ID),而克隆出的副本则叫做子进程。进程的 PPID 属性就是克隆它的父进程的 PID。
我们以一个实例加深对子进程与父进程的理解:在目前的 bash 环境下,再出发一次 bash ,并以 ps -l 命令观察进程 PID、PPID 的输出信息。
[root@web ~]# ps -l //第一个 bash 的 PID 是 1363 F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD 4 S 0 1363 1322 0 80 0 - 28864 do_wai pts/1 00:00:00 bash 0 R 0 1484 1363 0 80 0 - 38314 - pts/1 00:00:00 ps [root@web ~]# bash //执行 bash 进入到子程序的环境中 [root@web ~]# ps -l //第二个 bash 的 PID 是 1487 , 它的 PPID 就是第一个 bash 的 PID 1363 F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD 4 S 0 1363 1322 0 80 0 - 28864 do_wai pts/1 00:00:00 bash 4 S 0 1487 1363 0 80 0 - 28864 do_wai pts/1 00:00:00 bash 0 R 0 1500 1487 0 80 0 - 38314 - pts/1 00:00:00 ps
1.4 特殊进程
Linux 有三个特殊进程,idle 进程(PID=0),init 进程(PID=1),kthreadd(PID=2)。
idle 进程
idle 进程由系统自动创建的第一个进程, 运行在内核态,也是唯一一个没有通过 fork 或者 kernel_thread 产生的进程。完成加载系统后,演变为进程调度、交换。
init 进程
Linux 的所有进程都是有 init 进程创建并运行的。首先 Linux 内核启动,然后在用户空间中启动 init 进程,再启动其他系统进程。在系统启动完成完成后,init 将变为守护进程监视系统其他进程。
在我的 CentOS 7 系统里面,可以 ls 看到 init 进程是被软连接到 systemd 的。
[root@web ~]# ls -al /usr/sbin/init lrwxrwxrwx 1 root root 22 Apr 26 11:07 /usr/sbin/init -> ../lib/systemd/system
系统启动之后,init 进程会启动很多 daemon 进程,为启动运行提供服务,比如 httpd、ssh 等等,然后就是 agetty,让用户登录,登录后运行 shell(bash),用户启动的进程都是通过shell 运行的。
执行 ps -ef 命令会发现 PID 1 的进程就是我们的 init 进程 systemd ,PID 2 的进程是内核现场 kthreadd ,这两个在内核启动的时候都见过,其中用户态的不带中括号,内核态的带中括号。
[root@web ~]# ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 0 2019 ? 00:12:54 /usr/lib/systemd/systemd --system --deserialize 24 root 2 0 0 2019 ? 00:00:01 [kthreadd] root 3 2 0 2019 ? 00:03:10 [ksoftirqd/0] root 5 2 0 2019 ? 00:00:00 [kworker/0:0H] root 7 2 0 2019 ? 00:00:00 [migration/0] ... root 23086 23085 0 10:23 pts/3 00:00:00 su - root root 23087 23086 0 10:23 pts/3 00:00:00 -bash root 24529 2 0 16:28 ? 00:00:00 [kworker/0:2] root 25448 2 0 16:33 ? 00:00:00 [kworker/0:0] root 25861 2 0 16:36 ? 00:00:00 [kworker/u2:0] root 25863 1 0 Mar20 ? 01:58:45 /usr/local/qcloud/YunJing/YDEyes/YDService root 25916 1 0 Mar20 ? 00:06:03 /usr/local/qcloud/YunJing/YDLive/YDLive root 26359 2 0 16:38 ? 00:00:00 [kworker/0:1] root 26957 2 0 16:42 ? 00:00:00 [kworker/u2:1] root 27254 23087 0 16:43 pts/3 00:00:00 ps -ef root 27732 1 0 Apr28 ? 00:00:00 ./fork.o root 29944 1 0 Apr06 ? 00:18:37 /usr/bin/dockerd-current --add-runtime docker-runc=/usr/libexec/docker/docker-runc-current --default-runtime=docker-runc --ex root 29949 29944 0 Apr06 ? 00:12:40 /usr/bin/docker-containerd-current -l unix:///var/run/docker/libcontainerd/docker-containerd.sock --metrics-interval=0 --star root 30648 1 0 2019 ? 00:00:00 /usr/lib/systemd/systemd-udevd root 30936 1 0 2019 ? 00:01:06 /usr/sbin/crond -n chrony 32061 1 0 2019 ? 00:00:07 /usr/sbin/chronyd
sshd 的父进程是 1,pts 的父进程是 sshd,bash 的父进程是 pts,ps -ef 这个命令的父进程是 bash,这样这个子父进程的关系就比较清晰了。
[root@web ~]# ps -ef|grep sshd root 1299 1 0 12:06 ? 00:00:04 /usr/sbin/sshd -D root 6008 1299 0 16:49 ? 00:00:00 sshd: root@pts/0 root 6415 1299 2 17:07 ? 00:00:00 sshd: root [priv] sshd 6416 6415 0 17:07 ? 00:00:00 sshd: root [net] root 6420 6012 0 17:07 pts/0 00:00:00 grep --color=auto sshd [root@web ~]# ps -ef|grep bash root 6012 6008 0 16:49 pts/0 00:00:00 -bash root 6457 6012 0 17:10 pts/0 00:00:00 grep --color=auto bash [root@web ~]#
kthreadd 进程
kthreadd 进程由 idle 通过 kernel_thread 创建,并始终运行在内核空间,负责所有内核线程的调度和管理,所有的内核线程都是直接或者间接的以 kthreadd 为父进程。
1.5 进程的优先级
Linux 是多人多任务的环境,由 top 的输出结果我们也发现, 系统同时间有非常多的程序在运行中,叧是大部分的程序都在休眠 (sleeping) 状态而已。如果所有的程序同时被唤醒,那 CPU 应该要先处理那个程序呢?
具有优先级的程序队列图:
我们知道 CPU 一秒钟可以运作多达数 G 的微指令次数,透过核心的 CPU 排程可以让各程序被 CPU 所切换运作, 因此每个程序在一秒钟内或多或少都会被 CPU 执行部分的脚本。Linux 给予程序一个所谓的优先执行顺序 (priority, PRI), 这个PRI 值越低代表越优先的意思。不过这个 PRI 值是由内核动态调整的,用户无法直接调整 PRI 值的。
[root@web ~]# ps -l F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD 4 S 0 6012 6008 0 80 0 - 28864 do_wai pts/0 00:00:00 bash 0 R 0 7028 6012 0 80 0 - 38314 - pts/0 00:00:00 ps
由于 PRI 是内核动态调整的,无法去干涉 PRI,可以通过调整 Nice 的值去调整程序的优先执行顺序,就是上面的 NI 值。PRI 与 NI的相关性如下:
PRI(new)= PRI(old)+ Nice
nice 值是有正负的,PRI 越小越早被执行, 所以当nice 值为负值时,就会降低 PRI 值,即是会较优先被处理。
- nice 值范围为 -20 ~ 19 ;
- root 可随意调整自己或他人程序的 Nice 值;
- 一般用户仅可条整自己程序的 Nice 值,范围仅为 0 ~ 19 (避免一般用户抢占系统资源);
- 一般使用者仅可将 nice 值调高,例如本来 nice 为 5 ,只能调整到大于 5;
使用 nice 或 renice 命令调整程序的 nice 值
nice:给予新执行指令新的 nice 值
以下只是一个示例,在实际环境并没有作用,一般在系统中不重要的程序才需要调大 nice 的值,比如备份工作,由于备份比较消耗资源,调大备份指令的 nice 值,可以使系统的资源分配更为公平。
[root@web ~]# nice -n 19 vim /tmp/a.txt & [1] 7720 [root@web ~]# ps -l F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD 4 S 0 6012 6008 0 80 0 - 28864 do_wai pts/0 00:00:00 bash 0 T 0 7720 6012 1 99 19 - 36725 do_sig pts/0 00:00:00 vim 0 R 0 7725 6012 0 80 0 - 38314 - pts/0 00:00:00 ps
renice:已运行程序的 nice 重新调整
[root@web ~]# ps -l F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD 4 S 0 6012 6008 0 80 0 - 28864 do_wai pts/0 00:00:00 bash 0 T 0 7720 6012 0 99 19 - 36725 do_sig pts/0 00:00:00 vim 0 R 0 7911 6012 0 80 0 - 38314 - pts/0 00:00:00 ps [root@web ~]# renice 19 6012 6012 (process ID) old priority 0, new priority 19 [root@web ~]# ps -l F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD 4 S 0 6012 6008 0 99 19 - 28864 do_wai pts/0 00:00:00 bash 0 T 0 7720 6012 0 99 19 - 36725 do_sig pts/0 00:00:00 vim 0 R 0 7921 6012 0 99 19 - 38314 - pts/0 00:00:00 ps
1.6 /proc 文件系统
Linux 的 ps、top 命令都是从 /proc 目录读取进程的状态信息,但是它里面的信息却不局限于进程的信息--内核产生的所有状态信息和统计信息也都在 /proc 。
进程的信息都分别放到 /proc/[PID] 按 PID 命名的子目录里面
[root@web ~]# ls /proc 1 114 119 1252 1296 139 17 21 30499 31763 32066 32174 346 375 380 538 550 609 667 9 bus crypto execdomains iomem keys kpageflags misc pagetypeinfo scsi swaps timer_list vmstat 10 115 120 1254 1299 14 18 22 30680 318 32078 32175 355 376 381 546 551 610 705 973 cgroups devices fb ioports key-users loadavg modules partitions self sys tty zoneinfo 11 116 123 126 13 140 19 29178 31314 319 321 32176 356 377 4 547 552 615 708 979 cmdline diskstats filesystems irq kmsg locks mounts pressure slabinfo sysrq-trigger uptime 112 117 124 127 1310 15 2 29184 31610 31952 32113 32177 373 378 460 548 583 618 8 acpi consoles dma fs kallsyms kpagecgroup mdstat mtrr sched_debug softirqs sysvipc version 113 118 125 128 1314 16 20 3 317 320 32116 32178 374 379 487 549 6 627 878 buddyinfo cpuinfo driver interrupts kcore kpagecount meminfo net schedstat stat thread-self vmallocinfo
/proc 目录包含的几个最有用的文件/目录内容信息:
文件/目录 | 内容 |
/proc/cmdline | 加载 kernel 时下达的相关参数,查看此文件可了解系统启动的一些信息 |
/proc/meminfo | 内存信息 |
/proc/cpuinfo | cpu 相关信息,包含频率、类型、运算功能等等 |
/proc/mounts | 系统已经挂载的数据,执行 mount 命令直接读取此文件 |
/proc/modules | 已经加载的模块列表(驱动程序) |
/proc/[pid]/attr | 此目录中的文件提供了用于安全相关模块的 API |
/proc/[pid]/cwd | 链到进程当前目录的符号链接(软链接) |
/proc/[pid]/cmdline | 进程的完整命令行 |
/proc/[pid]/environ | 进程的环境变量 |
/proc/[pid]/exe | 链到正在被执行的文件的符号链接 |
/proc/[pid]/fd | 子目录,包含链到每个打开文件的描述符的链接 |
/proc/[pid]/maps | 内存映射信息(共享段、库等) |
/proc/[pid]/root | 链到进程的根目录(由 chroot 设置)的符号链接 |
/proc/[pid]/status | 进程大量的信息,包含进程的 name、state、ppid 等等 |
/proc/[pid]/stat | 进程的总体状态信息(ps使用这个) |
注:man proce 查看更多更详细的 /proc信息
1.7 信号
进程是可以通过信号(signal)控制的,比如重启、关闭、停止进程。
主要的信号代号名称以及内容:
代号 | 名称 | 内容 |
1 | SIGHUB | 启动被终止的进序,可让该 PID 重新读取自己的配置文件,类似重新启动 |
2 | SIGINT | 相当于用键盘输入【ctrl +c】来中断一个程序的进行 |
9 | SIGKILL | 强制中断进程,如果该进程运行到一半,那么尚未完成的部分可能会有【半产品】产生, 类似 vim 会有 .filename.swp 保留下来 |
15 | SIGTERM | 以正常的结束进程来终止该进程,由于是正常的终止,所以后续的动作会将它完成, 如果该进程已经发生问题,无法使用正常的终止方法,使用这个也没用 |
17 | SIGSTOP | 相当于用键盘输入【ctrl +z】来暂停一个进程 |
注:更多详细信息可执行 kill -l 或 man 7 signal 命令查看
kill:发送信号
顾名思义,kill 命令最常见的用法是终止一个进程,默认情况下发送 TERM 信号,语法是:
kill [signal] pid
signal 就是要发送信号的编号,没有带信号的编号的kill命令不保证进程会被杀死,因为 TERM 信号可能会被捕获、封锁或忽略。下面的命令:
kill -9 pid
将“保证”进程的消亡,因为是信号 9,即 KILL 不能被捕获到。给“保证”加引号是因为进程的生命力有时候能够变得相当“旺盛”,以致连 kill 9 也不能影响他们(通常是由于有些退化的 I/O 虚假锁定,例如等等已经停止旋转的磁盘)。
重新启动系统通常是解决这些“不听话”进程的唯一方法。
killall 命令可以按照进程的名字杀死所有进程,语法:
killall process_name
1.8 ps:监视进程
ps 用于报告当前系统的进程状态,是最基本同时也是非常强大的进程查看命令,ps 通过读取 /proc 中的虚拟文件来工作。
ps 的选项参数众多,只需记住几个常用的选项就可以满足平时查看系统进程 99% 的需要,遇到特殊情况的 %1,不懂的时候使用 man ps 。
ps 常用选项:
-A:显示所有进程,等于 -e 选项
-C:通过名字搜索进程 -f:显示当前用户运行进程的 UID、PID、PPID 等等 f:以 ASCII 字符显示树状结构,表达进程间的相互关系,类似 -H 选项 L:列出栏位相关信息 -l 或 l:采用详细的格式显示当前用户的进程信息 -o:自定义显示进程信息,比如可以根据ps L显示的栏目信息进行自定义输出的进程信息:ps -eo 'tty,comm,pid,ppid,%cpu,%mem',或者根据AIX格式: ps -eo "%p %y %x %c" -p:列出指定 pid的进程信息,类似 n 选项
-t/t:指定 tty 终端机编号,并列出属于该终端机的程序的状况。比如 ps -t 4 或 ps -t pts/4
-u/-U:列出指定用户的进程信息
x:显示所有程序,不以终端机来区分。
ps 常用命令组合:
查看所有进程信息: ps -ef ps aux 查看进程树: ps -ejH ps -axjf 查看线程有关的信息: ps -eLf ps axms
ps常用实例
ps axo pid,comm,pcpu # 查看进程的 PID、名称以及 CPU 占用率 ps aux | sort -rnk 4 # 按内存资源的使用量对进程进行排序 ps aux | sort -nk 3 # 按 CPU 资源的使用量对进程进行排序 ps -u root # 显示指定用户信息 ps -e -o "%C : %p :%z : %a" | sort -k5 -nr # 查看进程并按内存使用大小排列 ps -ef # 显示所有进程信息,连同命令行 ps -ef | grep ssh # ps 与 grep 常用组合用法,查找特定进程 ps -C nginx # 通过名字搜索进程 ps aux --sort=-pcpu,+pmem # CPU或者内存进行排序,-降序,+升序 ps -f --forest -C nginx # 用树的风格显示进程的层次关系 ps -o pid,ppid,uname,comm -C nginx # 显示一个父进程的子进程 ps -e -o pid,uname=USERNAME,pcpu=CPU_USAGE,pmem,comm # 重定义栏目名字 ps -e -o pid,comm,etime # 显示进程运行的时间 ps -aux | grep named # 查看named进程详细信息 ps -o command -p 91730 | sed -n 2p # 通过进程id获取服务名称
ps 的字段说明
[root@web ~]# ps -l F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD 4 S 0 3672 3649 0 80 0 - 28864 do_wai pts/2 00:00:00 bash 0 R 0 4596 3672 0 80 0 - 38314 - pts/2 00:00:00 ps
key | long | description |
F | flags | 4代表进程的权限位 root;1代表此子进程仅进行复制(fork)而没有实际执行(exec) |
S | Stat |
进程的状态主要有: D(Disk Sleep):不可中断状态说明(Uninterruptible Sleep),一般表示进程正在跟硬件交互不允许被其他进程中断; T(Stopped/Traced):停止状态,背景暂停或调试状态; Z(Zombie):僵尸状态,进程已经被终止但却无法被移除至内存外。 |
UID | User ID | 用户ID |
PID | Process ID | 进程ID |
C | Cpu | cpu 使用率,单位是百分比 |
PRI | Priority | Priority值 |
NI | Nice | nice值 |
ADDR | 内存有关 | ADDR是kernel function,指出进程在内存的哪个部分,如果是running,一般显示- |
SZ | Size | 进程使用的内存 |
WCHAN | WCHAN | 进程所在的内核函数的名称,如果进程是多线程则显示* |
TTY | controlling tty (terminal) | 登录者的终端机位置,若为远程则使用动态端口接口(pts/n) |
TIME | TIME | 消费CPU运作时间,这里不是系统时间,"[DD-]HH:MM:SS"格式。(别名cputime)。 |
CMD | COMMAND | 触发进程的指令名称 |
VSZ | VSIZE | 进程的虚拟内存大小,单位为KB |
RSS | RSS | 进程占用的固定内存,单位KB |
1.9 top:动态监视进程
ps 命令只能提供系统进程的一次性快照,因此,需要使用 top 命令来获得系统正在发生事情的“全景”。
常用 top 选项:
c/-c:显示完整的 Command-line/Program-name -d:屏幕刷新间隔时间,默认是1秒 u/-u/U/-U:指定用户名 -p/p:指定进程 pid -n:循环显示的次数
top 显示 cpu 相关参数说明:
us, user : 用户进程占用cpu百分比 sy, system :内核进程占用cpu百分比 ni, nice :经过改变优先级的用户进程占用cpu百分比 id, idle : 空闲cpu百分比 wa, IO-wait : 等待I/O进程的百分比 hi : time spent servicing hardware interrupts si : time spent servicing software interrupts st : time stolen from this vm by the hypervisor
常用 top 交互指令:
f/F: 从当前显示中添加或者删除项目。 l:切换显示平均负载和启动时间信息。 m/M:切换显示内存信息/根据驻留内存大小进行排序。 t:切换显示进程和CPU状态信息。 c:切换显示命令名称和完整命令行。 P:根据CPU使用百分比大小进行排序。 q 退出
1.10 strace:追踪信号和系统调用
有时候通过 ps、top 这样的命令来判断一个进程实际正在做什么相当困难,但通过 strace 命令直接观察一个进程,进程每调用一次系统调用,以及接受到一个信息,这个命令都能显示出来。
在 man strace 手册里有其中大多数功能的说明,例如,-f 标准后面跟 fork 出来的进程,这个可以帮助跟踪像 httpd 这个生出好多子进程的守护进程,-e 这个文件选项只显示文件操作,对应找到难以定位的配置文件的位置特别方便。
把 strace 附在正在执行的进程上,监视一会该进程,再从这个进程脱离。从而获取进程的活动信息情况。
比如下面是 strace 附在一个活动的 top 进程上获得的信息:
[root@web ~]# strace -p 29669 strace: Process 29669 attached pselect6(1, [0], NULL, NULL, {1, 921941517}, {[WINCH], 8}) = 0 (Timeout) lseek(6, 0, SEEK_SET) = 0 read(6, "MemTotal: 1882216 kB\nMemF"..., 8191) = 1282 lseek(5, 0, SEEK_SET) = 0 read(5, "12352734.94 12094491.68\n", 8191) = 24 openat(AT_FDCWD, "/proc", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 8 getdents(8, /* 168 entries */, 32768) = 4776 stat("/proc/1", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0 open("/proc/1/stat", O_RDONLY) = 9 read(9, "1 (systemd) S 0 1 1 0 -1 4202752"..., 1024) = 390 close(9) = 0 open("/proc/1/statm", O_RDONLY) = 9 read(9, "12925 919 600 355 0 2354 0\n", 1024) = 27 close(9) = 0 stat("/proc/2", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0 open("/proc/2/stat", O_RDONLY) = 9 read(9, "2 (kthreadd) S 0 0 0 0 -1 213817"..., 1024) = 170 close(9) = 0 open("/proc/2/statm", O_RDONLY) = 9 read(9, "0 0 0 0 0 0 0\n", 1024) = 14
......
在本例中,top 先打开 /proc 目录,用stat获得其信息,然后读取该目录的内容,由此获得当前正在运行的进程清单,top 接着用 stat 获得代表 init 进程的那个目录的信息,然后打开 /proc/1/stat 读取 init 的状态信息。
strace 实践
//编写一个简单的c语言代码test.c #include <stdio.h> int main() { int a; scanf("%d", &a); printf("%09d\n", a); return 0; } //编译后得到可执行文件test,然后使用strace调用执行 [root@web demo]# gcc -o test test.c [root@web demo]# ./test // 直接执行test的结果 68 000000068 [root@web demo]# strace ./test //通过strace执行的结果 execve("./test", ["./test"], [/* 20 vars */]) = 0 brk(NULL) = 0x16c9000 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f4d7e181000 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=38904, ...}) = 0 mmap(NULL, 38904, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f4d7e177000 close(3) = 0 open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\20&\2\0\0\0\0\0"..., 832) = 832 fstat(3, {st_mode=S_IFREG|0755, st_size=2156160, ...}) = 0 mmap(NULL, 3985888, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f4d7db93000 mprotect(0x7f4d7dd56000, 2097152, PROT_NONE) = 0 mmap(0x7f4d7df56000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c3000) = 0x7f4d7df56000 mmap(0x7f4d7df5c000, 16864, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f4d7df5c000 close(3) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f4d7e176000 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f4d7e174000 arch_prctl(ARCH_SET_FS, 0x7f4d7e174740) = 0 mprotect(0x7f4d7df56000, 16384, PROT_READ) = 0 mprotect(0x600000, 4096, PROT_READ) = 0 mprotect(0x7f4d7e182000, 4096, PROT_READ) = 0 munmap(0x7f4d7e177000, 38904) = 0 fstat(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f4d7e180000 read(0, 68 "68\n", 1024) = 3 fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f4d7e17f000 write(1, "000000068\n", 10000000068 ) = 10 exit_group(0) = ? +++ exited with 0 +++ [root@web demo]# strace -c ./test //获取进程所有的系统调用的统计分析 ^Cstrace: Process 2009 detached % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 0.00 0.000000 0 1 read 0.00 0.000000 0 2 open 0.00 0.000000 0 2 close 0.00 0.000000 0 3 fstat 0.00 0.000000 0 8 mmap 0.00 0.000000 0 4 mprotect 0.00 0.000000 0 1 munmap 0.00 0.000000 0 1 brk 0.00 0.000000 0 1 1 access 0.00 0.000000 0 1 execve 0.00 0.000000 0 1 arch_prctl ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000000 25 1 total
1.11 lsof:列出被进程打开的文件
常用选项:
-a:列出打开某文件的进程; -c<进程名>:列出指定进程所打开的文件; +D<目录>:递归列出目录下被打开的文件; -i<条件>:列出符合条件的进程。(4、6、协议、:端口、 @ip ) -p<进程号>:列出指定进程号所打开的文件; -u:列出UID号进程详情;
lsof 实例
[root@web ~]# lsof -i // 显示所有连接 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME sshd 2808 root 3u IPv4 89562037 0t0 TCP web:22999->225.73.55.115:policyserver (ESTABLISHED) sshd 2816 david 3u IPv4 89562037 0t0 TCP web:22999->225.73.55.115:policyserver (ESTABLISHED) ...... [root@web ~]# lsof -i 6 // 显示 ipv6连接 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME master 5880 root 14u IPv6 52188842 0t0 TCP VM_0_13_centos:smtp (LISTEN) chronyd 32061 chrony 6u IPv6 3289514 0t0 UDP VM_0_13_centos:323 ....... [root@web ~]# lsof -iTCP //显示tcp连接 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME sshd 2808 root 3u IPv4 89562037 0t0 TCP web:22999->225.73.55.115:policyserver (ESTABLISHED) sshd 2816 david 3u IPv4 89562037 0t0 TCP web:22999->225.73.55.115:policyserver (ESTABLISHED) taskmgr 3317 root 0u IPv4 89581455 0t0 TCP web:53644->103.117.121.93:6677 (SYN_SENT) master 5880 root 13u IPv4 52188841 0t0 TCP VM_0_13_centos:smtp (LISTEN) master 5880 root 14u IPv6 52188842 0t0 TCP VM_0_13_centos:smtp (LISTEN) sshd 8673 root 3u IPv4 89580550 0t0 TCP web:22999->225.73.55.115:3comfaxrpc (ESTABLISHED)...... [root@web ~]# lsof -i:80 // 显示监听80端口相关的进程信息 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME nginx 23008 root 8u IPv4 56706878 0t0 TCP *:http (LISTEN) nginx 23009 www 8u IPv4 56706878 0t0 TCP *:http (LISTEN) [root@web ~]# lsof -i@225.73.55.115 // 显示指定主机连接的进程信息 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME sshd 2808 root 3u IPv4 89562037 0t0 TCP web:22999->225.73.55.115:policyserver (ESTABLISHED) sshd 2816 david 3u IPv4 89562037 0t0 TCP web:22999->225.73.55.115:policyserver (ESTABLISHED) sshd 8673 root 3u IPv4 89580550 0t0 TCP web:22999->225.73.55.115:3comfaxrpc (ESTABLISHED) sshd 8676 david 3u IPv4 89580550 0t0 TCP web:22999->225.73.55.115:3comfaxrpc (ESTABLISHED) nginx 23009 www 3u IPv4 89582028 0t0 TCP web:http->225.73.55.115:fjhpjp (ESTABLISHED) [root@web ~]# lsof -i -sTCP:ESTABLISHED // 显示tcp连接的进程信息 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME sshd 2808 root 3u IPv4 89562037 0t0 TCP web:22999->225.73.55.115:policyserver (ESTABLISHED) sshd 2816 david 3u IPv4 89562037 0t0 TCP web:22999->225.73.55.115:policyserver (ESTABLISHED) sshd 8673 root 3u IPv4 89580550 0t0 TCP web:22999->225.73.55.115:3comfaxrpc (ESTABLISHED) sshd 8676 david 3u IPv4 89580550 0t0 TCP web:22999->225.73.55.115:3comfaxrpc (ESTABLISHED) nginx 23009 www 3u IPv4 89582028 0t0 TCP web:http->225.73.55.115:fjhpjp (ESTABLISHED) YDService 25863 root 6u IPv4 64551180 0t0 TCP web:51422->169.254.0.55:lsi-bobcat (ESTABLISHED) [root@web ~]# lsof -u david //显示指定用户打开的文件 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME sshd 2816 david cwd DIR 253,1 4096 2 / sshd 2816 david rtd DIR 253,1 4096 2 / sshd 2816 david txt REG 253,1 852856 16203 /usr/sbin/sshd sshd 2816 david mem REG 253,1 37168 16740 /usr/lib64/libnss_sss.so.2 sshd 2816 david mem REG 253,1 15480 8530 /usr/lib64/security/pam_lastlog.so ...... [root@web ~]# kill -9 `lsof -t -u david` // 杀死指定用户运行的进程 [root@web ~]# lsof -c nginx // 列出指定进程打开的文件 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME nginx 23008 root cwd DIR 253,1 4096 919161 /data/application/nginx-1.16.0/conf/vhosts nginx 23008 root rtd DIR 253,1 4096 2 / nginx 23008 root txt REG 253,1 7299448 919146 /data/application/nginx-1.16.0/sbin/nginx nginx 23008 root mem REG 253,1 61624 21247 /usr/lib64/libnss_files-2.17.so nginx 23008 root mem REG 253,1 155784 4428 /usr/lib64/libselinux.so.1 nginx 23008 root mem REG 253,1 105824 21252 /usr/lib64/libresolv-2.17.so ...... [root@web ~]# lsof -p 23009 //列出指定进程号打开的文件 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME nginx 23009 www cwd DIR 253,1 4096 919161 /data/application/nginx-1.16.0/conf/vhosts nginx 23009 www rtd DIR 253,1 4096 2 / nginx 23009 www txt REG 253,1 7299448 919146 /data/application/nginx-1.16.0/sbin/nginx nginx 23009 www mem REG 253,1 37168 16740 /usr/lib64/libnss_sss.so.2 nginx 23009 www mem REG 253,1 61624 21247 /usr/lib64/libnss_files-2.17.so nginx 23009 www mem REG 253,1 155784 4428 /usr/lib64/libselinux.so.1 nginx 23009 www mem REG 253,1 105824 21252 /usr/lib64/libresolv-2.17.so nginx 23009 www mem REG 253,1 15688 4556 /usr/lib64/libkeyutils.so.1.5 nginx 23009 www mem REG 253,1 67104 16148 /usr/lib64/libkrb5support.so.0.1 ...... [root@web ~]#
注:lsof 真的很强大!
1.12 线程
在 Linux 中,一些进程被细分为更小的部分,我们称为线程(thread)。线程和进程很类似,它有一个标识符(即TID)。内核运行线程的方式和运行进程基本相同。但有一点不同,即进程之间不共享内存和 I/O 这样的系统资源,而同一个进程中的所有线程则共享该进程占用的系统资源和一些内存。
单线程进程和多线程进程
很多进程只有一个线程,叫单线程进程。有超过一个线程的叫多线程进程。所有进程最开始都是单线程,起始线程通常称为主线程。主线程随后可能会启动新的线程,这样进程就变为多线程。这个过程和进程使用 fork() 创建新进程类似。
多线程的主要优势在于,当进程要做的事情很多时,多个线程可以同时在多个处理器上运行,这样可以加快进程的运行速度。虽然你也可以同时在多个处理器上运行多个进程,但线程相对进程来说启动更快,并且线程间通过共享的进程内存来相互通信,比进程间通过网络和管道相互通信更加便捷高效。
一些应用程序使用线程来解决在管理多个I/O资源时遇到的问题。传统上来说,进程有时候会使用 fork() 创建新的子进程来处理新的输入输出流。线程提供相似的机制,但却省去了启动新进程的麻烦。
[root@racknerd-d3e10f ~]# ps m // 查看线程 1276 tty4 - 0:00 /sbin/mingetty /dev/tty4 - - Ss+ 0:00 - 1278 tty5 - 0:00 /sbin/mingetty /dev/tty5 - - Ss+ 0:00 - 1281 tty6 - 0:00 /sbin/mingetty /dev/tty6 - - Ss+ 0:00 - 11618 pts/0 - 0:00 -bash - - Ss 0:00 - 11646 pts/0 - 0:00 ps m - - R+ 0:00 - 12287 - /usr/bin/python /usr/bin/gm-notify - 12287 - - 12288 - - 12289 - - 12295 - [root@racknerd-d3e10f ~]# ps m -o pid,tid,comm // 自定义查看线程 1276 - mingetty - 1276 - 1278 - mingetty - 1278 - 1281 - mingetty - 1281 - 11618 - bash - 11618 - 11694 - ps - 11694 - 12287 - /usr/bin/python /usr/bin/gm-notify - 12287 - - 12288 - - 12289 - - 12295 -
注意:单线程进程中的TID和PID相同,即主线程。对于多线程进程12287,线程12287是主线程。
1.13 其它
pidof:查找正在运行进程的 pid
[root@web ~]# pidof nginx 23009 23008 [root@web ~]# pidof systemd 1 [root@web ~]# pidof sshd 11079 8676 8673 2816 2808
程序 program、进程 process、线程 thread 之间的关系和区别
简单来说,程序是一个可执行的文件,是一组指令的集合,比如一个 windows 的谷歌浏览器安装文件 chrom.exe 或 shell 脚本 auto-install.sh
进程就是一个正在运行的程序,执行者的权限属性、程序的程序代码所需数据等都会被加载内存中, 操作系统给予这个内存内的单元一个标识符 (PID)。
线程是一个进程执行一次 fork 的结果,线程继承了包含它的进程的很多属性(例如,进程的地址空间),多个线程在同一个进程内按照一种称为多线程的机制并发执行。
线程与进程之间的区别
进程不共享其地址空间,而在同一进程下执行的线程共享地址空间。
进程是相互独立执行的,进程之间的同步仅由内核负责,而另一方面,线程同步必须由线程在其下执行的进程负责。
与进程之间的上下文切换相比,线程之间的上下文切换速度更快。
两个进程之间的交互只能通过标准的进程间通信来实现,而在同一进程下执行的线程可以轻松进行通信,因为它们共享大多数资源,例如内存,文本段等。
小结
一个程序至少有一个进程,一个进程至少有一个线程。
进程是程序的一部分,线程是进程的一部分。
参考文献
《鸟哥的Linux私房菜》
《Linux系统管理技术手册》
《精通Linux》
极客时间《趣谈Linux操作系统》