Linux学习47 高薪技能-Linux进程原理、类型及内核状态

一、上集回顾

  1、ip命令,ss命令,配置文件:CentOS7

  2、ifcfg,ip、netstat、ss

  3、配置文件:

    /etc/sysconfig/network-scripts/

      ifcfg-IFNAME

      route-IFNAME

  4、CentOS 7:nmcli,nmtui

二、Linux进程及作业管理

   1、内核的功用:进程管理,文件系统,网络功能。CPU在处理进程时会给每个进程划分一个时间片,CPU处理完这个时间片后这个时间片马上就退出然后再处理下一个时间片,不管有没有处理完都会开始处理下一个时间片。不过也不是说每一个时间片都是一样大小,因此就会有进程优先级。因此我们需要给进程打上标签,优先级高的我们需要优先调度到CPU上运行。

    进程管理最大一部分功能就叫进程调度,CPU只有一颗,时间线也只有一个,他会将时间划分成很多小段,在不同的小段处理不同的进程。相当于按照优先级将不同的进程在不同的时间片调度到CPU上运行。

    那么我们在这个时间片完了后我们的进程产生了很多数据此时我们内核将调度其它进程在CPU中运行,此时我们当前进程产生的这些数据将怎么办呢?他其实是会保存在我们的CPU指令指针寄存器中。并且这些时间片处理后的进程会被衔接起来,此时称为保存现场,然后下一次执行时会将上一次的所有时间片处理后衔接起来的进程继续进行处理,这个就叫恢复现场。

  2、进程:Process,即运行中的程序,(也叫做运行中的程序的一个副本,比如ls,可以运行多个ls文件命令)即运行起来的程序才叫进程,也就是要把程序提前给内核让内核把它放到CPU上运行起来,而任何时候内核把程序放到CPU上运行起来就叫CPU运行了一个活动的随时需要执行代码的一个实体,这就叫一个进程。进程存在生命周期:从创建开始运行,运行结束退出。

  3、内核为我们当前每一个运行在系统上的进程都要创建一个用来去追踪这个进程的元数据结构,比如他要有自己的属主,属组,时间戳,大小。进程也一样,进程应该有自己的名字,进程号,进程所占据的内存空间,所运行的CPU时间等等

  4、Linux内核存储进程信息的固定格式:task struct

    多个任务的task struct组件的链表:task list

  5、任何一个时刻当一个用户试图运行特权指令时我们的CPU就会立即唤醒内核,内核就会发现有人造反,然后就会立即控制住他。因为特权指令只有内核空间才能执行

  6、那么进程是如何创建的呢?

    a、对Linux而言进程创建是有进程树的,即整个Linux的运行机制是这样的,在系统启动时,刚开机的时候,一定是先运行内核代码,先把内核放在我们CPU上运行,等内核控制和掌管了一切,由内核负责启动进程,我们说过内核就像上帝一样,他要派一个使者去管理人间的俗物,所以内核启动完以后,把整个空间创造完以后他开始创建第一个进程,这个进程我们就把它叫做init进程,也就是初始化进程。所以内核一旦创建了init进程就意味着内核空间已经准备好了用户空间也被创建出来了。而init进程接下来要做什么呢?后续的用户空间所有的管理工作就由它来负责了,init不能代替内核完成系统调用等一切特权指令的执行,但是创建进程等都是由它负责向内核提交。所以他是总的进行一切用户进程空间管理的这么一个进程,而他开始创建子进程,如果需要创建一个进程的时候,通常需要init来负责照着自己的样子创造一个子进程,或者是他的子进程所创建的子进程。因此除了init之外每一个进程都是由其父进程所创建。

    b、父子关系,即init是由上帝内核创造的,其它进程都是由init进程创造的

    c、那么父进程是如何创建子进程的呢?进程都由其父进程创建,子进程都是由父进程fork而来,我们内核中有个系统调用叫fork(),所以进程需要创建子进程时他会向内核发起请求调用,并且fork之后他还会克隆自身的数据给子进程,也就是系统调用中的clone(),大家知道一个婴儿诞生后是不可能离开父母存活的,因此在linux上子进程也是按照一样的工作法则在工作,什么意思呢?任何时候一个进程有很多组成部分,其中有一个重要组成部分就是在内存中他应该有一段空间来存放数据和指令的。我们说过任何程序都是由数据加指令组成,当这个程序要运行他必须把指令装入内存,CPU读到这些指令才会运行,当一个进程创建一个子进程的时候,这个时候他的子进程和父进程一定使用同一段内存空间,所以父进程指向的内存空间同样也是子进程指向的内存空间,就好像一个婴儿出生后和自己的父母是一个家庭,只是这个小孩出生后会有自己的名字,也就是自己的id号,但是一旦这个小孩长大了就得开始分家了,即我们的子进程需要去修改进程空间数据的时候是不允许修改原来的数据的,因为父进程还需要使用,因此一旦子进程需要修改数据的时候就会把父进程的进程空间的数据复制一份出来让子进程自己去修改,而这种机制就叫做写时复制机制。因此创建一个进程以后如果这个进程不需要写数据,或者我们使用另一种方式来工作,那么我们创建并销毁这个进程的代价非常小。我们甚至连内存空间都不需要给他额外分配,代价非常小。而一旦进程需要终止,我们就应该在必要的时候去终止一个进程,此时我们这个进程就需要我们的父进程将其销毁,也就是白发人送黑发人,当父进程运行中突然间需要完成一个功能复杂的任务时,他干脆再启动另外一个家伙让另外一个家伙来负责完成这个复杂任务,只需要把结果反馈给我就可以,所以父进程创建子进程的目的只是在某一时刻由子进程负责来帮他完成一些任务,当任务完成以后他的使命也就结束了。

  7、进程优先级

    a、为了快速实现进程调度,即判定哪个进程优先级高,我们Linux 内核2.6版本才用了一种非常精巧的判断方式,他把内核进程优先级划分成了固定个数,即从0-139,即140个优先级

    b、这些优先级中我们分成了两部分

      1-99:实时优先级

      100-139:静态优先级,也就是用户可调度的优先级

      他还有一个比较诡异的情形,实时优先级中数字越大优先级越高,数字优先级中数字越小优先级越高。而实时优先级我们一般很少手动去处理它。

    c、我们在内核管理中还给优先级分了一个Nice值,也就是优雅值

      Nice值:,负20到19,分别对应100到139,我们可以尝试着调试一个进程的Nice值来改变他的优先级,任何一个普通用户启动一个进程只能把Nice值调低不能调高,但是管理员是可以的。

    d、Linux内核为了能快速实现这众多优先级的调度,他把整个系统上待运行的进程分了140个队列,即相同优先级的排一队,所以当我们的内核需要去完成某一次调度时,我只需要来扫描这每一个队列的守护从高到低即可,这样的结果就是无论你等待运行的队列有多长,对我而言我所需要的挑选时间都是固定的,而这种机制就是用来评定一个程序的算法复杂度的标准。一般来讲每个优先级的队列都有两队(即一共有280个队列),一个叫运行队列,一个叫过期队列,真正能用来被扫描的叫运行队列,过期队列只是用来放那些在同一个优先级上已经被调度过运行过的进程,当运行队列中被挑完了怎么办呢?此时过期队列中就可以重新再挑选一遍了,于是此时队列就调换了,过期队列就变成了运行队列,运行队列就变成了过期队列。

  8、我们进程在内部到底是存储的什么呢?

    

  8、进程内存

    a、物理内存只有一个,CPU也只有一颗,假如我们当前系统上运行了n个进程,进程都需要内存来存数据,第一个进程用哪些内存,第二个进程用哪些内存,我们内存怎么分配给这些进程呢?还有我们进程不断的被创建和销毁也就意味着我们内存不断被分配和回收,每一个进程是否会在物理内存空间中使用一段连续的空间呢?每一个进程是否需要事先假设自己有多少内存可用呢?假如说每个进程事先假设自己有2g内存可用,而每一台主机只有512M怎么办?事实上,对于每一个运行的进程他们不可能直接访问硬件的,包括内存,所以说每一个进程所使用的内存不是直接通过划分物理内存来实现的,因此内核就是专门负责完成资源分配的,但是内核也没办法在这么多内存中很轻易满足每一个进程的需要,因为不同的进程对内存的实际需要不一样,比如cat 命令打开的是个小文件就10M的内存,当查看一个1G的大文件会有什么结果呢?此时我们需要在内核上分配大量空间来显示这个数据。

    b、因此我们内核对内存的分配是这样的,首先我们内核需要占一部分内存,内核启动时就已经分配好了,而剩余的这一段就可以给进程使用,但是这一部分内存我怎么分配给这么多进程呢?为了能够实现高效分配,他会把设备内存切割成固定大小的片段,而后分配片段给我们的进程,这些大小一般来讲是4k,这些片段在内存中一般来讲我们称之为Page Frame(页框),页框就是用来存页面数据的,页面数据指的就是内存空间中能够存在页框中的数据。

    c、因此每一个进程启动起来以后就需要内存,我们内核就将内存分配给进程,怎么分呢?就从这众多页框中找一些空闲的分配给它,比如第一个是空闲的,就给这个进程,第三个是空闲的,也给这个进程,就这样拼凑起来,所以整个物理内存中由大量的Page Frame组成,而这些大量的Page Frame都以不连续的方式或者有可能有一段连续的组成。将这些不连续的空间集中分配给这个进程让这些内存看起来像连续的一样。为什么呢?我告诉他这是连续的,于是我们加一个中间层,伪装成连续的,所以既然是伪装的,所以我们可以理解为虚拟的,所以每一个进程看到的都是一个虚拟的内存,而真正的物理内存是内核给你分配的不连续的物理内存空间并把它假装成的连续空间的可用的。更重要的是这每一个进程在上来的时候内核给你描述的是非常美好的,因为每一个进程在感知中都只有两个程序,一是内核,一是自己,所以每一个进程都以为内核告诉自己有多少内存可用自己就真以为物理内存有这么多可用,比如内存告诉他他有3g内存可用,但是其实真正的并没有3g内存可用。内存就可以通过这种方式告诉每一个进程他有3g可用。但是这个内存真正占据的才是内核给其分配的。

    d、因此物理内存的地址就叫物理地址,而每一个进程认为自己能够存取数据的地址位置就叫线性地址。线性地址与物理地址的对应关系有可能是离散对应的,这种映射关系需要内核给他伪装成一个美好的家园来实现,而这就叫虚拟内存实现机制,对Linux来讲虚拟内存就是其整个代码内核最复杂的一个。每一个进程都以为自己在32位系统上,在32位系统上每一个进程都以为自己有3g空间可用,他怎么有效使用这3g呢?一样的,这段空间中我们要放数据指令等,于是他将这个空间开始分割一下,他就一部分放指令,一部分放数据,这些数据还包括变量,有一段空间来放堆内存,另一端我们开始放栈内存,栈我们一般采用先进后出方式。而堆内存需要我们存放大量数据,比如我打开一个文件后需要存放大量数据进来,这些文件只能放在内存中才能处理,他就是放在堆内存中的,因此开始我们的堆内存是很小的,然后由于我们的文件越来越多,堆内存就不断增长,就向栈方向增长,直到二者合拢了空间就占完了。当然一个进程通常也不会占满。

    因此我们内存就分代码段和数据段,代码段只会被读取,而数据段是会不断被加工操作的。

    

    一旦我们物理内存不够用了我们就会开始使用交换分区,他首先会扫描哪一个页框中的数据最近最少使用,会使用LRU算法,就是最近最少使用算法,谁没有被使用就会被先暂时的挪到交换分区上来,而内存中的空间就被腾出来了,万一我们挪到交换分区的页框要使用了我们就将我们的页框挪到我们内存中去然后把我们其它空闲的内存页框挪到交换分区中去。

    但是我们挪到内存中后还是原来的空间吗?肯定不是的,这样的话就会有问题的,因为我们虚拟内存中的线性地址空间是需要映射到物理地址空间的,这个映射就在哪儿保存着的呢?我们说过内核为每一个进程都保存了一个task_struct,他里面记录了这个真正的进程线性空间中的每一个地址和对应的物理地址空间,所以某一个进程想访问其线性内存地址空间,他也只能访问自己的线性地址空间,所以他每一次访问线性地址空间时内核都会把其转换为物理地址空间,然后在物理地址空间中取出数据返回至线性空间然后进程才能操作数据。这个过程在CPU中有一个硬件帮我们完成,他就是我们CPU中的MMU(Memory Management Unit,内存管理单元),这个内存管理单元就负责当一个进程被加载至CPU上运行的时候我们就专门把他们的映射关系放在MMU这一个专门的芯片上负责完成实时转换,当一个进程指令访问某一个内存空间的数据时,我们知道这个地址是一个线性地址相当于是假的,于是这个MMU芯片立即将其转换为物理地址,此时才能帮我们找到真正的数据。

    如果我们将我们的swap分区中的内存数据挪到内存空间后他就会重新进行映射,会将线性地址的关系映射到新挪的内存地址空间中来。

    一个进程有些数据是必须在内存中的,必须在内存中的数据就叫常驻内存集,可以交换出去的我们可以把其理解为可被交换的数据,我们称之为虚拟内存集。

  9、IPC:进程与进程之间我们有时候是需要通信的,这种我们称之为IPC:Inter Process Communication,也就是进程间通信。既然进程间彼此不能意识到对方的存在那么我们怎么进行通信呢?所以就必须要基于非常简单的方式来实现

    a、如果需要通信的两个进程在同一主机上,这时候就要简单的多,比如

      (1)、发信号:signal

      (2)、或者使用共享内存:shm(shared memory),找一个内存空间,我往里面放一个数据,另一个进程通过内存空间去读数据

      (3)、semerphor机制,即给对方打一指令,抛一媚眼之类的。

    b、不同主机上

      (1)、rpc:remote procecure call。远程过程调用,我们说过库调用就是调用本机上的一个库,而远程过程调用表示你调用的不是本机的而是另外一个机器上的函数

      (2)、socket:套接字通信。每一个主机要与另一个主机通信可以在本机上创建一个socket文件(Linux一切皆文件),这个socket文件一端保存的有自己的地址和端口,同时还保存的有对端的地址和端口,这个socket其实也就占用了TCP或者UDP连接,所以他们通信时他们会将相应的文件或数据直接发送给socket,然后内核通过socket就直接发送给对方了,对方也通过建立连接的socket打开对方传输的数据进行读取即可。

