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.mzph.cn/web/80562.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring 代理与 Redis 分布式锁冲突:一次锁释放异常的分析与解决

Spring 代理与 Redis 分布式锁冲突&#xff1a;一次锁释放异常的分析与解决 Spring 代理与 Redis 分布式锁冲突&#xff1a;一次锁释放异常的分析与解决1. 问题现象与初步分析2 . 原因探究&#xff1a;代理机制对分布式锁生命周期的干扰3. 问题复现伪代码4. 解决方案&#xff1…

SQL:多列匹配(Multiple-column Matching)

目录 基础概念 应用场景详解 1. 多列等值匹配 2. 多列 IN 匹配&#xff08;集合匹配&#xff09; 3. 多列 JOIN 匹配&#xff08;复合键连接&#xff09; 4. 多列匹配 子查询 5. 多列匹配 EXISTS 6. 多列匹配 UNION&#xff08;组合数据源&#xff09; 7. 多列匹配…

基于DeepSeek的智能客服系统实践与创新

引言:AI大模型重塑客户服务新范式 近年来,AI大模型技术的突破性进展正在深刻改变传统客户服务模式。作为国内领先的AI企业,DeepSeek凭借其创新的算法架构(如MoE混合专家模型、动态学习率调度器)和极致的成本效益(仅为同类模型成本的1/20),在自然语言理解、情感分析、多…

SGLang和vllm比有什么优势?

环境&#xff1a; SGLang vllm 问题描述&#xff1a; SGLang和vllm比有什么优势&#xff1f; 解决方案&#xff1a; SGLang和vLLM都是在大语言模型&#xff08;LLM&#xff09;推理和部署领域的开源项目或框架&#xff0c;它们各自有不同的设计目标和优势。下面我综合目前…

三、Hive DDL数据库操作

在 Apache Hive 中&#xff0c;数据库 (Database)&#xff0c;有时也被称为模式 (Schema)&#xff0c;是组织和管理 表及其他对象的基本命名空间单元。熟练掌握数据库层面的数据定义语言 (DDL) 操作&#xff0c;是构建清晰、有序的 Hive 数据仓库的第一步。本篇笔记将详细梳理 …

Redis(2):Redis + Lua为什么可以实现原子性

Redis 作为一款高性能的键值对存储数据库&#xff0c;与 Lua 脚本相结合&#xff0c;为实现原子性操作提供了强大的解决方案&#xff0c;本文将深入探讨 Redis Lua 实现原子性的相关知识 原子性概念的厘清 在探讨 Redis Lua 的原子性之前&#xff0c;我们需要明确原子性的概念…

科普:极简的AI乱战江湖

本文无图。 大模型 ‌2022年2月&#xff0c;‌文生图应用的鼻祖Midjourney上线。 ‌2022年8月&#xff0c;‌开源版的Midjourney&#xff0c;也就是Stable Diffusion上线。 2022年11月30日‌&#xff0c;OpenAI正式发布ChatGPT-3.5。 此后&#xff0c;不断有【大模型】面世&…

CSS- 4.5 css + div 布局 简易网易云音乐 官网布置实例

本系列可作为前端学习系列的笔记&#xff0c;代码的运行环境是在HBuilder中&#xff0c;小编会将代码复制下来&#xff0c;大家复制下来就可以练习了&#xff0c;方便大家学习。 HTML系列文章 已经收录在前端专栏&#xff0c;有需要的宝宝们可以点击前端专栏查看&#xff01; 点…

【滑动窗口】LeetCode 1004题解 | 最大连续1的个数 Ⅲ

最大连续1的个数 Ⅲ 一、题目链接二、题目三、题目解析四、算法原理解法一&#xff1a;暴力枚举 zero计数器解法二&#xff1a;滑动窗口 五、编写代码六、时空复杂度 一、题目链接 最大连续1的个数 Ⅲ 二、题目 三、题目解析 注意题目中说的是最多k次&#xff0c;在一个数组…

PyTorch音频处理技术及应用研究:从特征提取到相似度分析

