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

linux0.11内核源码修仙传第十二章——内核态到用户态

🚀 前言

    操作系统区分了内核态和用户态确保安全,其实这是两个问题,一个是如何从内核态切换到用户态,另一个问题是如何确保当前进程不会逃出用户态。本文内容对应于书中的第22回。希望各位给个三连,拜托啦,这对我真的很重要!!!

目录

  • 🚀 前言
  • 🏆特权级
  • 🏆特权级转换
    • 📃特权级变化
    • 📃特权级以外的改变
    • 📃让进程无法跳出用户态
  • 🎯总结
  • 📖参考资料

🏆特权级

    特权级说明白点就是给不同的人不同的权限。你是内核态,是公司老板,那就有权利过问任何人;你是用户态,是部门经理,就只能管自己部门的人。那么在操作系统中是如何体现的呢?

    首先回顾一下段选择子的结构,不记得段选择子可以看这篇博客:linux0.11内核源码修仙传第二章——setup.s。简单来讲,段选择子就三个东西,特权级,描述符索引,TI(描述索引符从GDT取还是LDT取)

在这里插入图片描述
    先来看CPL/RPL,为什么是两个呢,这是因为一个是标记自己,另一个是寻找别人。什么意思?举个例子:还是公司里的例子,我是xx部门经理,那我的工牌上就是xx经理,这叫做CPL,标榜自己当前所处特权级;现在其他部门的员工需要我开放某个文档的权限,那他就找xx经理,这叫做RPL。看吧,自己并没有变,只是使用主体变了,CPL是告诉自己我是什么特权级,RPL是别人来找我是什么特权级,也就是请求特权级。CPL = 0 是内核态,CPL = 3 是用户态

    假设要跳转到另一处执行,那汇编的话就是jmpcall和中断。以jmp举例:

短跳转:也就是段内跳转,不涉及段的变化,也就没有特权级检查
长跳转:也就是段间跳转,jmp yyy:xxx,这里的 yyy 是另一个要跳转的段选择子结构

    直接看长跳转,这里面yyy是一个段选择子结构,那既然是段选择子,那就有和上面一样的结构,注意!此处是别人找xx经理,所以此时段选择子最后两位是RPL,请求特权级!同时,CPU 会拿这个段选择子去全局描述符表中寻找段描述符,从中找到段基址。

    还记得段描述符吗,详细依旧可以查看这篇博客:linux0.11内核源码修仙传第二章——setup.s。简单来说这个描述符里面存放了这块地址属于什么段(代码段还是数据段)同时还有基地址等等信息,结构如下:

在这里插入图片描述
    注意看,这里面在上方的13~14位有个DPL,这表示目标代码段特权级,也就是 yyy 所对应的特权级,用下面的图更好理解:

在这里插入图片描述
    搞了这么半天,其实就是CPLDPL比较,CPL必须等于DPL,才会跳转成功。也就是说,当前代码所处段的特权级,必须要等于要跳转过去的代码所处的段的特权级,那就只能用户态往用户态跳,内核态往内核态跳,这样就防止了处于用户态的程序,跳转到内核态的代码段中做坏事。

    在访问内存数据时也会有数据段的特权级检查,其本质还是上面这三个进行比较。

CPL = 0(内核态):此时所有特权级是的数据段都可被访问;
CPL = 1:只有在特权级1到3的数据段可被访问;
CPL = 3(用户态):只有处于特权级3的数据段可被访问

    最终的效果就是,处于内核态的代码可以访问任何特权级的数据段,处于用户态的代码则只可以访问用户态的数据段,这也就实现了内存数据读写的保护。

    好的,来总结一下这一小节,就是代码跳转只能同特权级,数据访问只能高特权级访问低特权级

🏆特权级转换

📃特权级变化

    这里先讲结论,底层是采用中断返回实现的。很神奇吧?具体怎么做的,首先从main函数里面开始:

void main(void)	
{	
	···
	move_to_user_mode();
	···
}

    main函数里面就这一句话,不清不楚的,我们接着往下挖:

#define move_to_user_mode() \
__asm__ ("movl %%esp,%%eax\n\t" \
	"pushl $0x17\n\t" \		// 给ss赋值
	"pushl %%eax\n\t" \
	"pushfl\n\t" \
	"pushl $0x0f\n\t" \		// 给cs赋值
	"pushl $1f\n\t" \
	"iret\n" \				// 中断返回
	"1:\tmovl $0x17,%%eax\n\t" \
	"movw %%ax,%%ds\n\t" \
	"movw %%ax,%%es\n\t" \
	"movw %%ax,%%fs\n\t" \
	"movw %%ax,%%gs" \
	:::"ax")

    从上面代码和注释可以看到,这个宏函数是压了五个值进栈,然后中断返回了。具体啥子意思喃,我们先来看下面这幅图:

在这里插入图片描述
    这幅图是中断发生时,CPU自动帮我们做的压栈操作,注意这里,是自动!!!也就是不需要我们写出来的。由于是从内核态到用户态,发生了特权级变化,因此除了最后的错误码,刚好有五个需要压栈。当然了,这里是正常发生中断。但事实是,我们上面代码并没有任何中断发生,就是从main函数丝滑的调到这个汇编来了,我们之前开的中断里面也没有对应的中断。

    这就得说说linus本人天才的地方了。Intel设计的CPU中,中断和中断返回可以不成套出现。what???这是不是很反直觉?但事实正是如此,linus祖师爷也正是利用这一特性,在最开始往栈里面压入了对应的五个值,模拟已经发生了中断,这样在中断返回后,CPU 又会帮我们把压栈的这些值返序赋值给响应的寄存器,即:SS、ESP、EFLAGS、CS、EIP 这几个寄存器,这就感觉像是正确返回了一样,让其误以为这是通过中断进来的

    解释一下这五个寄存器:

