Open wtysos11 opened 3 years ago
操作系统相关的。这个是面试的时候被面试官追问的一个内容,说实话,确实不好去回答。
参考:
进程这个概念其实是比较简单的。按照操作系统书中的原话,进程是资源分配的最小单位。分配的资源主要包括内存空间、文件描述符等。具体可以参考线程间到底共享了哪些进程资源,这篇文章比较详细地介绍了进程资源的构成,以及线程共享的资源类型,我觉得是很不错的。
进程的问题在于它实在是有点大了,比如堆区的指针就是全部存在进程里面,还有着各种全局变量,还需要刷新页目录以使用新的地址空间,虚拟内存更新后TLB也需要更新。以进程为调度单位的话,上下文切换时每次都要把整个进程换出(参考linux虚拟空间中的堆和栈在进程切换时时怎样保存然后还原的,应该只是进行了上下文切换,虚拟内存不足的时候才会发生换出到swap分区的情况。并且实际分配给进程的空间很多都是虚拟内存,不占用实际的内存空间),还需要保存当前内存的所有状态。
进程上下文的组成:进程和线程的区别中的说法时CPU中的所有寄存器的值、进程的状态以及堆栈中的内容。
Go语言的栈内存和逃逸分析和一文教你搞懂Go的栈空间这篇比较好的解释了栈空间的组成,以及像go和java这种运行时干更多事情的语言中栈空间的分配。
线程是调度的基本单位。按照内核线程和用户线程的一点小总结的说法,linux内核中并不存在真正意义上的线程,所有的执行实体都是任务。每一个任务在概念上都是一个类似单线程的进程,具有内存空间、执行实体、文件资源等。只是不同任务之间可以选择共用内存空间,而共用同一个内存空间的多个任务构成了一个进程,其中的每一个任务则是这个任务里面的线程。这个说法我觉得还是比较有道理的,很KISS。
用户线程在用户空间实现,与内核线程不同,操作系统不去直接调度用户线程。用户线程只能在进程内竞争资源,切换由应用程序自己进行(不需要内核干涉),减少了内核态的消耗,但同时也不能很好地利用多核CPU。
用户线程的问题在于阻塞。因为执行系统调用时本质上是多个用户线程访问同一个内核线程,一旦一个用户线程阻塞,其他用户线程也就跟着被阻塞了。(联系有一对一和一对多,一般系统调用或者API创建的是一对一,一般架构中都是多对多的模式)
创建代价小
按照其他地方的定义,协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
但是这和线程就没什么区别了,为什么要用到协程?
协程最初的目的是为了用同步代码执行异步任务。比如一个需要执行UI的同步任务,正常想法是执行同步操作,然后异步等待,最终等到结果后在继续执行。但因为OI操作可能阻塞用户线程,就会使得其他(比如UI)线程被阻塞。
协程支持修改当前任务CPU的执行权限,并在需要的时候恢复权限。能够被临时剥夺CPU的执行权限而暂停,并在需要的时候又能收回权限继续执行的任务即为协程。(线程做不到这种级别,我的理解是线程对CPU的调度更多是内核参与的。就是说应用程序其实没有办法做到实时地把线程的CPU给剥夺)
总体来说,协程是比线程更小粒度的任务,允许切换协程的最小单元式函数,可以在任何地方进行切换(在go中的goroutine表现的特别明显)
一个区别是协程允许协作式的切换,即程序员和运行时可以决定切换的时间。 另一个区别在于代价,协程比线程的上下文切换代价更小 为什么协程的切换代价比线程更小
参考
从用户态切换到内核态,需要通过系统调用的方式(比如int 80H的软中断指令;也有大佬指出现代内核因为软中断太慢已经不使用了,而是改用特定的硬件指令,比如sysenter,syscall等)。该过程也是有CPU上下文切换的
切换时,先保存CPU寄存器中用户态的指令位置,再重新更新为内核指令的位置。这个过程中涉及到寄存器状态的切换、特权级别的切换、硬件的特权级保护机制和各种检查。
当系统调用结束时,CPU寄存器恢复到原来保存的用户态。一次系统调用,发生了两次CPU上下文切换。(这个部分需要再查一下书,确认一下)
我没有记错的话,主要是涉及到了CPU中一个标志位的更改,这个也需要查一下。
操作系统相关的。这个是面试的时候被面试官追问的一个内容,说实话,确实不好去回答。
参考:
进程
进程这个概念其实是比较简单的。按照操作系统书中的原话,进程是资源分配的最小单位。分配的资源主要包括内存空间、文件描述符等。具体可以参考线程间到底共享了哪些进程资源,这篇文章比较详细地介绍了进程资源的构成,以及线程共享的资源类型,我觉得是很不错的。
进程的问题在于它实在是有点大了,比如堆区的指针就是全部存在进程里面,还有着各种全局变量,还需要刷新页目录以使用新的地址空间,虚拟内存更新后TLB也需要更新。以进程为调度单位的话,上下文切换时每次都要把整个进程换出(参考linux虚拟空间中的堆和栈在进程切换时时怎样保存然后还原的,应该只是进行了上下文切换,虚拟内存不足的时候才会发生换出到swap分区的情况。并且实际分配给进程的空间很多都是虚拟内存,不占用实际的内存空间),还需要保存当前内存的所有状态。
进程上下文的组成:进程和线程的区别中的说法时CPU中的所有寄存器的值、进程的状态以及堆栈中的内容。
Go语言的栈内存和逃逸分析和一文教你搞懂Go的栈空间这篇比较好的解释了栈空间的组成,以及像go和java这种运行时干更多事情的语言中栈空间的分配。
线程
线程是调度的基本单位。按照内核线程和用户线程的一点小总结的说法,linux内核中并不存在真正意义上的线程,所有的执行实体都是任务。每一个任务在概念上都是一个类似单线程的进程,具有内存空间、执行实体、文件资源等。只是不同任务之间可以选择共用内存空间,而共用同一个内存空间的多个任务构成了一个进程,其中的每一个任务则是这个任务里面的线程。这个说法我觉得还是比较有道理的,很KISS。
内核线程
用户线程
用户线程在用户空间实现,与内核线程不同,操作系统不去直接调度用户线程。用户线程只能在进程内竞争资源,切换由应用程序自己进行(不需要内核干涉),减少了内核态的消耗,但同时也不能很好地利用多核CPU。
用户线程的问题在于阻塞。因为执行系统调用时本质上是多个用户线程访问同一个内核线程,一旦一个用户线程阻塞,其他用户线程也就跟着被阻塞了。(联系有一对一和一对多,一般系统调用或者API创建的是一对一,一般架构中都是多对多的模式)
进程与线程的区别
创建代价小
协程
按照其他地方的定义,协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
但是这和线程就没什么区别了,为什么要用到协程?
协程最初的目的是为了用同步代码执行异步任务。比如一个需要执行UI的同步任务,正常想法是执行同步操作,然后异步等待,最终等到结果后在继续执行。但因为OI操作可能阻塞用户线程,就会使得其他(比如UI)线程被阻塞。
协程支持修改当前任务CPU的执行权限,并在需要的时候恢复权限。能够被临时剥夺CPU的执行权限而暂停,并在需要的时候又能收回权限继续执行的任务即为协程。(线程做不到这种级别,我的理解是线程对CPU的调度更多是内核参与的。就是说应用程序其实没有办法做到实时地把线程的CPU给剥夺)
总体来说,协程是比线程更小粒度的任务,允许切换协程的最小单元式函数,可以在任何地方进行切换(在go中的goroutine表现的特别明显)
线程和协程
一个区别是协程允许协作式的切换,即程序员和运行时可以决定切换的时间。 另一个区别在于代价,协程比线程的上下文切换代价更小 为什么协程的切换代价比线程更小
特权模式切换
参考
从用户态切换到内核态,需要通过系统调用的方式(比如int 80H的软中断指令;也有大佬指出现代内核因为软中断太慢已经不使用了,而是改用特定的硬件指令,比如sysenter,syscall等)。该过程也是有CPU上下文切换的
切换时,先保存CPU寄存器中用户态的指令位置,再重新更新为内核指令的位置。这个过程中涉及到寄存器状态的切换、特权级别的切换、硬件的特权级保护机制和各种检查。
当系统调用结束时,CPU寄存器恢复到原来保存的用户态。一次系统调用,发生了两次CPU上下文切换。(这个部分需要再查一下书,确认一下)
我没有记错的话,主要是涉及到了CPU中一个标志位的更改,这个也需要查一下。