当前位置: 首页 > news >正文

Linux笔记---内核态与用户态

用户态(User Mode)

  • 权限级别:较低,限制应用程序直接访问硬件或关键系统资源。

  • 适用场景:普通应用程序的运行环境。

  • 限制:无法执行特权指令(如操作I/O端口、修改内存管理单元配置等)。

内核态(Kernel Mode)

  • 权限级别:最高,允许完全访问硬件和系统资源。

  • 适用场景:操作系统内核代码的执行环境。

  • 能力:可执行所有CPU指令,管理进程、内存、设备驱动等核心功能。


1. 操作系统的运行方式

文章开头给出的概念是站在用户的角度上来讲的,对于加深我们对二者的理解来说,无异于杯水车薪。要搞清楚用户态与内核态的概念与意义,我们首先要了解操作系统是如何运行的。

如下是Linux0.11版本中,任务0(init)的主函数部分代码:

void main_rename(void)		
{...... // 这里省略前面的初始化代码
/** 注意!! 对于任何其它的任务,'pause()'将意味着我们必须等待收到一个信号才会返* 回就绪运行态,但任务0(task0)是唯一的意外情况(参见'schedule()'),因为任* 务0 在任何空闲时间里都会被激活(当没有其它任务在运行时),* 因此对于任务0'pause()'仅意味着我们返回来查看是否有其它任务可以运行,如果没* 有的话我们就回到这里,一直循环执行'pause()'。*/for(;;) pause();
}

任务0是内核启动后创建的第一个用户态任务(注意任务0不等同于操作系统本身),它的主要作用是在系统空闲时让出CPU,执行for(;;) pause();循环,通过调用pause()系统调用触发schedule(),主动让出CPU给其他任务。

pause() 的作用

  • 常规行为:对于普通任务(非任务 0),pause() 会使任务进入可中断睡眠状态,直到收到信号后被唤醒。

  • 任务 0 的特殊性
    任务 0 是内核初始化后创建的第一个任务(空闲任务),它的 pause() 行为被刻意修改为:

    • 不进入睡眠:直接调用 schedule() 主动触发调度。

    • 轮询机制:若没有其他任务可运行,调度器会再次选中任务 0,继续执行循环。

简单来说,这个死循环的作用就是:不断触发主动调度,并作为没有其他进程正在运行时的默认动作。 

注意:操作系统内核并不是以一个进程的形式存在的,也就是说内核不是依靠进程调度来获得CPU的(毕竟调度本身就是操作系统的任务)。内核是操作系统的核心部分,它是一段运行在特权级别的代码,直接与硬件交互,对系统进行全面的控制和管理。

那么,操作系统的内核在何时能得到运行呢?

在任务0作为一个进程存在的同时,操作系统的内核在不断地等待着别人请求自己的服务,而请求操作系统服务的方式就是引发中断,唤醒处于等待状态之中的操作系统。 

可以说,除了初始化以外,在没有中断的时候,操作系统的代码是不执行的;又或者说操作系统就是初始化程序 + 中断处理程序。

包括schedule()主动触发调度,本质上也是依靠触发软中断来请求操作系统的进程调度服务的。

接下来,我们再来介绍一下中断到底是什么,以及在操作系统中有哪几类中断。


2. 中断

中断(Interrupt) 是计算机系统中一种核心的事件驱动机制,允许CPU暂停当前执行的任务,转而处理优先级更高或需要即时响应的外部或内部事件。其本质是通过硬件或软件触发的信号,强制CPU切换执行流程,以保障系统的实时性、资源利用率和错误处理能力。


中断的核心特性

  1. 异步性:中断的发生与CPU当前执行的指令无关,随时可能被触发(如键盘输入、定时器到期)。
  2. 优先级分层:不同中断源(如硬件故障、磁盘I/O完成)有不同的优先级,高优先级中断可抢占低优先级中断。
  3. 上下文保存:CPU在响应中断前,会自动保存当前任务的状态(如寄存器值、程序计数器PC),以便处理完成后恢复原任务。