三、Linux内核的工作模式

  1、为抢占式多任务,即任何一个进程需要工作时发现别的进程在运行,此时可以把别人的运行时间给抢过来,当然别人刚开始运行就抢也不合适,比如每个进程给其分5毫秒,每个进程运行了一毫秒的同时剩下的四秒其它进程可以用,等这一毫秒运行完后再运行接下来的一毫秒,剩下的三毫秒其它进程又可以先用。所以这种调度方式更高效。

  2、进程类型

    a、守护进程:daemon,与终端无关的进程,在系统引导过程中启动的进程。

    b、前台进程(用户进程):即用户通过终端启动的进程。也叫做前台进程,跟终端相关,通过终端启动进程。

      注意:我们由前台启动将其运行为守护模式的进程也叫前台进程,比如通过service运行的守护进程。

  3、进程状态

    a、运行态:running

    b、就绪态(ready):也可以称之为睡眠态

    c、睡眠态:

      可中断睡眠(interruptable):随时都可以将其调度到cpu上运行

      不可中断睡眠(uninterruptable):调度到cpu上都没法运行。当任何一个进程运行时,我们说过进程需要指令加数据,数据从哪儿来呢?我们也可以从文件读取,进程运行时候我要传递这个文件,于是进程启动了,需要加载文件了,如果文件很大的话就需要很长时间才能加载完成,所以进程本来运行起来了需要等待文件加载完成,加载的过程中不可能让其一直占着cpu,因此我就只能让其把cpu让出来让其它进程去运行,这就让其等待文件从磁盘加载至内存即可,而这个过程就称作一次IO的过程,一次IO过程就意味着某一进程运行过程中需要加载的数据在内存中没有,于是就不得不请求内核把数据从磁盘装入到内存中来让进程来访问,但是真正的IO执行过程就分成两段,因为你要去加载数据进程自己是没有权限的要向内核申请,内核通常只是自己加载数据到自己的内存,即内核的内存中,因此这个过程变成了这样:这个要被加载的文件首先要从磁盘装入到内核内存中来,因为内核也是一个程序,每个程序都要有自己的内存空间,所以第一步要装载至内核内存,第二步要把这个数据复制一份复制到进程内存中来,所以一次IO是在两段进行的。第一个步骤是将数据从磁盘加载至内核内存,而后把数据从内核内存复制到进程的内存中来。而后进程才能操作数据。所以进程从发起调用开始一共要等两段时间,第一段等待数据从磁盘到内核内存,第二段再等待数据从内核内存到进程内存。而只有第二段才叫真正的数据调用的IO的过程,第一段叫数据装入的过程。因此当一个进程需要去用数据的时候发现数据需要IO才能完成,然后内核就会把他调度然后转入睡眠状态,因为如果他的数据还没有加载完成你把它叫醒也没用,所以这就叫不可中断的睡眠。那么什么叫可中断睡眠呢?即你把它调度到CPU上的处理时间片耗尽了,他自己不睡也不行这样我们下次再调度时他就不需要再有其它额外的数据准备,所以就叫可中断睡眠。

    d、停止态(stopped):意外着一个进程处于停止模式,你可以理解为一个进程暂停在内存中了,不会被运行也不会被调度。即暂停于内存中,他的task_struct都存在,但不会被调度执行,除非手动唤醒他。

    e、僵死态(zombie):一个进程是被其父进程创建的,而这个子进程任务已经完成了,他就需要把自己所需要的资源,打开的文件关闭,从父进程那里拿到的资源给还原,就等待父进程收尸了,所以这个过程就叫僵死模式,直到父进程把它干掉。但是有时候可能会有这种场景,当父进程创建子进程了,子进程还在运行父进程却突然间挂了,我们说过任何子进程都是由父进程帮其收尸的,现在父进程挂了那么子进程怎么办呢?就变成了孤儿进程了,将来终止以后谁给其收尸呢?所以一个有操守道德的父进程应该在自己挂之前立个遗嘱给其子进程找一个新父亲才行,如果真没地方去也可以托管给init,init是可以收留所有没有父进程的子进程的。如果一个菜鸡程序员在写程序的时候没有处理这个问题的话你会发现很多父进程结束了子进程没有处理到最后你会发现内存中充满了僵尸而导致无内存空间可用都是有可能的。

  4、进程的分类

    a、CPU-Bound:CPU密集型的进程

    b、IO-Bound:IO密集型的进程

   《Linux内核设计与实现》 -->  《深入理解Linux内核》

posted @ 2020-03-09 13:49  Presley  阅读(344)  评论(0编辑  收藏  举报