【操作系统】用户态线程和内核态线程有什么区别?

本文内容转载于“拉勾教育”的讲义,详细可看拉勾教育的课程。本人学习之余做做笔记,顺便当个搬运工。

目录

用户态线程和内核态线程有什么区别?

什么是用户态和内核态?

系统调用过程

线程模型

用户态线程

内核态线程

用户态线程和内核态线程之间的映射关系

总结


用户态线程和内核态线程有什么区别?

这是一个组合型的问题,由很多小问题组装而成,比如:用户态和内核态是什么?用户级线程和内核级线程是一个怎样的对应关系?内核响应系统调用是一个怎样的过程?

什么是用户态和内核态?

Kernel 运行在超级权限模式(SupervisorMode)下,所以拥有很高的权限。按照权限管理的原则,多数应用程序应该运行在最小权限下。因此,很多操作系统,将内存分成了两个区域:

  • 内核空间(KernalSpace),这个空间只有内核程序可以访问;
  • 用户空间(User Space),这部分内存专门给应用程序使用。

用户空间中的代码被限制了只能使用一个局部的内存空间,我们说这些程序在用户态(User Mode)执行。内核空间中的代码可以访问所有内存,我们称这些程序在内核态(Kernal Mode)执行。

系统调用过程

如果用户态程序需要执行系统调用,就需要切换到内核态执行。过程如下。

内核程序执行在内核态(Kernal Mode),用户程序执行在用户态(User Mode)。当发生系统调用时,用户态的程序发起系统调用。因为系统调用中牵扯特权指令,用户态程序权限不足,因此会中断执行,也就是Trap(Trap是一种中断)。发生中断后,当前CPU执行的程序会中断,跳转到中断处理程序。内核程序开始执行,也就是开始处理系统调用。内核处理完成后,主动触发Trap,这样会再次发生中断,切换回用户态工作。

线程模型

一个应用程序启动后会在内存中创建一个执行副本,这就是进程。Linux的内核是一个Monolithic Kernel(宏内核),因此可以看作一个进程。也就是开机的时候,磁盘的内核镜像被导入内存作为一个执行副本,成为内核进程。进程可以分成用户态进程和内核态进程两类。用户态进程通常是应用程序的副本,内核态进程就是内核本身的进程。如果用户态进程需要申请资源,比如内存,可以通过系统调用向内核申请。那么用户态进程如果要执行程序,是否也要向内核申请呢?

程序在现代操作系统中并不是以进程为单位在执行,而是以一种轻量级进程(LightWeighted Process),也称作线程(Thread)的形式执行。一个进程可以拥有多个线程。进程创建的时候,一般会有一个主线程随着进程创建而创建。

如果进程想要创造更多的线程,就需要思考一件事情,这个线程创建在用户态还是内核态。进程可以通过API创建用户态的线程,也可以通过系统调用创建内核态的线程。

用户态线程

用户态线程也称作用户级线程(User LevelThread)。操作系统内核并不知道它的存在,它完全是在用户空间中创建。用户级线程有很多优势,比如:

  • 管理开销小:创建、销毁不需要系统调用
  • 切换成本低:用户空间程序可以自己维护,不需要走操作系统调度。

但是这种线程也有很多的缺点

  • 与内核协作成本高:比如这种线程完全是用户空间程序在管理,当它进行I/O的时候,无法利用到内核的优势,需要频繁进行用户态到内核态的切换。
  • 线程间协作成本高:设想两个线程需要通信,通信需要I/O,I/O需要系统调用,因此用户态线程需要支付额外的系统调用成本。
  • 无法利用多核优势:比如操作系统调度的仍然是这个线程所属的进程,所以无论每次一个进程有多少用户态的线程,都只能并发执行一个线程,因此一个进程的多个线程无法利用多核的优势。
  • 操作系统无法针对线程调度进行优化:当一个进程的一个用户态线程阻塞(Block)了,操作系统无法及时发现和处理阻塞问题,它不会更换执行其他线程,从而造成资源浪费。 

内核态线程

内核态线程也称作内核级线程(Kernel LevelThread)。这种线程执行在内核态,可以通过系统调用创造一个内核级线程。内核级线程有很多优势

  • 可以利用多核CPU优势:内核拥有较高权限,因此可以在多个CPU核心上执行内核线程。
  • 操作系统级优化:内核中的线程操作I/O不需要进行系统调用;一个内核线程阻塞了,可以立即让另一个执行。