中断的分类

1. 硬件中断(Hardware Interrupt)

  • 定义:由外部硬件设备通过中断请求线(IRQ)主动触发。

  • 特点

    • 异步性:与CPU当前执行的指令无关。

    • 可屏蔽性:可通过中断屏蔽位(如x86的IF标志位)控制是否响应。

  • 子类型

    • 可屏蔽中断(Maskable Interrupt)
      例如:键盘输入、磁盘I/O完成、定时器中断等。
      通过中断控制器(如APIC或8259A)管理优先级。

    • 不可屏蔽中断(Non-Maskable Interrupt, NMI)
      例如:硬件故障(内存校验错误)、系统看门狗超时。
      CPU必须立即处理,无法通过软件屏蔽。

2. ​​​​​​​软件中断(Software Interrupt)

  • 定义:由程序执行特定指令(如intsyscall)主动触发。

  • 特点

    • 同步性:由程序显式调用,与指令流同步。

    • 不可屏蔽性:无法通过硬件屏蔽。

  • 子类型

    • 系统调用(System Call)
      用户程序通过int 0x80(x86)或syscall(x86_64)切换到内核态。

    • 调试中断(Breakpoint)
      例如:int 3指令触发调试器断点。

    • 异常(Exception)
      CPU执行指令时检测到错误(如除零、页错误),自动触发。

2.1 硬件中断

通过外部硬件中断,操作系统就不需要对外设进行任何周期性的检测或者轮询。

由外部设备触发的,中断系统运行流程,叫做硬件中断。

其中,中断向量表在内存中的位置与格式由 CPU 架构硬性规定,中断号实际上就是相对于中断向量表起始地址的偏移量。当CPU获得中断号之后,就可以在中断向量表当中查询到对应的中断服务程序的起始地址,进而转向执行中断服务程序。

而中断服务程序则是由操作系统负责注册到中断向量表当中的:

// 下面是异常(陷阱)中断程序初始化子程序。设置它们的中断调用门(中断向量)。
// set_trap_gate()与set_system_gate()的主要区别在于前者设置的特权级为0,后者是3。因此
// 断点陷阱中断int3、溢出中断overflow 和边界出错中断bounds 可以由任何程序产生。
// 这两个函数均是嵌入式汇编宏程序(include/asm/system.h,第36 行、39 行)。
void trap_init(void)
{int i;set_trap_gate(0,&divide_error);// 设置除操作出错的中断向量值。以下雷同。set_trap_gate(1,&debug);set_trap_gate(2,&nmi);set_system_gate(3,&int3);	/* int3-5 can be called from all */set_system_gate(4,&overflow);set_system_gate(5,&bounds);set_trap_gate(6,&invalid_op);set_trap_gate(7,&device_not_available);set_trap_gate(8,&double_fault);set_trap_gate(9,&coprocessor_segment_overrun);set_trap_gate(10,&invalid_TSS);set_trap_gate(11,&segment_not_present);set_trap_gate(12,&stack_segment);set_trap_gate(13,&general_protection);set_trap_gate(14,&page_fault);set_trap_gate(15,&reserved);set_trap_gate(16,&coprocessor_error);
// 下面将int17-48 的陷阱门先均设置为reserved,以后每个硬件初始化时会重新设置自己的陷阱门。for (i=17;i<48;i++)set_trap_gate(i,&reserved);set_trap_gate(45,&irq13);// 设置协处理器的陷阱门。outb_p(inb_p(0x21)&0xfb,0x21);// 允许主8259A 芯片的IRQ2 中断请求。outb(inb_p(0xA1)&0xdf,0xA1);// 允许从8259A 芯片的IRQ13 中断请求。set_trap_gate(39,&parallel_interrupt);// 设置并行口的陷阱门。
}初始化串行中断程序和串行接口。
void
rs_init (void)
{set_intr_gate (0x24, rs1_interrupt);	// 设置串行口1 的中断门向量(硬件IRQ4 信号)。set_intr_gate (0x23, rs2_interrupt);	// 设置串行口2 的中断门向量(硬件IRQ3 信号)。init (tty_table[1].read_q.data);	// 初始化串行口1(.data 是端口号)。init (tty_table[2].read_q.data);	// 初始化串行口2。outb (inb_p (0x21) & 0xE7, 0x21);	// 允许主8259A 芯片的IRQ3,IRQ4 中断信号请求。
}