文章目录 音频处理技术及应用音频处理技术音视频摘要技术音频识别及应用 梅尔频率倒谱系数音频特征尔频率倒谱系数简介及参数提取过程音频处理快速傅里叶变换(FFT)能量谱处理离散余弦转换 练习案例&#xff1a;音频建模加载音频数据源波形变换的类型绘制波形频谱图波形Mu-Law 编…

鸿蒙OSUniApp 实现的语音输入与语音识别功能#三方框架 #Uniapp

UniApp 实现的语音输入与语音识别功能 最近在开发跨平台应用时&#xff0c;客户要求添加语音输入功能以提升用户体验。经过一番调研和实践&#xff0c;我成功在UniApp项目中实现了语音输入与识别功能&#xff0c;现将过程和方法分享出来&#xff0c;希望对有类似需求的开发者有…

2025年卫星遥感行业最新发展趋势深度分析

一、国内发展趋势&#xff1a;政策引领与技术突破双轮驱动 &#xff08;一&#xff09;政策体系持续完善&#xff0c;顶层设计深化行业发展 国家级战略与标准体系构建 中国政府将卫星遥感产业纳入“十四五”规划核心战略&#xff0c;明确构建“通导遥”一体化空间基础设施。20…

SIP协议栈--osip源码梳理

文章目录 osiposip主体结构体code main函数 状态机转化结构体code状态转换 sip事务结构体code osip_dialog结构体code 创建并发送200 OK响应 osip_message结构体code osip_eventcode 打印接收到的SIP消息 osip OSIP&#xff08;Open Source Implementation of SIP&#xff09;…

Linux之Yum源与Nginx服务篇

1.Yum源知识理论总结概括 Yum源概述 Yum 源 即软件仓库的标识&#xff0c;里面承载着软件包集合 Yum源组成 包含模块 【OS】、【everything】、【EPOL】、【debuginfo】、【source】、【update-source】 【os】:简称operator system 它内部包含操作系统的核心组件&#x…

从单体架构到微服务:架构演进之路

引言&#xff1a;当“大货车”遇上“集装箱运输” 在软件开发领域&#xff0c;单体架构曾像一辆载满货物的大货车&#xff0c;将所有功能打包在一个应用中。但随着业务复杂度飙升&#xff0c;这辆“大货车”逐渐陷入泥潭&#xff1a;启动慢如蜗牛、故障波及全局、升级如履薄冰……

AM32电调学习解读九:ESC上电启动关闭全流程波形分析

这是第九篇&#xff0c;前面的文章把各个模块的实现都介绍了一轮&#xff0c;本章是从运行的角度结合波形图&#xff0c;把整个流程走一遍。 先看下一运行的配置&#xff0c;我把一些配置关闭了&#xff0c;这样跑起来会好分析一些&#xff0c;不同配置跑起来效果会有差异。使用…

全球宠物经济新周期下的亚马逊跨境采购策略革新——宠物用品赛道成本优化三维路径

在全球"孤独经济"与"银发经济"双轮驱动下&#xff0c;宠物用品市场正经历结构性增长。Euromonitor数据显示&#xff0c;2023年全球市场规模突破1520亿美元&#xff0c;其中中国供应链贡献度达38%&#xff0c;跨境电商出口增速连续三年超25%。在亚马逊流量红…

reshape/view/permute的原理

在pytorch中&#xff0c;Tensor的存储是行主序的&#xff0c;也就是意味着最后一个维度的元素的存储时连续的&#xff0c;reshape和view并不改变元素存储的内存&#xff0c;仅仅改变访问的间隔&#xff0c;下面举例说明&#xff1b; 比如一个23的Tensor在内存中的存储是连续的&…

upload-labs靶场通关详解:第11关

一、分析源代码 $is_upload false; $msg null; if (isset($_POST[submit])) {if (file_exists(UPLOAD_PATH)) {$deny_ext array("php","php5","php4","php3","php2","html","htm","phtml"…

L1-7 最短字母串【保姆级详细讲解】

请你设计一个程序&#xff0c;该程序接受起始字母和目标字母作为输入&#xff0c;通过在字母表中向前或向后移动来计算两个给定字母之间的最短路径。然后&#xff0c;程序会沿着最短路径打印出从起始字母到目标字母的所有字母。例如&#xff0c;如果输入“c”和“k”作为起始字…