内核线程也有一些缺点

  • 创建成本高:创建的时候需要系统调用,也就是切换到内核态。
  • 扩展性差:由一个内核程序管理,不可能数量太多。
  • 切换成本较高:切换的时候,也同样存在需要内核操作,需要切换内核态。

用户态线程和内核态线程之间的映射关系

线程简单理解,就是要执行一段程序。程序不会自发的执行,需要操作系统进行调度。我们思考这样一个问题,如果有一个用户态的进程,它下面有多个线程。如果这个进程想要执行下面的某一个线程,应该如何做呢?这时,比较常见的一种方式,就是将需要执行的程序,让一个内核线程去执行。毕竟,内核线程是真正的线程。因为它会分配到CPU的执行资源。

如果一个进程所有的线程都要自己调度,相当于在进程的主线程中实现分时算法调度每一个线程,也就是所有线程都用操作系统分配给主线程的时间片段执行。这种做法,相当于操作系统调度进程的主线程;进程的主线程进行二级调度,调度自己内部的线程。这样操作劣势非常明显,比如无法利用多核优势,每个线程调度分配到的时间较少,而且这种线程在阻塞场景下会直接交出整个进程的执行权限。

由此可见,用户态线程创建成本低,问题明显,不可以利用多核。内核态线程,创建成本高,可以利用多核,切换速度慢。因此通常我们会在内核中预先创建一些线程,并反复利用这些线程。这样,用户态线程和内核态线程之间就构成了下面4种可能的关系:

  • 多对一(Many to One)

用户态进程中的多线程复用一个内核态线程。这样,极大地减少了创建内核态线程的成本,但是线程不可以并发。因此,这种模型现在基本上用的很少。这里有个疑问:用户态线程怎么用内核态线程执行程序?程序是存储在内存中的指令,用户态线程是可以准备好程序让内核态线程执行的。后面的几种方式也是利用这样的方法。

 

  • 一对一(One to One)

该模型为每个用户态的线程分配一个单独的内核态线程,在这种情况下,每个用户态都需要通过系统调用创建一个绑定的内核线程,并附加在上面执行。这种模型允许所有线程并发执行,能够充分利用多核优势,Windows NT内核采取的就是这种模型。但是因为线程较多,对内核调度的压力会明显增加。

 

  • 多对多(Many To Many)

这种模式下会为n个用户态线程分配m个内核态线程。m通常可以小于 n。一种可行的策略是将m设置为核数。这种多对多的关系,减少了内核线程,同时也保证了多核心并发。Linux目前采用的就是该模型。

 

 

  • 两层设计(Two Level)

这种模型混合了多对多和一对一的特点。多数用户态线程和内核线程是n对m的关系,少量用户线程可以指定成1对1的关系。

 

 

总结

用户态线程工作在用户空间,内核态线程工作在内核空间。用户态线程调度完全由进程负责,通常就是由进程的主线程负责。相当于进程主线程的延展,使用的是操作系统分配给进程主线程的时间片段。内核线程由内核维护,由操作系统调度。

用户态线程无法跨核心,一个进程的多个用户态线程不能并发,阻塞一个用户态线程会导致进程的主线程阻塞,直接交出执行权限。这些都是用户态线程的劣势。内核线程可以独立执行,操作系统会分配时间片段。因此内核态线程更完整,也称作轻量级进程。内核态线程创建成本高,切换成本高,创建太多还会给调度算法增加压力,因此不会太多。

实际操作中,往往结合两者优势,将用户态线程附着在内核态线程中执行。

硬件 内核 操作 操作系统 shell的关系:

操作系统连接用户使用的软件和电脑硬件。内核是操作系统的一部分,操作系统分为内核和系统调用两种。shell字面意思理解为壳,是一个软件,提供了一个用户和操作系统之间的接口,通过shell我们可以调用其他软件,比如chmod、chown、firefox、man等,它可以调用内核来工作. shell其实就是一个接口;狭义的shell 指的是命令行界面,比如我们的bash;广义的shell 则包括图形界面的软件,因为其实图形界面也可以调用系统内核。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吻雨fated

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值