2.2 时钟中断

我们曾经使用过sleep,alarm等函数,也了解了时间片的概念,那么操作系统是如何计时的呢?

实际上,操作系统运行的一大依据就是时钟中断,顾名思义,这种中断就是用来帮助系统计时的。

时钟中断本质上也是一种硬件中断,这种中断依靠一种特定的硬件设备来触发:时钟源。

早期的时候时钟源就是上图中的外设之一,但是鉴于其重要程度以及时钟中断的时效性,现在大多数的CPU都已经集成了中断源,使中断源能直接向CPU发送信号。

时钟源发送时钟中断的时间间隔是固定的,由其频率。时钟源的频率也叫CPU或者系统的主频

时钟中断的核心作用
  1. 系统计时
    通过全局变量jiffies记录自系统启动以来的节拍数(每个节拍对应一次时钟中断),结合HZ(每秒节拍数,默认100~1000)实现时间计算。例如,jiffies/HZ可转换为系统运行时间(秒)。

  2. 进程调度
    每次时钟中断会调用do_timer()函数更新进程时间片,触发抢占式调度。进程的时间片本质上就是一个计数器,每次时钟中断都会使其减1,直到为0。

  3. 动态定时器管理
    内核通过红黑树或时间轮管理动态定时器(timer_list结构),时钟中断触发时检查并执行到期定时器的回调函数。

  4. 资源统计
    统计进程消耗的用户态和内核态时间,更新系统负载平均值。

 2.3 软中断

系统调用本质上就是请求操作系统的服务,这就要求我们能从软件层面上触发中断,即软中断。

为了让操作系统支持进行系统调用,CPU也设计了对应的汇编指令(int 或者 syscall),可以让CPU内部触发中断逻辑。

本质上,我们使用的系统调用函数内部的逻辑就是使用int 0x80指令请求操作系统提供指定的服务,其本身并不具备任务对系统进行操作的能力。

 打开文件函数。
// 打开并有可能创建一个文件。
// 参数:filename - 文件名;flag - 文件打开标志;...
// 返回:文件描述符,若出错则置出错码,并返回-1。
int open(const char * filename, int flag, ...)
{register int res;va_list arg;// 利用va_start()宏函数,取得flag 后面参数的指针,然后调用系统中断int 0x80,功能open 进行
// 文件打开操作。
// %0 - eax(返回的描述符或出错码);%1 - eax(系统中断调用功能号__NR_open);
// %2 - ebx(文件名filename);%3 - ecx(打开文件标志flag);%4 - edx(后随参数文件属性mode)。va_start(arg,flag);res = va_arg(arg,int);_asm{mov eax,__NR_openmov ebx,filenamemov ecx,flagmov edx,resint 0x80mov res,eax}
/*	__asm__("int $0x80":"=a" (res):"0" (__NR_open),"b" (filename),"c" (flag),"d" (va_arg(arg,int)));*/
// 系统中断调用返回值大于或等于0,表示是一个文件描述符,则直接返回之。if (res>=0)return res;
// 否则说明返回值小于0,则代表一个出错码。设置该出错码并返回-1。errno = -res;return -1;
}

可以看到open函数内部是使用嵌入式汇编的方式传递参数,并使用int 0x80触发软中断。

软中断处理程序 _system_call 根据第一个参数 __NR_open 就能索引到需要调用的函数:

正真有能力对系统进行操作的,操作系统内部的系统调用函数被注册到了一张表当中,这张表就是上面汇编程序索引到所需函数的依据:

 3. 内核态与用户态的本质

