当我们说进程的时候,就必然要说道线程,在Linux里面经典定义是
进程是一个支援分配的单位,所以,你搞清楚一个进程,就是搞清楚它的资源,当我们要说进程的时候,首先要说Linux里面是用一个什么样的数据结构去描述它
这是个process control block(进程控制块)这是OS里面通用的概念,在linux里面,进程控制块就是一个task_struct结构体
这个时候,我们应该怎么记住这个task_struct长什么样子呢,前面不要去死记硬背,我们学一个东西首先要定位到这个东西是做什么用的,比如说我们了解task_struct就是用来描述进程的,然后你就想,进程是个资源分配的单位,既然进程是资源分配的单位,那我task_struct就是描述这个资源。所以自然而然就得出task_stuct应该长成内核下定义的那个定义
struct task_struct{
pid;
......
*mm;
*fs;
*file;
*signal;
}
比如它里面有个mm成员,这个指针指向它内存的描述,这个叫做mm_struct,这个是描述内存的资源
比如说这个fs指针,这个描述它文件系统资源,比如说你一个进程在运行的时候,你的root在哪里,你的当前路径在哪里,这个肯定是一个进程关心的
所以这个这个就是进程的文件系统资源
还有文件资源,比如说你一个进程在运行的时候打开了哪些文件,这些文件对应的fd就是file described,所以你进程里有个file指针,指向它文件资源的描述,就是files_struct
这里面有个fd_array就是一些打开了fd的数组,比如你一个进程打开了你文件一是什么,文件二是什么等等等等,这显然属于进程的资源
除此之外还有信号,信号处理函数等等。你要把自己想成Linux设计者你会怎么设计。
pid就是每个人都有的身份证号,你作为一个进程,你肯定会有个进程身份证号,在Linux下有个/proc/sys/kernel/pid_max的文件,你可以读出最多有多少个pid
所以说明一个问题pid的数量是有限的,你不可能在Linux里面创建无限多的进程,你创建了足够多的线程之后,再创建就创建不出来了。所以在Linux有个很好玩的东西叫fork
炸弹,比如说一个老外闲的无聊写了这么一段程序这是个shell脚本,程序如下
这个程序怎么看懂这个程序呢,这里面有个函数就叫:,:就是函数名,这个{}里面是这个函数体,这个函数体干什么呢,调自己,| 里面是管道的意思,所以竖线前面调用自己,所以这里是导致创建一个管道,管道里面创建一个新的进程,这个新的进程也去递归的调用自己,这个&是表示后台执行,最后这个:表示函数结束,最后又来了个:又去调用这个函数,所以这个:不停的被调用,所以这个程序就是不停创建进程。所以这样就把Linux里面所有的pid都全部用完
你用kill已经不能阻止了,因为你pid已经用玩了,你起一条kill的命令其实也是开一个新的进程,你这个kill已经不可能再起起来了。所以你这个看起来就像死了一样,事实证明也一样
前面我们给大家描述到,task_struct是描述进程的数据结构,Linux里面有这么多task_struct,怎么管理的,肯定要搞个东西链接起来。Linux先到了一个最简单的方法就是搞一个链表,把所有的task_struct链在一起,但是显然是不太好的,因为Linux里面所有的进程形成的是一个树形的关系,进程是有父子兄弟关系的,一条pstree命令可以查看
我们可以看出进程最顶端的经常是Init,init进程里面又有 子进程,子进程里面又有子进程,你用链表怎么能描述这个树形关系呢,所以在Linux里面还要还需要另外一个数据结构就是树,所以Linux马上就有了另外一个数据结构就是树,把所以的task_struct挂在一个树上面,这样我通过一个树,就可以知道每一个进程的父进程,子进程等等
在Linux下面还有另外一种需求,就是通过pid来检索那个对象,比如说我kill -2 xxxxx pid就是数字,对于我们内核来说,跟着数字去 找到这个数据结构,我们学了很多数据结构就是一种数据结构检索是非常快的,那就是哈希表。进程只有一种,但是linux里面用三个不同的数据结构从多个不同的侧面来描述它,这样你在各种场景下都比较快,比如你想变量进程,你可以用链表,你想知道这个进程的父子兄弟关系,你可能就用树,你如果想用pid去快速的检索一个进程,你就用哈希。
下面来看每一个进程的生命周期是怎么回事
在Linux里面一个进程是fork出来,fork出来之后就属于就绪态(TASK_RUNNING),一旦占用CPU就在运行。
这里面有6个框,就绪,执行,僵死,暂停,深度睡眠,浅度睡眠,僵死。为什么有6个框
我一旦被fork出来,我就处于就绪态,我一旦拿到CPU,我当然就处于运行,在Linux里面就绪和运行的标准是一样的,但是不能老是我一个人运行啊,比如我在Linux里面可以跑的,比如有微博,QQ,微信,我QQ不能一直占用CPU,我要把CPU让给别人啊,所以运行态的进程也可能会切回就绪,所以有两种可能性,第一种是给你的时间片用完了,分时调度,不能在总是你跑,第二个是给你时间片没用完,但是被抢占了,我有更紧急的事件要处理。当我运行态是时候经常要等资源,比如我要等串口,我要等网络发包,我等资源肯定不能占着CPU在那里死等,我必须要把自己切成睡眠态,一旦我等到了资源,我就把自己又切换到就绪态。Linux里面一个进程刚死的时候,不是人间蒸发的,它会变成一个僵尸,就是这个task_struct还没有消失,但是这个task_struct所依附的 资源已经消失了。这个task_struct以及task_struct里面的一些成员还没有消失,他留下来的目的就是准备让它的父经常去通过Linux里面一个wait4的api去等待它死,它才会消失,僵尸是个非常短的临界状态,就是一个进程死了,父进程还没有还没来得及去wait它的情况,一旦父进程wait它,他就直接人家蒸发掉了。看一下Linux源码
里面有个wait_task_zombie就是等待进程僵尸,一旦一个进程变成僵尸的时候,资源已经被释放了,所以不可能有内存泄漏等等
这里面有个task_struct,这个task_struct里面还有个退出妈exit_code,父进程在wait它的时候就可以得到它的退出码,就可以查到子进程的死因
在Linux里面一旦一个进程死掉的时候,父进程是可以完美的知道它的死因的,看个例子
运行如下
僵尸态就是子进程死了,父进程也不去清理它,
我不让这个父进程去执行那个等待,然后杀死子进程看一看
然后你会发现,状态变成了Z+,名字上也盖了个棺材,打了个【】,这是个僵尸
看到僵尸就非常的不爽,信号里面有个信号9机器的非常猛,什么进程都可以杀,但是你却杀不了它,你唯一能杀的就是把父进程杀掉,他也就死了,只死掉不清理就是个僵尸
工程里面如何观察一个工程里面有没有内存泄漏呢,使用联系多点采用法,比如说6点取一次,7点取一次,等等等等,然后我发现它随着时间流逝,所占用的内存越来越多,证明它必有内存泄漏。但是不能用两个点来说明问题,比如说我观察它昨天晚上8点,今天晚上8点,今天晚上8点比昨天晚上8点多了10M内存,不能以为它泄漏了10M,比如我写了一个MP3播放器,我昨天在放16K单声道的,今天在放一个48K立体声的,今天耗费的内存肯定要大一些。
还一个停止态,停止态是什么态呢,停止态是进程的运行的时候人为的让他停止,比如发STOP信号,一般两种时候我会发,一种是ctrl+z这样的快捷键,让这种进程听而不死,第二中是gdb_attach_debug的时候,ctrl+z叫做job control,第二种是gdb_attach_debug调试,一般称为jc,停止态的时候它也不占用CPU,当你发continue的信号可以让他接着运行。
实验
运行,然后我们用ctrl+z来stop它
然后你在终端输入一个fg又继续运行了
bg也可以继续,只是fg是前台的,bg是后台的
cpulimit可以控制一个进程的CPU消耗
比如cpulimit -l 10 -p 10111
这个控制pid位10111程序的cpu使用率不超过百分之10
Linux把睡眠分为深度睡眠和浅度睡眠,都属于睡眠,一个叫深度睡眠一个叫浅度睡眠,深度睡眠是指必须要等到我的资源到来我才会醒,浅睡眠就是资源来了我也会醒,但是信号来了我也会醒。深度睡眠是不响应任何信号的,你用kill -9都杀不掉它。