保护模式与实模式

  • 实模式想怎么干就怎么干
  • 保护模式有限制,用户态想写内核空间的内存得经过内核同意才行
  • Linux内核很健壮,很难将Linux内核搞死

内核执行的操作

  • 大概只有两百多个内核函数(200多个系统调用)
  • java读一些东西(本地或者网络),其实都是在调用系统内核的一些操作:sendfile、read、write、pthread、fork、……
  • 内核执行的操作—->系统调用
  • JVM—->站在os老大的角度,就是个普通程序;对于字节码来说,他更底层!

进程线程纤程

进程和线程有什么区别?

答案:进程就是一个程序运行起来的状态,线程是一个进程中的不同执行路径。(不专业)
专业:进程是os用来分配资源的基本单位,线程是os用来执行调度的基本单位;分配资源最重要的是:独立的内存空间;线程调度执行;进程和线程最大的区别:线程共享进程的内存空间,没有自己独立的内存空间,进程共享资源,所以出现了资源的争用问题

操作系统层面的线程实现是不一样的

  • Linux中就是一个普通进程,只不过和其他进程共享资源—->fork从现有的进程中克隆出一个新的进程(父进程与子进程)
  • 一个进程起一个线程其实在os角度也是起一个进程,只不过这两个进程共享内存空间,共享系统的一些资源(全局数据……)
  • 其他系统都有各自的LWP的实现Light Weight Process轻量级进程
  • Linux中是一个普通进程,而其他os中是一个轻量级进程
  • 高层面理解:一个进程中不同的执行路线
  • 线程的实现:其中一个线程是主进程,其他进程和主进程共享内存空间
  • 底层实现要用到c语言的系统调用,系统调用起一个线程的时候完全是可以把原来有的内粗空间共享给新进程的


纤程、协程fiber、quasar

  • go语言中叫coroutine或者goroutine
  • 线程中的线程—->在用户空间的线程
  • java本身不支持纤程,可以用第三方库Quasar
  • 使用纤程Fiber的时候,在操作系统层面其实只有一个线程,在这一个线程基础之上存在着好多纤程(10000个),这些纤程是并发(并行❓)执行的,在用户空间进行切换—->这比操作系统层级的线程切换代价低得多
  • 目前的模型是操作系统一个线程,jvm中一个线程,这之上有好多个纤程
  • 更好的方式是将这10000个任务分成10份,起10个线程去执行,每个线程执行1000个任务—->**既充分利用了操作系统在内核级别对于线程的调度,又利用了jvm在用户空间对于纤程的调度**

image.png

  • 单独的计算任务之间没有依赖关系,是不需要用fork、join的


纤程的应用场景

纤程 vs. 线程池:

  1. 很短的计算任务,不需要和内核打交道时,用纤程要比用线程合适—->计算任务,只要做计算就行了,很短时间就可以完成,在用户空间进行切换就可以了,并且不需要和内核打交道,不需要去读取文件,不需要在那等着
  2. 一堆任务在那等着读文件是不适合用线程(纤程❓❓❓)来做的
  3. 一个任务除了计算什么都不干,有一个任务、一个请求来了,这个纤程运算一下马上返回,这时用纤程比线程效率高
  4. 并发量高的时候,用纤程比用线程好,纤程能轻松起到几万,而线程最多只能到1万左右
  5. 吞吐量上升❓
  6. 缺点:❓
  7. 阿卡(kafka❓)内部支持纤程,所以他的并发比java语言高一些
  8. kafka是用go语言写的???❓
  9. go语言比java语言先进在纤程这一块,天生支持高并发,自带了纤程;别的方面差不了太多
  10. 纤程:用户空间级别的线程
  11. 纤程没有在操作系统层级切换,是在用户空间层级的一个切换
  12. 纤程与线程的安全❓❓❓

进程

Linux中的数据结构PCB(Process Control Block)

  • 进程和线程都有这样的PCB,只不过其中有标志位表示是否和别的PCB是共享的

    内核线程

  • 内核启动之后需要做的一些后台的操作由内核线程来完成的

  • 这些线程只运行在内核空间,即内核内部使用的线程
  • 不要将其与启动一个普通线程混淆,用来执行背后的操作:定时、定期清理、标记垃圾等,这些是内核线程的任务
  • 用户空间不可能访问到内核线程,只能通过内核老大去调用这些内核线程
  • 内核线程:内核内部使用的线程,内核独用的线程

    进程的创建和启动

  • 系统函数fork和exec

  • fork里面其实还调了clone克隆函数,即从当前线程克隆出一个新的线程来
  • 原来的进程为父进程,新的进程为子进程