经历了上面的学习,相信大家对于内核态与用户态的本质已经有了一定的想法。

即,执行用户代码时,系统就处于用户态;因中断而转向执行中断处理程序时(操作系统内核代码),系统就处于内核态。

内核态的代码是全局的,不受约束的(或者说只受到硬件约束);而用户态的代码是以进程的形式,在虚拟地址空间上运行的,受到操作系统的约束。

问题是,CPU在运行代码时,如何区分其是内核代码(特权级)还是用户代码(受限制)呢?

Linux内核态与用户态的本质区别在于CPU特权级隔离内存访问权限控制,这种设计通过硬件机制与软件协同实现系统资源的保护:

  1. 特权级划分
    x86架构通过Ring等级划分权限(0-3级),Linux仅使用Ring0(内核态)和Ring3(用户态)。关键寄存器CS(代码段寄存器)的低2位存储当前特权级(CPL):

    // 内核代码段选择子(CPL=0)
    #define __KERNEL_CS 0x10
    // 用户代码段选择子(CPL=3)
    #define __USER_CS 0x1B
    
  2. 内存隔离机制

    • 页表隔离:用户态进程只能访问用户空间页表项(VM_READ/VM_WRITE),内核态通过全局页表swapper_pg_dir直接映射物理内存。

    • 地址空间划分:x86_64下用户空间为0x0000_0000_0000_0000 - 0x0000_7FFF_FFFF_FFFF,内核空间为0xFFFF_8000_0000_0000以上。也就是说,用户代码中地址的范围与内核代码中地址的范围是互斥的,用户态下无法使用内核地址,在内核态下无法使用用户地址。

http://www.xdnf.cn/news/525691.html

相关文章:

  • Manus AI 突破多语言手写识别技术壁垒:创新架构、算法与应用解析
  • 智象科技:自动化模块驱动IT运维效能升级
  • pyspark测试样例
  • OpenCv(7.0)——银行卡号识别
  • 芯驰科技与安波福联合举办技术研讨会,深化智能汽车领域合作交流
  • Java知识点-Stream流
  • Maven配置安装
  • Unity入门学习(三)3D数学(3)之Vector3类的介绍
  • 15、Python布尔逻辑全解析:运算符优先级、短路特性与实战避坑指南
  • 使用 NGINX 的 `ngx_http_secure_link_module` 模块保护资源链接
  • 编译Qt5.15.16并启用pdf模块
  • 紫光同创FPGA实现AD9238数据采集转UDP网络传输,分享PDS工程源码和技术支持和QT上位机
  • PDF 合并测试:性能与内容完整性
  • 2025-5-19Vue3快速上手
  • 双指针算法:原理与应用详解
  • 大数据实时分析:ClickHouse、Doris、TiDB 对比分析
  • [低代码] 明道云调用本地部署 Dify 的进阶方法
  • 存储系统03——数据缓冲evBuffer
  • 不同类型桥梁的无人机检测内容及技术难度
  • 智能体应用如何重塑未来生活?全面解析技术场景与实在Agent突破
  • Oracle 的 PGA_AGGREGATE_LIMIT 参数
  • 2024年ASOC SCI2区TOP,多机制群优化算法+多风场输电线路巡检中多无人机任务分配与路径规划,深度解析+性能实测
  • 使用PowerShell备份和还原Windows环境变量
  • 第三十八节:视频处理-视频保存
  • Vue百日学习计划Day36-42天详细计划-Gemini版
  • 树莓派(Raspberry Pi)中切换为国内的软件源
  • easy-live2d v0.2.1 发布啦! 增加了语音 以及 口型同步功能,现在你的Live2D角色 可以在web里说话了!Ciallo~(∠・ω< )
  • OpenMV IDE 的图像接收缓冲区原理
  • 2025年AI与网络安全的终极博弈:冲击、重构与生存法则
  • 谷歌前CEO TED演讲解析:AI 红利的三年窗口期与行业重构