摘要:内核空间是 Linux 内核的运行空间,它拥有最高的权限,可以执行任意命令,调用系统的一切资源。Linux 内核是整个 Linux 操作系统的核心部分,负责系统的硬件管理、进程调度、内存管理、文件系统操作、网络通信等关键功能,是系统中最底层的部分,直接控制和管
Linux 系统的虚拟地址空间被划分为内核空间和用户空间两部分。
内核空间是 Linux 内核的运行空间,它拥有最高的权限,可以执行任意命令,调用系统的一切资源。Linux 内核是整个 Linux 操作系统的核心部分,负责系统的硬件管理、进程调度、内存管理、文件系统操作、网络通信等关键功能,是系统中最底层的部分,直接控制和管理硬件,并为上层应用程序提供接口。内核空间为内核代码和设备驱动程序等高权限任务提供了一个专用的内存区域,保证了系统的安全性,因为普通用户程序被隔离在用户空间,无法直接影响内核。
用户空间是用户程序的运行空间,只能执行简单的运算,不能直接调用系统资源,必须通过系统接口才能向内核发出指令。每个进程的 4G 地址空间中,最高 1G 都是内核空间,只有剩余的 3G 才归进程自己使用,换句话说,最高 1G 的内核空间是被所有进程共享的。在用户空间中,进程运行在用户地址空间中,被执行的代码要受到 CPU 的诸多检查,它们只能访问映射其地址空间的页表项中规定的在用户态下可访问页面的虚拟地址,且只能对任务状态段(TSS)中 I/O 许可位图(I/O Permission Bitmap)中规定的可访问端口进行直接访问。
为了保证内核的安全,现在的操作系统一般都强制用户进程不能直接操作内核。具体的实现方式基本都是由操作系统将虚拟地址空间划分为两部分,一部分为内核空间,另一部分为用户空间。针对 Linux 操作系统而言,最高的 1G 字节(从虚拟地址 0xC0000000 到 0xFFFFFFFF)由内核使用,称为内核空间。而较低的 3G 字节(从虚拟地址 0x00000000 到 0xBFFFFFFF)由各个进程使用,称为用户空间。
为什么要区分内核空间与用户空间呢?在 CPU 的所有指令中,有些指令极其危险,若错用可能导致系统崩溃,比如清内存、设置时钟等。为降低系统崩溃的概率,CPU 将指令分为特权指令和非特权指令。对于那些危险的指令,只允许操作系统及其相关模块使用,普通应用程序只能使用不会造成灾难的指令。例如 Intel 的 CPU 将特权等级分为 4 个级别:Ring0~Ring3,而 Linux 系统只使用了 Ring0 和 Ring3 两个运行级别,Windows 系统也是如此。当进程运行在 Ring3 级别时处于用户态,运行在 Ring0 级别时处于内核态。
在内核态下,进程运行在内核地址空间中,此时 CPU 可以执行任何指令,运行的代码也不受限制,可以自由访问任何有效地址,还能直接进行端口访问。而在用户态下,进程运行在用户地址空间中,被执行的代码要受到 CPU 的诸多检查,只能访问映射其地址空间的页表项中规定的在用户态下可访问页面的虚拟地址,且只能对任务状态段(TSS)中 I/O 许可位图中规定的可访问端口进行直接访问。
对于以前的 DOS 操作系统来说,没有内核空间、用户空间以及内核态、用户态这些概念,所有代码都运行在内核态,用户编写的应用程序代码很容易让操作系统崩溃。而对于 Linux 来说,通过区分内核空间和用户空间的设计,隔离了操作系统代码与应用程序代码。这样即便单个应用程序出现错误,也不会影响到操作系统的稳定性,其他程序还可以正常运行。所以,区分内核空间和用户空间本质上是要提高操作系统的稳定性及可用性。
当进程运行在内核空间时处于内核态,此时 CPU 可以执行任何指令,运行的代码也不受任何限制,可以自由访问任何有效地址,也可以直接进行端口访问。例如,内核态的程序可以直接操作硬件、管理内存分配、处理中断和异常等,拥有对系统硬件和资源(如内存、CPU、I/O 设备)的完全访问权限和控制权。
当进程运行在用户空间时处于用户态,被执行的代码要受到 CPU 的诸多检查,只能访问映射其地址空间的页表项中规定的在用户态下可访问页面的虚拟地址,且只能对任务状态段中 I/O 许可位图中规定的可访问端口进行直接访问。运行于用户态的代码则要受到处理器的诸多检查,它们只能访问映射其地址空间的页表项中规定的在用户态下可访问页面的虚拟地址,且只能对任务状态段(TSS)中 I/O 许可位图(I/O Permission Bitmap)中规定的可访问端口进行直接访问。
隔离操作系统代码与应用程序代码,提高系统稳定性。即使单个应用程序出现错误,也不会影响操作系统的稳定性,其他程序仍可正常运行。
内核态和用户态的划分是操作系统设计中的重要概念。运行在用户态下的程序不能直接访问操作系统内核数据结构和程序,当程序运行在 3 级特权级上时,就可以称之为运行在用户态,因为这是最低特权级,是普通的用户进程运行的特权级;当程序运行在 0 级特权级上时,就可以称之为运行在内核态。这种特权级的不同确保了用户态程序的崩溃不会影响整个系统的稳定性,因为它无法直接接触到系统的核心部分。即使用户态程序崩溃,操作系统内核仍然可以正常工作。同时,通过严格的隔离,操作系统可以确保各个进程之间的相互影响最小化,防止一个进程的崩溃影响其他进程。内核态有能力管理这些进程之间的资源共享与隔离。
在 Linux 系统中,用户空间的应用程序可以通过以下方式进入内核空间:
方式系统调用:应用程序向内核发起 “系统调用”,从用户态进入内核态,在内核空间中完成任务后再切换回用户态。软中断和硬件中断。系统调用:应用程序要读取磁盘上的文件时,可以向内核发起 “系统调用”,告诉内核 “我要读取磁盘上的某某文件”。通过一个特殊的指令,进程从用户态进入内核态,到达内核空间后,CPU 可以执行任何指令,包括从磁盘读取数据。具体过程是先把数据读取到内核空间中,然后再把数据拷贝到用户空间并从内核态切换到用户态。此时应用程序从系统调用中返回并拿到数据,可以继续执行。例如在 ARM32 平台,系统调用是通过 swi 指令发起,如常见的 write 系统调用,在 glibc-2.31 中,最终通过一系列宏定义展开,通过 swi 指令发起系统调用,参数通过特定的寄存器传递。进入内核空间后,根据 ARM32 架构,swi 指令会导致 CPU 产生异常,进入 Supervisor 模式,跳转到对应的异常向量指向的地址执行,执行流程转入 swi 指令异常处理接口 vector_swi。软中断和硬件中断:对于一个进程来讲,从用户空间进入内核空间的方式有三种,即系统调用、软中断和硬件中断。但这三种方式每一种都涉及到大量的操作系统知识,这里不做展开。过程应用程序把高科技的事情(如从磁盘读取文件)外包给系统内核,系统内核做这些事情既专业又高效。具体过程是先把数据读取到内核空间中,然后再把数据拷贝到用户空间并从内核态切换到用户态。应用程序把如从磁盘读取文件等任务外包给系统内核,系统内核利用其专业高效的能力完成这些任务。以用户空间使用 open 系统调用函数打开一个字符设备为例,VFS 层接收 open 系统调用,在虚拟文件系统 VFS 中查找与字符设备对应 struct inode 节点,遍历字符设备列表找到 cdev 对象,创建并初始化 struct file 对象,将其 file_operations 成员指向字符设备驱动 struct cdev 对象中的 file_operations 成员,最后回调 file_operations 中的 open 函数,完成任务后给应用返回一个文件描述符。当应用程序使用该文件描述符调用 read /write 等读写函数时,VFS 层再回调 file_operations 中的 read /write 函数,调用字符设备驱动读写。对于系统调用的实现,在 Linux 中系统调用是用户空间访问内核的唯一手段。一般地,系统调用都是通过软件中断实现的,如 x86 系统上的软件中断由 int $0x80 指令产生,128 号异常处理程序就是系统调用处理程序 system_call 。Linux 中每一个系统调用都有对应的系统调用号作为唯一的标识,内核维护一张系统调用表,sys_call_table,表中的元素是系统调用函数的起始地址,系统调用号就是系统调用在调用表的偏移量。在 x86 上,系统调用号是通过 eax 寄存器传递给内核的。参数传递方面,Linux 最多允许向系统调用传递 6 个参数,分别依次由 % ebx,% ecx,% edx,% esi,% edi 这 5 个寄存器完成,另外还有一个单独的寄存器存放指向所有这些参数在用户空间的地址的指针。给用户空间的返回值通过 eax 寄存器传递。当进行系统调用时,进程不仅要从用户态切换到内核态,同时也要完成栈切换,从用户栈切换到内核栈时,需保存 % esp 以及相关寄存器的值并将 % esp 设置成内核栈的对应值;从内核栈切换回用户栈时,需要恢复用户栈的 % esp 及相关寄存器的值以及保存内核栈的信息。用户栈的 % esp 和寄存器的值在调用 int 指令进行系统调用后会被压入内核栈中,系统调用通过 iret 指令返回,在返回之前会从内核栈弹出用户栈的 % esp 和寄存器的状态,然后进行恢复。从下往上依次为:硬件 -> 内核空间 -> 用户空间。在硬件之上,内核空间中的代码控制了硬件资源的使用权,用户空间中的代码只有通过内核暴露的系统调用接口才能使用到系统中的硬件资源。
Linux 系统的整体结构可以分为三个部分,最底层是硬件,往上是内核空间,再往上是用户空间。硬件是整个系统的基础,提供了计算、存储和输入输出等功能。内核空间是操作系统的核心部分,负责管理硬件资源、调度进程、处理中断等关键任务。用户空间则是应用程序运行的场所,应用程序在用户空间中执行各种业务逻辑。
内核空间中的代码具有最高的权限,可以直接访问硬件资源。例如,内核可以控制磁盘的读写、内存的分配和回收、网络设备的通信等。而用户空间中的代码则受到严格的限制,不能直接访问硬件资源,必须通过系统调用接口向内核请求服务。
这种分层结构的设计有很多好处。首先,它提高了系统的安全性。由于用户空间的代码不能直接访问硬件资源,因此即使应用程序出现错误,也不会对硬件造成严重的损坏。其次,它提高了系统的稳定性。如果一个应用程序崩溃,只会影响到它自己所在的用户空间,不会影响到内核空间和其他应用程序。最后,它提高了系统的可扩展性。内核空间和用户空间可以独立开发和维护,不同的开发者可以专注于不同的部分,从而提高了开发效率。
总之,Linux 系统的整体结构是一个分层的架构,从硬件到内核空间再到用户空间,每一层都有自己的职责和功能。这种结构设计提高了系统的安全性、稳定性和可扩展性,是 Linux 操作系统成功的关键之一。
定义:内核空间是计算机内存中专门分配给内核代码和关键系统任务的区域。内核空间作为 Linux 系统内存中的特定区域,为内核代码的运行和关键系统任务的执行提供了专用的场所。它是操作系统架构中的重要组成部分,承载着系统的核心功能。
作用:为内核代码和设备驱动程序等高权限任务提供了一个专用的内存区域,保证了系统的安全性,因为普通用户程序被隔离在用户空间,无法直接影响内核。内核空间的存在具有至关重要的作用。首先,它为内核代码和设备驱动程序等高权限任务提供了一个独立且受保护的内存区域。这意味着内核代码能够在一个相对安全的环境中运行,不受普通用户程序的干扰。
Linux 系统对自身进行划分,一部分核心软件独立于普通应用程序,运行在较高的特权级别上,驻留在被保护的内存空间上,拥有访问硬件设备的所有权限,这就是内核空间。而应用程序则在 “用户空间” 中运行,只能看到允许它们使用的部分系统资源,不能直接访问内核空间和硬件设备。
这种划分保证了系统的安全性。普通用户程序被隔离在用户空间,无法直接影响内核,即使用户程序出现错误或被恶意攻击,也不会对内核造成严重的损坏。例如,一个恶意的用户程序无法直接访问内核空间中的数据或代码,从而无法破坏系统的核心功能。
此外,内核空间的存在也有助于提高系统的稳定性。当一个用户程序崩溃时,它不会影响到内核空间和其他用户程序的运行。因为内核空间和用户空间是相互独立的,各自有自己的运行环境和资源管理机制。
总之,内核空间在 Linux 系统中起着关键的作用,它为内核代码和高权限任务提供了专用的内存区域,保证了系统的安全性和稳定性。
定义:用户空间是用户程序的运行空间。用户空间是指 Linux 系统中供用户程序运行的一块内存空间,与内核空间相对应。在 Linux 系统中,用户程序在用户空间中运行,包括绝大多数应用程序以及系统服务。
作用:为用户程序提供运行的环境,包括各种用户级别的库、实用工具和应用程序。用户空间与内核空间之间通过系统调用机制实现交互,用户程序通过系统调用向内核发出请求,获取系统资源、执行操作等。用户空间为用户程序提供了一个独立的运行环境,使得用户程序能够在 Linux 系统中稳定、高效地运行。在用户空间中,有各种用户级别的库、实用工具和应用程序,为用户程序的开发和运行提供了丰富的资源。
用户空间与内核空间之间通过系统调用进行通信。系统调用是用户空间程序与内核空间进行交互的方式,通过系统调用用户程序可以请求内核空间执行一些操作,如读写文件、网络通信等。在 Linux 系统中,系统调用是通过特定的接口函数来实现的,用户程序可以通过调用这些函数来与内核进行交互。
此外,用户空间还包括一些重要的组件,如动态链接库、命令解释器等。动态链接库是一些可重复使用的代码片段,用户程序可以通过链接这些库来实现共享代码,提高程序的运行效率和可复用性。命令解释器则是用户程序与操作系统进行交互的桥梁,用户可以通过命令解释器来执行各种操作系统命令。
红帽作为一家专注于 Linux 领域的软件公司,深知用户空间的重要性。他们不断优化和改进用户空间的设计,以适应不断变化的市场需求和技术发展。通过不断创新和改进,红帽的操作系统能够为用户提供更好的使用体验,进而赢得更多用户的信赖和支持。
总之,用户空间在 Linux 系统中起着至关重要的作用,它为用户程序提供了运行的环境和丰富的资源,通过系统调用与内核空间进行交互,实现了对系统资源的有效利用。随着技术的不断发展和创新,用户空间将继续发挥重要作用,为 Linux 系统的用户带来更好的体验。
系统调用:用户空间程序通过系统调用请求内核提供服务,如文件读写、进程管理、网络通信等。在 Linux 中,系统调用是用户空间程序与内核空间进行交互的一种机制。当用户程序需要执行诸如文件操作、网络通信、进程管理等不能直接由用户空间代码执行的操作时,它们会通过系统调用来请求内核代为完成这些操作。例如,应用程序要读取磁盘上的文件时,可以向内核发起 “系统调用”,告诉内核 “我要读取磁盘上的某某文件”。通过一个特殊的指令,进程从用户态进入内核态,到达内核空间后,CPU 可以执行任何指令,包括从磁盘读取数据。具体过程是先把数据读取到内核空间中,然后再把数据拷贝到用户空间并从内核态切换到用户态。此时应用程序从系统调用中返回并拿到数据,可以继续执行。常见的系统调用有 open、read、write、close、fork、exec 等。信号:内核可以通过信号机制向用户空间进程发送通知,如中断处理、进程终止等。信号在内核里的用途主要集中在通知用户程序出现重大错误,强行杀死当前进程,这是内核通过发送 SIGKILL 信号通知进程终止。信号发送必须要事先知道进程序号(PID),所以要想从内核中通过发信号的方式异步通知用户进程执行某项任务,那么必须事先知道用户进程的进程号才可以(可以让应用程序通过 oictl 函数,把自己的 PID 主动告诉驱动程序)。而一般内核运行时搜索特定进程的进程号是个费事的工作,可能要遍历整个进程控制块链表。所以用信号通知特定用户进程的方法很糟糕,一般在内核不会使用。内核中使用信号的情形只出现在通知当前进程(可以从 current 变量中方便获得 pid)做某些通用操作,如终止操作等。内存映射:用户空间程序可以通过内存映射机制访问特定的硬件资源或共享内存区域。Linux 通过 mmap 的把内核中特定部分的内存空间映射到用户级程序的内存空间去,从而提供了用户程序对内存直接访问的能力。该方式尤其适合在那些内核和用户空间需要快速大量交互数据的情况下。例如,两个不同进程 A、B 共享内存的意思是,同一块物理内存被映射到进程 A、B 各自的进程地址空间。进程 A 可以即时看到进程 B 对共享内存中数据的更新,反之亦然。采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。其他方式:如使用 API(get_user、put_user、Copy_from_user/Copy_to_user)、proc 文件系统、sysfs 文件系统 + kobject、netlink、文件、mmap 系统调用等。使用 API:get_user (x, ptr):在内核中被调用,获取用户空间指定地址的数值并保存到内核变量 x 中。put_user (x, ptr):在内核中被调用,将内核空间变量 x 的数值保存到到用户空间指定地址处。Copy_from_user /copy_to_user :主要应用于设备驱动读写函数中,通过系统调用触发。proc 文件系统:和 sysfs 文件系统类似,也可以作为内核空间和用户空间交互的手段。/proc 文件系统是一种虚拟文件系统,通过它可以作为一种 Linux 内核空间和用户空间的交互方式。与普通文件不同,这里的虚拟文件的内容都是动态创建的。sysfs 文件系统 + kobject:每个在内核中注册的 kobject 都对应着 sysfs 系统中的一个目录。可以通过读取根目录下的 sys 目录中的文件来获得相应的信息。netlink:netlink socket 提供了一组类似于 BSD 风格的 API,用于用户态和内核态的 IPC。相比于其他的用户态和内核态 IPC 机制,netlink 有几个好处:使用自定义一种协议完成数据交换,不需要添加一个文件等;可以支持多点传送;支持内核先发起会话;异步通信,支持缓存机制。文件:当处于内核空间的时候,直接操作文件,将想要传递的信息写入文件,然后用户空间可以读取这个文件便可以得到想要的数据了。mmap 系统调用:可以将内核空间的地址映射到用户空间。在以前做嵌入式的时候用到几次。一方面可以在 driver 中修改 Struct file_operations 结构中的 mmap 函数指针来重新实现一个文件对应的映射操作。另一方面,也可以直接打开 /dev/mem 文件,把物理内存中的某一页映射到进程空间中的地址上。来源:嵌入式大杂烩