僵尸进程和孤儿进程

僵尸进程

  • 僵尸进程是那些父进程不释放子进程的PCB,而子进程已经结束了的子线程
  • 僵尸进程出了PCB之外其他很少占用系统资源
  • 僵尸进程是可以释放的,可以用wait的函数手工释放这个僵尸线程
  • 有的时候可能是父进程是一个Demon进程,是在服务器端不断运行的,可能会不小心没有手工释放子进程的数据结构,这时就会产生僵尸进程
  • 少量的僵尸进程没有什么影响,可是大量的僵尸进程也会占用大量的空间,造成资源浪费
  • 🌟写c程序的时候一定要手动释放掉子进程的PCB!!!
  • 僵尸进程只占PCB

    孤儿进程

  • 子进程还没有结束,父进程就已经退出了

  • 一个进程是不能去释放自己的PCB,就相当于子进程没有爹了,称为孤儿
  • 系统一般是将孤儿进程都交给一个特殊的进程去处理,让他当所有孤儿的爹,一般是init进程,在图形界面的情形下不一定是这个,就是交给某个特殊的进程去处理了
  • 孤儿会自己找一个爹,这个爹是固定好的,一般是init进程,进程号是1
  • 图形界面情况下,可能比较特殊,可能是1457来着,和其他的不一样;在ui界面上的公共爹,如果ui界面都死了,那么他的老大就变成1号进程了—->表示ui界面仍然是1号进程的子进程(也会有孤儿进程的情况出现)
    • 在图形化界面上基本上所有的进程的父进程都是1457号进程,而1457的父进程是1号进程—->公共爷爷
    • 因为是在图形界面之下,1457是图形界面的公共爹
  • 产生了孤儿进程不会有什么特殊的影响,影响不大,顶多就是换了个爹,让他的爹去给他释放他的资源
  • 孤儿进程和孤儿线程没有什么区别—->线程是特殊的进程
  • 用ps不加任何参数可以查看用户进程—->defunct标志代表这个进程是僵尸进程(functional有功用的、defunctional无用的残废的)
  • 可以手动kill掉????—->是kill不掉的,因为僵尸进程已经死了,是不能杀掉一个死人的—->要将他的父进程杀掉才行
  • 当子进程变成僵尸进程的时候,kill掉父进程的时候,父进程和已经是僵尸进程的子进程就会全部被干掉了

    进程调度

  • 操作系统有一堆任务要执行,那么先执行哪个,每个执行多少时间

    • 内核进程调度器决定:
      • 该哪一个进程运行?
      • 何时开始?
      • 运行多长时间?
      • 什么时候这个执行完了让给下一个
  • 进程、线程、任务调度
  • Linux的调度方案特别灵活,不是指定了一种方案,而是不同情况下选用不同的调度策略;而且可以给某个进程指定某个特定的调度方案
  • 在Linux中写操作系统内核的话,可以给内核打补丁,给自己的内核写自己实现的调度方案
  • 某一个进程什么时候运行是由他所对应的调度方案决定的
  • Linux中每一个进程都有自己的调度方案

    我们学的都是工程技术不是科研 工程技术没有别人掌握不了的东西,而科研可以有别人没有发现的算法 工程技术更多的是分方向的,无非是精力花在了某一特定的领域 掌握了一些系统底层或者别人没掌握的东西是不能鄙视别人的,不能去怼面试官,面试官只是将时间花在了工程技术学习上!!! 做工程的最重要的是做东西 心态放平 🌟最值钱的是做了很多项目,能够为公司产生效益的东西 其他的是面试准备😒

  • 单任务(独占)—->多任务(分时)

  • 原则:最大限度地压榨CPU资源(大厂喜欢要这样的人)

    抢占式和非抢占式(多任务)

  • 非抢占式(cooperative multitasking):除非进程主动让出cpu (yielding),否则将一直运行

  • 抢占式(preemptive multitasking):由进程调度器强制开始或暂停(抢占)某一进程的执行(现在大多数是抢占式—->是由进程调度器来进行安排的

Linux中的进程调度(精巧、帅、妙啊)

  • linux2.5经典Unix O(1)调度策略,偏向服务器,但对交互不友好、
    • 找医生看病,医生按照时间片去分配时间,每隔多少毫秒更换
    • 时间片到了就换下一个,很公平的一个分片东西
    • 为什么偏向服务器而对交互ui不友好?
      • 因为他没有即时响应,对ui操作极度不友好,必须等轮到要运行的任务执行才能给出响应;服务器没有ui
  • linux2.6.23采用CFS完全公平调度算法Completely Fair Scheduler
    • 经过十几年、二十几年……
    • 现在这是一个经典的策略
    • 完全公平调度算法,不是上面那种不偏不倚都是10ms的那种公平策略
    • 这里的绝对公平指不再采用绝对时间片
    • 那种绝对的公平反而不是完全的公平,因为有的人可能不需要这10ms,有的人可能急需系统资源—->好比医院里的急诊病人要优先抢救
      • 绝对公平和完全公平的差异!!!
    • 采用的是按优先级分配时间片的比例
      • 一个字处理软件和一个急需处理的软件
      • 分配没人50%的时间片
      • 执行到你时,发现你不动就不会把50%的时间片给你
      • 执行了1s,0.5s不给你,直接给你划掉了,因为你没动
      • 把所有的时间片都给另一个进程,所以另一个进程执行了1s
      • 假如字处理软件突然来了一个字符要处理之后,这时Linux会记录下来过去的时间中另一个进程执行了1s而另一个进程一点都没有执行
      • 本来是一人50%,结果发现没到50%,所以接下来就会优先让这个进程执行
      • 本来要分配给你50%,实际上只给了10%,所以就亏欠了这个进程了,所以下次你要用的时候因为你之前没有用到50%,所以就让你优先执行(补偿给你
      • 平均分配看上去是公平的,其实是不公平的
      • 什么时候到50%了,他们由绝对公平执行了

image.png

  • 病人来了按优先级分配的,优先级高的先给;有时候你用不着也不会强制分配给你,我会拿走给别人去用,什么时候要用了,欠你的马上还给你

进程调度的基本概念

  • 进程类型
    • IO密集型大部分时间用于等待IO—->优先级低
    • CPU密集型大部分时间用于闷头计算—->优先级高,要去计算
  • 进程优先级
    • 实时进程(0 - 99) > 普通进程(-20 - 19),实时进程在运行的时候,普通进程永远没有机会执行
      • 实时进程中又分100个级别,级别越高,优先级越高
      • 实时进程和普通进程完全是两个不同优先级的:实时进程是急诊,普通进程是普通门诊
      • 实时永远大于普通,实时的高级别永远大于低级别
      • 目前对于Linux来说,实时进程也有不同的执行策略了,但原则上仍然是优先级高的优先执行
      • 普通进程nice值(-20 - 19)
      • 大多数进程都是普通进程,普通进程和普通进程是能够分到一些时间片的,而普通进程和实时进程是分不到时间片的
  • 时间分配
    • linux采用按优先级的CPU时间比
    • 其他系统多采用按优先级的时间片
  • eg. 两个app同时运行
    • 一个文本处理程序
    • 一个影视后期程序

Linux默认的调度策略

  • 可以自定义调度策略,但是默认的调度策略只有三种
  • 对于实时进程:使用SCHED FIFO(先到先得、先进先出)和SCHED RR(run rounding❌robin✔轮询)两种
  • 对于普通进程:使用CFS(完全公平调度)
  • 其中等级最高的是FIFO(实时进程中的,99的永远先执行,之后才执行98……**除非自己让出来**),这种进程除非自己让出CPU否则Linux会一直执行它除非更高等级的FIFO和RR(实时进程中优先级相同的用轮询—->各50%轮着来)抢占它
  • RR只是这种线程中是同级别FIFO中的平均分配
  • 只有**实时进程主动让出,或者执行完毕后**,普通进程才有机会运行
  • 默认调度策略:
    • 实时(急诊)优先级分高低 - FIFO (First In First Out),优先级一样 - RR (Round Robin)
    • 普通:CFS

实时进程用java创建不了,要用c语言去创建

编程相关:进程之间通信—->管道等的实现(C语言编程)

  • 略过

进程之间的同步、互斥……(内核同步的概念)

纤程调度是在用户空间中,是程序员自己写的

中断

  • 中断是硬件和操作系统通信的一种机制
  • 中断会导致上下文切换。上下文切换不是都是中断引起的。(进程调度的时候的时间片切换会产生上下文切换)
  • 分为硬件中断和软件中断
    • 硬件中断:各种硬件的中断控制器向os内核发出的信号,让cpu优先处理该信号中断
      • 内核处理叫上半场,处理的应用程序叫下半场

image.png

  • 软件中断:系统调用,是由程序主动发出的中断,常见的就是int 0x80或者现在intel cpu的sysenter原语——>200多个系统调用

image.png

  • 操作系统内核不一定会处理这个中断,内核是可以禁止中断的(中断屏蔽),只是给个信号,内核处不处理还是内核的事情

    内存映射的过程