操=作=系=统=之=进=程=管=理

进程 线程 纤程 中断

高频面试题:进程和线程的区别?

口语化回答:进程就是在内存中运行起来的程序,而线程是进程中的的执行路径。
专业回答进程是操作系统分配资源的基本单位,线程是操作系统执行调度的基本单位。最重要的是,每个进程有独立的内存空间,由线程调度执行。进程和线程最重要的区别是,线程共享进程的内存空间,没有自己独立的内存空间。

进程

Linux 中也成为 task,是系统分配资源的基本单位。
资源包括:独立的地址空间、内核数据结构(进程描述符…)、全局变量、数据段【请背过】
在这里插入图片描述

进程描述符PCB

每一个进程都有自己的进程描述符PCB(Process Control Block),我们刚才说过线程在Linux中也是普通的进程,所以线程也有自己的PCB。只不过有的PCB里面记录着和别人共享的空间,有的是自己独立的空间,就这么点区别,其他的没有了。

内核线程(了解即可)

【截图】

进程的创建和启动

【截图】

系统函数:Linux 是拿 C 写的,它对外提供了一些接口,比方提供了一个函数叫 fork(),调用它就可以启动一个进程。并且进程自己没办法释放自己的PCB,需要调用者手动释放。

从A中fork了B的话,A称之为B的父进程

僵尸进程、孤儿进程

【截图】

僵尸进程

特点:僵尸进程只占PCB

扫描二维码关注公众号,回复: 11059347 查看本文章

僵尸进程一般没有什么影响,因为这个进程整个都结束了,无非就是在它的父亲那里还挂了一个名,也就是PCB,实际上和这个进程相关的资源全都已经释放了,也就是基本不再占任何资源了,他在内存中唯一所占的资源就是那个PCB了,它的父进程不释放它。

当然僵尸进程是可以手动释放的,可以使用 wait() 方法来释放这个进程。

僵尸进程是怎么产生的呢?一般情况下,它的父进程可能是deamon守护进程,然后由于操作上问题没有释放子进程PCB,导致了子进程成了僵尸进程。僵尸进程上面说了,一般只有PCB这么一块数据没有释放,一般情况下不会有什么影响,但是一旦僵尸进程多了,累积起来肯定还是有影响。

孤儿进程

僵尸进程是指子进程还没结束,父进程就已经退出了。我们上面说过子进程是无法释放自己的PCB的,而它的父亲已经推出了,也就相当于子进程没有爹了,没有人帮它释放PCB了。这时候系统是怎么处理的呢?一般情况下,会交给某个特殊的进程(init进程)去处理,他就是所有孤儿的爹。

产生了孤儿进程之后会有什么影响吗?其实没有任何影响,哈哈哈哈哈。顶多他就是换了一个爹嘛。

进程的调度

内核进程调度器决定:

  • 该哪一个进程运行?
  • 何时开始?
  • 运行多长时间?

调度的方案也有很多,并且执行方式也可以非常灵活,以 Linux 为例子,Linux 可以为某个进程执行特定的调度方案。所以在 Linux 里面有一个很重要的概念:某一个进程都有自己专属的调度方案,而且这个调度方案还是可以自定义的。

进程调度基本概念

进程类型:
  • IO密集型:大部分时间用于等待IO操作(等待网络IO)
  • **CPU密集型 **:大部分时间用于闷头计算
进程的优先级
  • 实时进程(级别:0 ~ 99)数字越大级别越高
  • 普通进程 nice值(-20 ~ 19)

实时进程的优先级永远大于普通进程,只要实时进程在运行,普通进程就没有运行的机会

时间分配
  • linux 采用按优先级的CPU时间比
  • 其他操作系统多采用按优先级的时间片

多任务调度策略

  • 非抢占式:除非进程主动让出CPU,否则将一直运行
  • 抢占式(主流):由进程调度器强制开始或暂停(抢占)某一个进程的执行

Linux默认的调度策略

  • 对于实时进程:使用两种策略 SCHED_FIFO(先进先出)SCHED_RR(轮询)
    其中等级最高的是 FIFO ,这种进程除非自己让出CPU否则Linux会一直执行它,除非更高等级的 FIFO 和 RR 抢占它
    RR只有这种线程中是同等级别 FIFO 中的平均分配
  • 对于普通进程:CFS(绝对公平算法)
    刚才说过了,实时进程的优先级是远大于普通进程的,有实时进程在运行,普通进程永远得给我等着。

Linux的进程调度

Linux2.5 经典Unix O(1) 调度策略,偏向服务器,但对交互不友好
Linux2.6.23 采用CFS 完全公平调度算法Completely Fair Scheduler

线程 Thread

操作系统级别线程是如何实现的,每一个不同的操作系统,它的线程实现是不太一样的。

Linux 中的实现

在这里插入图片描述
在Linux里,线程和进程没有太大的区别,只不过和其他进程共享资源(内存空间 全局数据等…)
Linux里,可以通过 fork 指令起一个新的进程pid,并且完全可以把原来有的内存空间共享给新的进程

其他操作系统

在例如 Windows 里面,线程都是Light Weight Process,叫做轻量级进程。

纤程 Fiber

线程可以理解为线程中的线程,也就是在用户空间的一个线程。

【截图】

怎么来理解这个 Fiber,我们的 JVM 是跑在用户态的,操作系统是跑在内核态的。在 JVM 里面的 Thread 对象是怎么实现的呢?就是起一个操作系统级别的线程,也就是 LWP。在 JVM 空间的一个线程,对应着操作系统空间的一个 LWP 进程。但 LWP 不等于线程,只不过 JVM 使用操作系统的 LWP 来实现线程的功能。目前来说 HotSpot 的线程和操作系统的LWP是一对一。
纤程可以理解为线程的线程,就是线程里的一条条可执行路径,线程里的所有纤程共享线程对应的LWP。

