在现代操作系统中,CPU实际上都在两种截然不同的模式中花费时间:

Kernel Mode

在内核模式下,执行代码可以完全且不受限制地访问底层硬件。它可以执行任何CPU指令和引用任何内存地址。内核模式通常为操作系统的最低级别、最受信任的功能保留。内核模式下的崩溃是灾难性的;他们会让整个电脑瘫痪。

User Mode

在用户模式下,执行代码不能直接访问硬件或引用内存。在用户模式下运行的代码必须委托给系统api来访问硬件或内存。由于这种隔离提供的保护,用户模式下的崩溃总是可恢复的。在您的计算机上运行的大多数代码将在用户模式下执行。

CPU保护模式

x86 CPU提供了四个保护环(protection rings):0、1、2和3。通常只使用0环(内核)和3环(用户)。

应用程序一般会在以下几种情况下切换到内核模式:

1. 系统调用。

2. 异常事件。当发生某些预先不可知的异常时,就会切换到内核态,以执行相关的异常事件。

3. 设备中断。在使用外围设备时,如外围设备完成了用户请求,就会向CPU发送一个中断信号,此时,CPU就会暂停执行原本的下一条指令,转去处理中断事件。此时,如果原来在用户态,则自然就会切换到内核态。

思考:CAS操作是否涉及到用户态到内核态的切换?

不会,CAS只是实现原子性的让CPU更改变量的值,从0变为1, 不涉及内核操作.

思考:物理内存是有限的(比如16G内存),怎么把有限的内存分配给不同的进程?

Linux 给每个进程虚拟出一块很大的地址空间,比如 32 位机器上进程的虚拟内存地址空间是 4GB,从 0x00000000 到 0xFFFFFFFF。但这 4GB 并不是真实的物理内存,而是进程访问到了某个虚拟地址,如果这个地址还没有对应的物理内存页,就会产生缺页中断,分配物理内存,MMU(内存管理单元)会将虚拟地址与物理内存页的映射关系保存在页表中,再次访问这个虚拟地址,就能找到相应的物理内存页。每个进程的这 4GB 虚拟地址空间分布如下图所示:

  用户空间从低到高依次是代码区、数据区、堆、共享库与 mmap 内存映射区、栈、环境变量。
其中堆向高地址增长,栈向低地址增长。
  用户空间上还有一个共享库和 mmap 映射区,Linux 提供了内存映射函数 mmap, 它可将文件
内容映射到这个内存区域,用户通过读写这段内存,从而实现对文件的读取和修改,无需通过
read/write 系统调用来读写文件,省去了用户空间和内核空间之间的数据拷贝,Java 的MappedByteBuffer 就是通过它来实现的;用户程序用到的系统共享库也是通过 mmap 映射到了
这个区域。
  task_struct结构体本身是分配在内核空间,它的vm_struct成员变量保存了各内存区域的起始和
终止地址,此外task_struct中还保存了进程的其他信息,比如进程号、打开的文件、创建的
Socket 以及 CPU 运行上下文

  进程的虚拟地址空间总体分为用户空间和内核空间,低地址上的 3GB 属于用户空间,高地址的 1GB 是内核空间,这是基于安全上的考虑,用户程序只能访问用户空间,内核程序可以访问整个进程空间,并且只有内核可以直接访问各种硬件资源,比如磁盘和网卡。那用户程序需要访问这些硬件资源该怎么办呢?答案是通过系统调用,系统调用可以理解为内核实现的函数,比如应用程序要通过网卡接收数据,会调用 Socket 的 read 函数:

ssize_t read(int fd,void *buf,size_t nbyte)

  CPU 在执行系统调用的过程中会从用户态切换到内核态,CPU 在用户态下执行用户程序,使用的是用户空间的栈,访问用户空间的内存;当 CPU 切换到内核态后,执行内核代码,使用的是内核空间上的栈。

  在 Linux 中,线程是一个轻量级的进程,轻量级说的是线程只是一个 CPU 调度单元,因此线程有自己的task_struct结构体和运行栈区,但是线程的其他资源都是跟父进程共用的,比如虚拟地址空间、打开的文件和 Socket 等。

文章作者: 刘同学
本文链接:
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 刘同学的小站
后端 Java
喜欢就支持一下吧