用户态与内核态

概念

简单说,Kernel space 是 Linux 内核的运行空间,User space 是用户程序的运行空间。为了安全,它们是隔离的,即使用户的程序崩溃了,内核也不受影响。

权限区别

Kernel space 可以执行任意命令,调用系统的一切资源;User space 只能执行简单的运算,不能直接调用系统资源,必须通过系统接口(又称 system call),才能向内核发出指令。

str = "my string" // 用户空间
x = x + 2
file.write(str) // 切换到内核空间

y = x + 4 // 切换回用户空间

切换的代价

当程序中有系统调用语句,程序执行到系统调用时,首先使用类似int 80H的软中断指令,保存现场,去的系统调用号,在内核态执行,然后恢复现场。
每个进程都会有两个栈,一个内核态栈和一个用户态栈。当执行int中断执行时就会由用户态,栈转向内核栈。系统调用时需要进行栈的切换。
而且内核代码对用户不信任,需要进行额外的检查。
系统调用的返回过程有很多额外工作,比如检查是否需要调度等。

进程与线程

区别和联系

对于Linux来说,线程(轻量级进程)和进程在内核态本质上是同一个东西,它们最大区别就在于地址空间。
不同的进程拥有不同的地址空间,在切换的时候切换页目录以使用新的地址空间,而线程不需要,多个线程可以共享同一个进程的内存,所以线程的切换比进程的切换更高效
另外,线程之间的通信也更简单的,直接通过共享的内存进行通信;而进程间的通信(管道、信号量、信号、SOCKET等)消耗较大且通信数据少。

用户态线程与内核态线程

请参考内核线程与用户线程的一点总结

多线程的代价

  • 设计更复杂(共享内存的并发问题)
  • 上下文切换开销(不同线程的CPU寄存器、计数器和状态的保存和载入)
  • 其他资源消耗(线程创建和消亡开销,切换的调度开销)

协程(goroutine)

协程存在于用户态,大量的协程实际上只占用了内核态的一个线程。
当协程数量和内核线程的数量不一致时,需要有调度器来维护所有的协程,尽可能让它们公平地使用CPU。
在go runtime有自己的调度器,主要使用了“work stealing”算法,调度器原理的形象解释参考goroutine与调度器以及goroutine的调度