为什么又要有纤程啊?

因为纤程是在用户空间的。它不需要和操作系统内核打交道,它启动的时候,也不需要操作系统再起一个对应的LWP。操作系统能够起的线程没有多少,以目前的配置来讲,起10000个线程已经卡顿的不行了,CPU的性能都花在线程切换上了。而纤程的切换是在用户空间发生的,我甚至不需要和硬件打交道,我只需要在我的用户空间里面,跟我的某一个JVM的线程,在它的内部就可以完成了,它切换起来的速度也特别快,也非常的轻量级。它能启动的数量是多少,几万个,甚至几十万个,都是可以的。
纤程和JVM里的线程对应关系是,多对一。

优点总结

1、跑在用户态,线程中的线程,切换和调度不需要经过操作系统OS
2、占用的资源很少(操作系统要起一个线程,前前后后要为它配的资源空间大概由1M)纤程只需要4K空间。
3、由于它非常的轻量级,切换起来比较简单,并且可以启动很多个(10万+没有问题)。想想看起10万个线程是什么概念,里面所有的资源都花在线程切换上了。

纤程和线程的区别

纤程是跑在用户空间的,由JVM自己管理自己切换;而线程由操作系统内核管理,是跑在内核空间的,JVM只负责和内核进行通信,由内核来进行线程的管理和切换。
再具体一点就是,线程的调用,涉及到用户态和内核态之间的通信,以及内核级别的线程上下文之间的切换;而纤程只在用户态,只涉及用户态级别的纤程上下文切换。

纤程的具体实现

实现方式和线程基本是一样的,它也有自己的纤程栈,包括上下文切换方式,都是可以参照纤程来实现。

目前支持内置线程的语言

  • kotlin、scala、go、python()、Java目前还不支持(Java openJDK里面有个项目)

纤程的应用场景

纤程用在什么地方呢,就是很短的计算任务,不需要和内核打交道。我只需要做计算任务,并且时间非常的短,我在用户空间就足以完成了,不需要在内核空间切换来切换去。

纤程 vs 线程池

纤程主要的优势就在于并发量,以及并发效率。

中断

所谓中断,就是打断内核,让内核放弃当前正在做的任务,来给到你响应,比如你键盘上敲了一个键,或者你的程序想要进行网络通信,内核对你发出请求的一种响应机制。中断说白了,是一种信号,内核基于你的信号会做出相应的响应。中断分硬中断和软中断。

硬中断

硬中断就是硬件给的中断,比如键盘、网卡、打印机等等这些个硬件产生的中断,叫做硬中断。1号中断–键盘,2号中断–网络,3号中断–硬盘。

软中断

软中断就是软件产生的中断,什么时候软件会给中断呢?就是我要和内核打交道的时候,我要做任何系统调用的时候,我做任何的系统调用,都要打断内核·。比方说你的程序想向硬盘读数据,这个IO操作只有内核能做,所以你的程序必须和内核打交道,说“嘿老铁,给我从硬盘上读个文件呗!”这就会产生一个软中断。

软中断比较特殊,是十六进制的80号中断–0x80 或者叫 80H。所有软件产生的中断都是 80中断。80中断 对应着一堆的函数(大概有 200 多个函数)。产生软中断,就是在对内核说“老铁,不好意思,打断你一下,我要调用系统函数啦,所以我给你一个 80信号”。站在内核的角度就是说“噢,有个哥们给了我一个 80信号,它一定是要调用我 200 多个函数中的一个。接下来你得告诉我你要调的是哪个函数,比方说你要调用的是1号函数,那么我就查这个表,1号函数是 read。好的,那你再把参数传给我吧”。好知道函数和参数之后,内核稀里哗啦一执行,执行完之后返回给你。这就是俗称的 interrupt 80。

阿里P9问的面试题

系统调用:int0x80(老的系统,需要调用c语言写的interrupt函数,往里头传个80)或者 sysenter 原语(由于80中断太常用,新的硬件现在提供原生支持,通过 sysenter 原语,从原来c语言的软件层面提升到了现在的硬件层面,所以效率就高了)。

  1. 调用号通过 ax 寄存器传入内核。调用号是指要调用哪个函数,内核通过函数对照表查找到具体调用的那个函数.
  2. 参数通过 bxcxdxsidi 传入内核。参数就5个吗,内核函数最长的就5个参数。
  3. 返回值通过 ax 返回。返回值一般都是 int 类型值,例如 pid 就是 int 类型。
举个例子

你的程序读网络上的一个东西,JVM 里的 read() 函数 调用 c 库 里面的 read() , c 库 里面的 read() 会调用内核空间的sysenter原语,告诉内核我在 ax 这个寄存器里已经填好了调用号,并且在 bx、cx、dx、si、di 读取参数,然后执行完函数之后,把返回值放回到 ax 里,最后一层层反馈,反馈到 java程序这儿。

腾讯第三轮面试题

1、聊一聊什么是线程
2、聊一聊什么是纤程
3、Fiber vs Thread
4、为啥和内核态打交道效率就低呢?(实际问的就是80中断)这里涉及到一个系统调用的过程,你需要往寄存器里装好多东西,然后在 80中断产生调用,然后再拿结果。那和我用户空间直接计算完了直接拿结果,那肯定效率要低一些。

发布了40 篇原创文章 · 获赞 0 · 访问量 382

猜你喜欢

转载自blog.csdn.net/weixin_43780400/article/details/105670676