CS 和 EIP 就表示中断发生前代码所处的位置,这样中断返回后好继续去那里执行。
SS 和 ESP 表示中断发生前的栈的位置,这样中断返回后才好恢复原来的栈。

    其中,特权级的转换,就体现在 CS 和 SS 寄存器的值里。最后这里可以抽象思维一下,这里其实就是假设我们中断前本来就是在用户态,中断进来用户态,中断后又返回原来的状态,就和下面这个图一样:

在这里插入图片描述
    来详细看一下CS段和SS段的值:

push 00000017h
push 0000000fh

    这里看个cs段举例,先把0x0f化为二进制,再配合段选择子来看:

0000000000001111

在这里插入图片描述

    最后两位是 11 表示特权级3(CPL=3),这正是代表了用户态。所以在经过iretd之后,CS寄存器的值就变成了这个,也就是成了用户态特权级。

📃特权级以外的改变

    还是看上面CS寄存器的值,这次看除了最后两位的其他位。其实除了最后两位,就是看看TI以及描述符索引了。

    倒数第3位表示TI位,这一位的含义是,前面的描述符索引是从GDT还是从LDT中取。这里是1,表示从LDT,也就是局部描述符中取。关于局部描述符可以参看这篇博客:linux0.11内核源码修仙传第十章——进程调度始化

    这里回顾一下GDT与LDT中的分布情况,如下所示:

在这里插入图片描述
    描述符索引是1,且从LDT中选取,则代表是代码段。如上面的红框框所示。

    继续回到上面的汇编代码,也就是move_to_user_mode这个宏函数。返回后的EIP 是标号1的位置。也就是返回后会执行标签1后面的内容,但是此时已经是用户态了。

···
"pushl $1f\n\t" \
"iret\n" \				// 中断返回
"1:\tmovl $0x17,%%eax\n\t" \
"movw %%ax,%%ds\n\t" \
"movw %%ax,%%es\n\t" \
"movw %%ax,%%fs\n\t" \
"movw %%ax,%%gs" \
:::"ax")

    这后面的内容是将ds,es,fs,gs寄存器都设置为0x17。既然是设置这些段寄存器,那还是涉及到段寄存器,0x17的最后两位也是11,表明对应的CPL也修改为了用户态。

📃让进程无法跳出用户态

    具体如何让进程无法跳出来,已经在上一大节:🏆特权级 中描述过了。其实就是进行长跳转的时候要检查CPL、RPL、DPL三者的关系,代码只有同级才能发生跳转。

    经过 move_to_user_mode 这行代码,当前就已经从内核态变化到用户态了。一旦转变为了用户态,那么之后的代码将一直处于用户态的模式,除非发生了中断,比如用户发出了系统调用的中断指令,那么此时将会从用户态陷入内核态,不过当中断处理程序执行完之后,又会通过中断返回指令从内核态回到用户态。整个变化过程如下所示:

在这里插入图片描述

🎯总结

    来回顾一下本文重要的一些点:1. 首先是关于特权级,其实本质就是比较CPL,RPL与DPL之间的关系。2. 特权级的转换是通过中断返回实现的,这个中断返回不需要中断调用,只需要模拟中断发生,往栈里面压5个值即可。3. 进入用户态后就会一直在用户态,只有中断才可以进入内核态,而后又会回到用户态。


📖参考资料

[1] linux源码趣读
[2] 一个64位操作系统的设计与实现
[3] 操作系统学习(九) 、访问数据段时的特权级检查

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.dtcms.com/a/102785.html

相关文章:

  • vue3 根据城市名称计算城市之间的距离
  • 【系统性偏见:AI照出的文明暗伤与生存悖论】
  • 10种涨点即插即用模块 特征处理合集篇(六)!!!(附原文地址+论文+代码)
  • Using SAP an introduction for beginners and business users
  • 磁盘结构损坏防护与应对全解析:从风险预警到数据拯救的关键策略
  • git push origin masterremote: [session-bd46a49f] The token username invalid
  • kaggle共享单车预测
  • 当 EcuBus-Pro + UTA0401 遇上 NSUC1500
  • C++/数据结构:哈希表知识点
  • Redis BitMap 实现签到及连续签到统计
  • Dart之库和可见性和异步支持、生成器、可调用类与Isolates、Typedefs和元数据
  • 微前端 - 以无界为例
  • C语言库zlog日志库工具
  • 23种设计模式-结构型模式-组合
  • RabbitMQ消息队列面试题集合
  • 如何使用 FastAPI 构建 MCP 服务器
  • 【电动汽车再生制动控制技术(万字长文,图文并茂)】
  • 全国职业院校技能大赛 网络建设与运维样题解析
  • 11-SpringBoot3入门-整合aop
  • 分布式计算Ray框架面试题及参考答案
  • 喜讯 | 耘瞳科技视觉检测与测量装备荣膺“2024机器视觉创新产品TOP10”
  • SerDes(Serializer/Deserializer)详解
  • SOME/IP-SD -- 协议英文原文讲解10
  • 广度优先搜索(BFS)与深度优先搜索(DFS)解析
  • 通义万相2.1 你的视频创作之路
  • Web-ssrfme:redis 未授权访问攻击
  • 【go】数组与切片
  • GoLand 2024.3 中文 GO语言开发工具
  • 什么是架构,以及当前市面主流架构类型有哪些?
  • 智能车载终端测试:慧通测控多参数综合测试定制化方案