一、介绍
用户态和内核态是操作系统的两种运行状态,它们分别对应于不同的权限级别和访问能力。
- 用户态(User Mode):这是应用程序运行的环境。在用户态下,应用程序可以访问系统资源,如文件、网络等,但其访问能力受到限制。具体来说,用户态下的CPU只能受限地访问内存,并且不允许直接访问外围设备,如网卡、硬盘等。此外,用户态下的CPU不允许独占,即CPU可以被其他程序获取。为了使应用程序能够访问到内核管理的资源(如CPU、内存、I/O等),用户态必须通过系统调用来请求内核提供服务。
- 内核态(Kernel Mode):这是操作系统内核运行的环境。在内核态下,代码拥有更高的权限,可以直接访问系统硬件资源。内核态下的代码负责管理系统资源,如进程调度、内存管理、设备驱动等。具体来说,处于内核态的CPU可以访问任意的数据,包括外围设备,并且可以从一个程序切换到另一个程序,且占用CPU时不会发生抢占情况。
用户态和内核态的划分主要是出于访问能力的限制和安全性的考量。计算机中有一些比较危险的操作,如设置时钟、内存清理等,这些都需要在内核态下完成。如果允许用户态的程序随意进行这些危险操作,极容易导致系统崩溃。因此,用户态和内核态的划分确保了系统的稳定性和安全性。
在操作系统执行用户程序时,CPU会为程序分配一段独立的内存空间作为用户态,并将程序的代码和数据加载到这段内存空间中。同时,CPU会为程序创建一个进程控制块(PCB),用于记录程序的运行状态、内存使用情况、文件描述符等信息。而在执行系统调用时,操作系统会为当前进程创建一个新的内核栈,并将当前进程的上下文切换到内核态。
请注意,频繁的用户态和内核态之间的切换会严重影响系统性能。因此,在设计和实现操作系统时,需要权衡性能和安全性之间的关系,以找到最佳的平衡点。
二、java中那些操作使用了内核态
在Java中,由于Java是一种跨平台的语言,并且主要通过Java虚拟机(JVM)运行,因此大部分Java代码本身并不直接运行在内核态。然而,当Java程序需要访问底层系统资源或执行某些特权操作时,它可能会通过JNI(Java Native Interface)或其他机制调用本地代码(通常是C、C++等编写的),这些本地代码可能会运行在内核态。
以下是一些Java中可能使用内核态的示例操作:
- 系统调用:Java程序可以通过JNI或其他机制调用本地代码来执行系统调用,这些系统调用通常运行在内核态。系统调用是用户态程序请求内核态服务的一种方式,例如打开文件、读取文件、创建进程等。
- 硬件访问:虽然Java本身并不直接提供硬件访问的接口,但通过JNI调用本地代码,Java程序可以间接地访问硬件资源。这些硬件访问操作通常需要在内核态下执行,以确保对硬件的安全和正确的控制。
- 网络I/O:Java中的网络I/O操作(如Socket编程)通常通过JVM提供的网络库来实现。这些网络库可能会调用本地代码来执行网络I/O操作,这些操作可能涉及到对底层网络硬件的访问和控制,因此可能需要运行在内核态。
- 文件I/O:虽然Java提供了丰富的文件I/O接口,但这些接口的实现通常依赖于底层操作系统的文件系统。当Java程序执行文件I/O操作时,它可能会通过JNI调用本地代码来执行底层文件系统的操作,这些操作可能需要运行在内核态。
- 多线程管理:Java中的多线程管理是通过JVM的线程管理器来实现的。在某些情况下,线程管理器可能需要调用本地代码来执行一些特权操作,如设置线程优先级、创建线程等。这些操作可能需要运行在内核态。
需要注意的是,虽然这些操作可能会涉及到内核态的执行,但Java程序员通常不需要直接关心这些细节。JVM和相关的库会负责处理这些底层细节,并提供给Java程序员一个简单、安全的编程接口。在大多数情况下,Java程序员只需要关注他们的业务逻辑和算法实现即可。
三、cache line的概念
Cache Line(缓存行)是CPU Cache中的最小缓存单位,它通常是由若干个连续排列的字节组成的区域,该区域作为一个整体成组访问主存。目前主流的CPU Cache的Cache Line大小通常是64个字节。
CPU从内存读取数据时实际上是按块读取的,这就是Cache Line。当CPU要读取一个数据时,它会读取该数据所在的整个Cache Line到CPU Cache中,如果接下来要读取的数据也在这个Cache Line中,那么CPU就可以直接从Cache中读取,而不需要再去访问内存,这样可以大大提高CPU的访问速度。
此外,Cache Line的设计也是基于局部性原理的。当一个指令或数据被访问过之后,与它相邻地址的数据有很大概率也会被访问,因此将更多可能被访问的数据存入缓存,可以提高缓存命中率。
Cache Line又分为多种类型,包括直接映射缓存、多路组相连缓存和全相连缓存等。直接映射缓存会将一个内存地址固定映射到某一行的Cache Line,而多路组相连缓存和全相连缓存则具有更复杂的映射关系。
总的来说,Cache Line是CPU缓存设计中的一个重要概念,它对于提高CPU的访问速度和程序性能具有重要的作用。
Cache Line在代码中的应用:
package chatpter11;
import java.util.concurrent.CountDownLatch;
public class TestCacheLine {
public static long COUNT = 1_0000_0000L;
private static class T {
private long p1,p2,p3,p4,p5,p6,p7;
public long x = 0l;
private long p8,p9,p10,p12,p13,p14,p15;
}
public static T[] arr = new T[2];
static {
arr[0] = new T();
arr[1] = new T();
}
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(2);
Thread t1 = new Thread(() -> {
for (long i = 0; i < COUNT; i++) {
arr[0].x = i;
}
countDownLatch.countDown();
});
Thread t2 = new Thread(() -> {
for (long i = 0; i < COUNT; i++) {
arr[1].x = i;
}
countDownLatch.countDown();
});
final long start = System.nanoTime();
t1.start();
t2.start();
countDownLatch.await();
System.out.println((System.nanoTime() - start) / 100_0000);
}
}
执行结果:
46
Process finished with exit code 0
四、CPU缓存一致性协议
CPU缓存一致性协议是指在多核CPU系统中,多个CPU的缓存副本应该保持一致,以保证数据的正确性和一致性。
在多核CPU系统中,每个CPU都有自己的缓存,用于提高数据访问的速度和效率。然而,由于数据的复制和缓存,不同CPU之间的缓存副本可能会出现数据不一致的情况。为了解决这个问题,CPU缓存一致性协议提出了一些机制和协议,以确保多个CPU之间的缓存副本能够保持一致。
其中,MESI协议是一种常用的CPU缓存一致性协议。它定义了缓存行的四种状态:
- Modified(被修改):该缓存行只被缓存在该CPU的缓存中,并且是被修改过的,因此它与主存中的数据是不一致的。
- Exclusive(独享):独享状态的缓存行只被缓存在该CPU的缓存中,它是未被修改过的,是与主存中的数据一致的。
- Shared(共享):共享状态意味着该缓存行可能被多个CPU进行缓存,并且各个缓存中的数据与主存中的数据是一致的。
- Invalid(无效):该缓存行中的数据无效,需要从主存中重新读取。
当CPU要修改内存中的数据时,它会向其他CPU发送消息,通知它们该数据已经被修改。其他CPU收到消息后会将自己缓存中对应的数据标记为无效状态,确保下次访问时从主存中重新读取数据,从而保证数据的一致性。
除了MESI协议外,还有其他一些CPU缓存一致性协议,如MOESI协议等。这些协议都旨在解决多核CPU系统中缓存数据一致性的问题,确保系统的正确性和稳定性。