Linux体系架构
推荐视频:
c/c++ linux服务器开发学习地址:C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂
基础概念
进程是个什么玩意?
进程是一个资源分配的单位。每个进程在操作系统中都有一个“进程控制块PCB*”来描述一个进程,在linux中使用 task_struct 这个结构体描述一个进程/线程。
线程和进程的异同在后续文章再讲,明确一个概念,linux内核中线程和进程都是同一个结构。
如何描述一个进程呢?
1、有一个pid,用作进程的唯一标识
2、有自己的内存空间 *mm
3、文件系统资源(进程的可执行文件路径等)*fs
4、进程打开的文件资源数组 *files
5、信号相关资源 *signal
所以我们自然的可以想到 task_struct 结构应该是这样的结构:
task_struct结构
linux系统中开启了N个进程,linux该怎么组织一堆 task_struct 结构呢?
先想下,我们有哪些场景会需要找进程的task_struct?
1、kill -9 pid 杀掉指定pid。
2、pstree 命令查询进程的树状父子关系
3、linux内核需要组织管理所有进程
所以,linux底层用3种结构维护所有 task_struct 结构:
1、hash结构 应对直接查询pid的场景
2、tree 结构 描述进程间父子关系
3、链表组织管理 task_struct
这是典型的空间换时间的思想,在不同的场景下使用不同的结构。
关于pid:在系统中的pid的数量是有限的
[root@dev ~]$ cat /proc/sys/kernel/pid_max32768
进程的生命周期
linux的一个进程是被fork() 出来的,这里主要讲这六种状态
想一想,理论上只需要三个状态(就绪、运行、睡眠)就可以描述进程状态了吧?为什么是6个状态呢?
按照自己的思路想一下:
一个进程被fork() 出来之后处于“就绪”状态,当进程拿到cpu之后,就进入“执行”态。因为cpu不可能被一个进程一直占用,所以执行态的进程也可能会切回就绪态。
那么什么情况下可能切回就绪态呢?
我们知道linux内核是抢占式的,那么有可能有优先级更高的任务要执行,cpu被抢占。
还有一种可能,分配给进程的cpu时间片用完了(分时调用)。
当进程在运行的时候经常会等资源,比如等网络发包、串口等,那么等资源的时候不应该再占用cpu,这时候进程应该主动让出cpu,进入睡眠态。
【文章福利】需要C/C++ Linux服务器架构师学习资料加群812855908(资料包括C/C++,Linux,golang技术,内核,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等)
僵尸态
那么僵死态是什么情况呢?
当一个子进程死亡,父进程还没有waitpid()的时候,子进程的状态就是僵死态。
僵死是一个非常短的临界状态,一旦父进程waitpid()后,子进程就“人间蒸发”消失了。
进程死亡进入僵死态后,进程名下的资源已经释放了,不可能出现内存泄露。
Linux内核中,父进程可以通过内核提供的wait4得到子进程的退出码(死亡原因),具体可查看linux的wait_task_zombie()*函数:
来做个实验:
? ~ ./a.outppid = 58111child process pid:58118// 在另一个终端中执行 kill -9 58118child exited with killed by signal 9? ~ ./a.outppid = 58181child process pid:58182// 在另一个终端中执行 kill -2 58182child exited with killed by signal 2
如果父进程不对子进程做waitpid() 清理操作,那么会怎么样?
我们将上述代码的第34行之前加入死循环,父进程fork子进程后,在死循环不去清理子进程。会发生什么情况?
? ~ ./a.outppid = 58436child process pid:58437// 将子进程kill之后,父进程不回收子进程,子进程状态就变成Z+? ~ kill -9 58437? ~ ps aux | grep 58437Root 58445 0.0 0.0 4285692 700 s004 S+ 2:33下午 0:00.00 grep --color=auto 58437Root 58437 0.0 0.0 0 0 s003 Z+ 2:33下午 0:00.00 (a.out)
这时候即便对僵尸进程 kill -9 也杀不掉僵尸进程
一个进程已经是僵尸了,即便再杀它也杀不死它(死透了),当一个进程变成僵尸进程,它所占有的资源全部被linux释放掉了,除了对应的task_struct,父进程靠子进程的task_struct获取子进程的死亡原因,所以不必担心内存泄露。
那么怎么才能将僵尸进程彻底杀掉呢?
将僵尸进程的父进程杀掉,僵尸进程就会消失。
如果僵尸进程太多,会占用系统的pid资源,所以僵尸太多也不好。
一般来说,只有父进程没有回收(waitpid)子进程的情况,才会导致僵尸进程存在。
关于内存泄露:
内存泄露从来不是进程死了导致内存泄露,永远是一个进程运行随着时间的流失,所占内存越来越多,这种情况必有内存泄露。我们观察一个进程是否存在内存泄露,可以用连续多点采样法,对进程在不同的时间点采集进程的内存消耗情况,只有拉长采样时间才可以确定进程存在内存泄露。如果进程消耗内存在很长一段时间内是上下震荡收敛的,这种是正常情况,并不存在内存泄露。
停止态
什么叫做停止态?
进程在运行的时候不去睡眠,人为干扰进程,让进程进入停止态。
怎么可以让进程进入停止态呢?
1、给进程发STOP信号 kill -20 (STOP信号)
2、ctrl + z
3、gdb 对进程调试
如何从停止态恢复到运行态?
1、发送STOP信号后,执行 kill -18 (CONT信号)进入运行态
2、ctrl +z 进入停止态后,执行 fb或bg进入运行态
3、gdb 使用 continue 进入运行态
注意:STOP/CONT 信号除了暂停/回复之外,还会将进程挂起后变后台进程的作用,就像bg命令
linux下有一个cpulimit 命令可以控制进程的cpu的占用率
cpulimit -l -p
这个命令的原理就是不断的将进程停止->运行->停止->运行….
深睡眠和浅睡眠
深睡眠:必须等到资源才会醒
浅睡眠:资源来了可以醒,信号来了也会醒
信号就是针对进程的异步打断机制
linux下信号有:
$ kill -l 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR111) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+338) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+843) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+1348) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-1253) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-758) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-263) SIGRTMAX-1 64) SIGRTMAX
在一个进程进入深睡眠的时候,是不会对外界信号有任何反应,即便kill -9 也杀不掉进程。这里的深度睡眠和僵尸进程是有区别的!这里深度睡眠不是指TASK_KILLABLE(可杀的深度睡眠)
为什么要有深度睡眠?都用浅睡眠不行么?
Linux下特定情况必须使用深度睡眠的,举个栗子:
比如进程有个代码段在硬盘上,当进程想要执行这个代码段上的函数时,发生了page fault*,然后需要将缺失的代码段读到内存中,这时候linux会将进程置为深度睡眠。如果被置成浅度睡眠,那么进程就可以接收信号,如果这时候接收到信号,信号处理函数还可能执行刚刚缺失的代码段,那么就会又发生page fault… 禁止套娃!
如果发生page fault之后读取硬盘时,硬盘挂了,那么这时候进程就会一直处在深度睡眠态。什么时候能活过来呢?
1、硬盘恢复
2、电脑重启
总结:
深度睡眠和浅度睡眠
1、睡眠是一种阻塞状态
2、睡眠是“主动”设置为睡眠。
3、进程在等待资源的时候,将自己设置为睡眠态。比如等网络回包、磁盘操作
4、睡眠是内核态操作进入的
5、睡眠需要进程在用户态显示的调用Linux内核的api
6、无法强制进程进入深度睡眠,这个由linux内核控制
停止态是外部人为将进程设置为暂停,是被动的,属于作业控制。
fork函数执行后,父子进程执行的先后顺序是不确定的,除非调整
kernel.sched_child_runs_first 内核配置,人为控制父子进程先后顺序。
进程从睡眠态醒来变成就绪态,为什么不直接进入执行态?
进程醒来之后,因为进程执行优先级问题,不一定能被系统调度到。可能有更优先的程序需要先执行。
子进程变成僵尸后,父进程如果不清理,子进程才会一直处于僵尸状态。僵尸态是个过渡态,一般存在时间很短。
父进程变成僵尸,父进程也有父进程的,所有进程都是以linux内核启动的第一个进程init为root结点,在树上不断fork出来的进程。
所以,一旦init进程被杀掉,内核就会触发panic。