Linux系统的整体架构:用户态和内核态。线程等待的两种方式:阻塞和自旋。
开始
今天,我们来聊一聊Linux系统的整体架构和线程等待的两种方式。
Linux的系统架构
Linux系统架构 宏观上来说,上图很好的反应了Linux操作系统的架构图。首先,Linux系统包含一个内核,内核,实质是一个软件,特殊的地方在于,内核具备直接操纵CPU资源,内存资源,I/O资源等的能力。
内核之外是系统调用。系统调用这层,相当于内核增加了一层封装。将内核操作的复杂性封装起来,对外层提供透明的调用。系统调用可以理解为操作系统的最小功能单位,所有有应用程序,都是将通过将系统调用组合而成的。
系统调用之外,就是应用程序,各种各样的应用程序。特殊的有两个,一个是Shell,它是一个特殊的应用程序,俗称命令行。类似于windows的cmd,是一个命令解释器。有些应用程序是基于Shell做的,而不是基于系统调用。另一个特殊的是公用函数库。系统调用是操作系统的最小功能单位,一个Linux系统,根据版本不同,大约包含240~260个系统调用。为了使得操作更为简单,更加便于应用程序使用,Linux系统对系统调用的部分功能进行了再次封装,形成了公用函数库,以供应用程序调用。公用函数库中的一个方法,实质是若干个系统调用以特定的逻辑组合而成。
用户态和内核态
Linux的架构中,很重要的一个能力就是操纵系统资源的能力。但是,系统资源是有限的,如果不加限制的允许任何程序以任何方式去操纵系统资源,必然会造成资源的浪费,发生资源不足等情况。为了减少这种情况的发生,Linux制定了一个等级制定,即特权。Linux将特权分成两个层次,以0和3标识。0的特权级要高于3。换句话说,0特权级在操纵系统资源上是没有任何限制的,可以执行任何操作,而3,则会受到极大的限制。我们把特权级0称之为内核态,特权级3称之为用户态。
一个应用程序,在执行的过程中,会在用户态和内核态之间根据需要不断切换的。因为,如果只有用户态,那么必然有某些操纵系统资源的操作很难完成或不能完成,而如果一直都在内核态,那事实上,导致特权的分层失去了意义。大家全是最高权限,和大家都没有限制,没有什么区别。
所以,应用程序一般会运行于用户态,会在需要的时候,切换成内核态,完成相关任务后,会再切换加用户态。需要注意的是,一种切换是有一定系统开销的。
应用程序一般会在以下几种情况下切换到内核态:
1. 系统调用。
2. 异常事件。当发生某些预先不可知的异常时,就会切换到内核态,以执行相关的异常事件。
3. 设备中断。在使用外围设备时,如外围设备完成了用户请求,就会向CPU发送一个中断信号,此时,CPU就会暂停执行原本的下一条指令,转去处理中断事件。此时,如果原来在用户态,则自然就会切换到内核态。
线程等待
说到线程等待,很快就会想到阻塞。但是,其实线程等待不一定是阻塞,还有可能是自旋。
要阻塞或唤醒一个线程,就会消耗较多的系统资源,因为这种操作是需要操作系统介入的,自然就会发生用户态和核心态之间的切换,就会消耗大量系统资源。当这种操作高频发生时,就会消耗大量的CPU处理时间。那么,有什么方案可以解决这种问题吗?有。方案是:让子弹飞一会儿。
假设A线程有对资源Z加锁,但此时发现资源Z已经被线程B锁定,此时,一种方案是A进入阻塞状态,等待B释放锁。但是,我们如果事先知道,线程B对资源Z的加锁状态持续时间很短,那么,A其实没必要阻塞,等一等就好了。类似于执行一段空循环。这样,就避免了线程的阻塞和唤醒,也就避免了用户态和内核态的切换。这就是自旋。当然,自旋也需要消耗一定的计算资源,但是比较阻塞来说,就要好太多了。当然,这种方案是基于B的加锁状态不会持续太久,且不会有太多线程同时竞争同一资源的场景下的。换句话说,是基于乐观锁而设计的。