【Linux 源码】内核态到用户态

文章目录

    • 1. 由来
    • 2. 流程图
    • 3. 中断
      • 3.1 概念
      • 3.2 8259A芯片
      • 3.4 中断时的栈处理
        • 3.4.1 相同特权级
        • 3.4.2 不同特权级
      • 3.5 中断流程
      • 3.6 定位中断程序
      • 3.7 中断流程步骤总结
    • 4. 源码
      • 4.1 `move_to_user_mode`
      • 4.2 0号进程
      • 4.3 `TSS`和`LDT`在`GDT`表排布
      • 4.4 ldt中的0x17栈段
    • 5. 总结

1. 由来

​ 首先需要强调下,博主这边涉及的linux内核源码是0.11版本,因为这是linusintel 开发手册最纯粹的编写,也就可以理解为照着intel 开发手册写的,后面的小节会体现出来。intel 开发手册是对CPU的一些规则制定,所以读者们也不要心生畏惧,就比如你要写一个JAVA程序,是不是首先要熟悉它的语法规则,然后通过这些规则进行编写自己目标程序。而intel 开发手册就类似于这个概念,而linux实现的源码就是在此基础上进行的程序实现。(PS: intel规定的ISA规则 -》机器码 -》 汇编语言 -》C语言)

​ 在聊内核态和用户态之前,我觉得读者对这2个概念都会恐慌,退缩。即使知道它们的概念,也不知道是个什么,也就知道内核态是OS,用户态是应用程序,而不理解为什么需要这么切换,这么切换的目的又是什么,不急,我这边逐一讲解。这里会涉及大篇幅的前置知识介绍,这些知识都通了,读者们也就顺理成章理解了内核态和用户态的神秘面纱的背后是什么。

​ 接下来我会介绍下他们的由来,对于CPU来说,所有程序都是二进制的各种组合,而CPU本身通过自己指定的二进制规则,来识别程序的二进制,达到程序的执行。操作系统本身也是一个程序,应用程序也是一个程序,所以对于CPU来说,都是一视同仁的。而CPU本身是来驱动硬件,并不能让所有程序都能根据CPU的硬件控制规则,来控制硬件,这会造成极度的不安全。所以CPU产生了特权级的概念,也可以类理解为我们日常开发的权限系统,特权级的小知识可以看我之前写的博客【intel 开发手册】特权级。当需要特权级后进行甄别不同特权级的代码,是否可以运行。每个特权级都有自己的栈空间,来存储自己程序信息。为什么每个特权级都有自己的栈,用来隔阂不同程序间的敏感数据。那这时就会引申出OS为最高特权级,会有自己的栈,那么应用程序为最低特权级,也有自己的栈。那么比如现在我执行的是内核OS的代码,那么如何切换到应用程序的栈空间呢?这里会涉及到CPU给定的规则中断规则,来执行切换,来完成内核态到用户态切换的效果。

​ 以上就是本文章的核心,整个文章也会因此展开详细的解说。

2. 流程图

3. 中断

3.1 概念

​ 什么是中断?为什么需要这个概念?

​ 拿一个生活的例子举例,比如你是一个JAVA CODER正在编写需求,这时一个电话打来了,你是否需要停下手头的代码任务,来看谁打来的电话,并接听。那如果你没有这个中断动作,一直执行程序编写,那你的电话岂不是是会被打爆。CPU也同样如此,比如CPU执行一个程序时,通过中断检测周期来检查是否发生中断,有中断后,执行中断程序,执行完后,在继续执程序,也就是所谓的不同任务切换。那说到这,中断的背景和概念也是出来了,那我通过专业点的术语来描绘下这个过程吧。

​ 中断是通过8259A芯片阵脚置为高电频,并对应的中断程序位置为高电频,当中断检测周期检测到后,会把CPUINTR阵脚置为高电频,通过后续的检测,来执行中断程序,执行完后通过指定的中断返回指令iret来进行返回,让CPU继续执行代码。

​ 这里博主主要讲述下中断流程,而流程的原理来源于intel 开发手册,我们通过图解来更深入理解下流程

3.2 8259A芯片

​ 中断的原理,就是围绕8259A芯片展开和软件中断,这里博主主要讲下硬件中断,这个芯片名称大家看到后不要退缩,博主当时学的时候,就担心因为有很多未知的芯片都需要去记,就很苦恼,也会产生畏惧,但发现这些都是无用的畏惧。因为当你学一个,才用一个,未来是未来学,不要把未来的苦恼搬到现在,让自己临阵退缩。而是研究一个东西,就研究到底,形成自己的知识树。

​ 该图取自《Linux 内核完全注释》,8259A芯片左面有一列的中断源,芯片INT引脚连接着CPU的引脚INTR。一共2个8259A,读者会好奇为什么是2个,不是更多,因为当有1个8259A芯片时,中断源只能有8个,不够用,所以级联的方式级联了另外一个8259A

PC机通过8259A芯片的设置,周期性发出中断信号。也就是中断检测周期,CPU通过接收到中断检测周期后,检查INTR是否被8259A置为高电频,然后通过数据总线解码后获取中断向量,在通过中断向量执行对应的中断程序。

在这里插入图片描述

3.4 中断时的栈处理

​ 开头说了不同特权级,有着不同的栈,那么中断也是同理,中断属于内核代码,拥有最高权限,那如果执行的代码权限相同,会在当前执行成的栈上进行,不同的特权级,会产生切换。

3.4.1 相同特权级

​ 当不切换时,在原先的栈继续执行,需要保存原先执行代码的地址信息,错误码,状态标志位。为什么需要这些信息?首先但你执行完中断代码,需要找到原先的代码继续执行,产生中断时并且告诉CPU,我在中断中,对应的中断信息是什么,也就是错误码。

​ 状态标志位用来记录当前执行的中断程序是否可以再次嵌套中断,为什么中断后还可以中断呢?因为中断有2种类型,一种是陷阱门,另一种是中断门,如果是中断门,就会清除状态标志位IF,来告诉CPU不允许被中断(但有时会有不可屏蔽中断,如切断电源)。

🐯volume 1 - 6.5 INTERRUPTS AND EXCEPTIONS:栈未切换的步骤流程(特权级相同)

在这里插入图片描述

当处理完后,需要返回到原先的执行代码

🐯volume 1 - 6.5 INTERRUPTS AND EXCEPTIONS:同一个特权级,栈未切换,返回的处理

来自中断/异常处理器返回,被IRET指令发起。除了它会存储中断程序的eflags内容,IRET指令和far ret指令类似。在中断处理程序的同个特权级,当执行来自中断/异常处理器的返回时,处理器会做如下动作:

  1. 将CS和EIP寄存器恢复到中断或异常之前的值。
  2. 恢复eflags寄存器。
  3. 适当的增加栈针偏移量。
  4. 恢复中断程序的执行。
3.4.2 不同特权级

​ 如果产生了切换,就需要额外存储原先的栈的地址信息。

🐯volume 1 - 6.5 INTERRUPTS AND EXCEPTIONS:栈切换的步骤流程(特权级不同)在这里插入图片描述

当处理完后,需要返回到原先的执行代码

🐯volume 1 - 6.5 INTERRUPTS AND EXCEPTIONS:不同特权级,栈切换,返回的处理

执行一个特权级别不同的返回,处理器会有如下动作:

  1. 执行权限校验。
  2. 将CS和EIP寄存器恢复到中断或异常之前的值。
  3. 恢复eflags寄存器。
  4. 将SS和ESP寄存器恢复到中断或异常之前的值,从而导致堆栈切换回被中断过程的堆栈。
  5. 恢复被中断的程序。

3.5 中断流程

​ 那既然发生了中断,那intel 开发手册是如何规定的中断运转流程呢?看下下面这张图。

在这里插入图片描述

CPU首先正在运行当前程序,中断检测周期定期通知,CPU当执行完一个原子性的操作后,然后根据中断检测周期来的提醒,检测CPUINTR阵脚,和IF标志位(后面会说),进行决定是否运行中断程序,当产生中断后,会有中断程序被执行,那这些程序是如何产生的?又怎么找到的呢?

intel 开发手册规定了18个预定义的中断和异常,224用户定义的中断,这些预定义的程序的入口在IDT表里里。在IDT里的每个中断和异常被一个向量数字所标识。

目录位置:🐯volume 1 - 6.5 INTERRUPTS AND EXCEPTIONS

在这里插入图片描述

​ 有CPUOS自己定义的中断程序,通过IDT来寻找对应的程序,简简单单的一句话,解决了2个疑问。那下来我来介绍下如何执行中断流程代码的步骤

3.6 定位中断程序

​ 这里会涉及很多概念,如GDTSegmenet SelectorSegment Descriptor概念,读者也不用慌,博主也写了一篇文章专门对这些概念的介绍【intel 开发手册】GDT相关概念。建议读者先大致了解下,然后继续这一节的流程。

​ 通过intel 开发手册提供的图直接讲解,IDT是中断描述符表,存储一些信息,那如何找到这张表的地址,那就需要一个寄存器,也就是IDTR中断寄存器来存储这个表地址和相关信息。用通俗易懂的话讲,你要存数据,就需要一个表,而表是存储在硬盘上的,那如何找到这张表,就需要知道这个表在硬盘存在哪。

​ 所以下图也是这个目的,通过IDTR存储着IDT表的地址,IDT表存储着中断门程序的地址和特权级等一些信息。、

​ 那就梳理一下流程:通过IDTR寄存器里的IDT Base Addr定位IDT表的首地址,IDT limit限制表的大小。

目录:🐯volume 3 - 7.1 Relationship of the IDTR and IDT

​ 既然找到中断程序门描述符了,那就需要寻找对应的中断的程序了,那就会有一个疑问,为什么不直接存程序呢,而是要多一个步骤,因为涉及中断权限校验,看你是否有权限。

​ 通过之前说的,8259A芯片会有一个中断源的引脚产生高电频,CPU通过数据总线解码(也有int中断指令),得到这个高电频的位置,也就是中断向量,那么我们就可以通过IDT和这个Interrupt Vector来定位中断程序在IDT表中的位置信息,定位到后,开始权限校验,来获取段选择子,和offset从而定位GDT表里的Code Segment的位置,从而获取到最终的中断代码

目录:🐯volume 3 - 7.3 Interrupt Procedure Call

3.7 中断流程步骤总结

来梳理下流程:

  1. 首先8259A芯片会产生一个中断标志,然后阵脚INT置为高电频,对应的中断向量位置置为高电频。或者int指令进行中断
  2. 中断检测周期发现后,将CPU的INTR置为1,当CPU执行完最后一个指令(IF,ID,EXEC,MEM,WB)时,开始判断是否可以中断
  3. 检查eflagsif标志位,是否是被清除的,是的话,不能中断
  4. CPU和8259A芯片连接的数据总线,解码获取到中断向量(与就是中断的程序位置)
  5. 获取到后,和IDTR(base addr + offset)找到对应IDT表,专表专用,通过中断向量找到对应的IDT描述符
  6. 进行特权级检查,是否发生栈切换,(CPL和中断程序的dpl判断),然后压入对应的SS,SP,ELFAGS,CS,IP,ERROR CODE等信息
  7. 通过中断描述符段选择子找到对应的GDT里对应的各种段信息
  8. 然后开始执行code segment中断程序
  9. 执行后,通过iret,通过压入栈的SS,SP信息返回压入的栈地址

4. 源码

​ 需要再次声明,linux使用的是0.11版本源码,如果小伙伴不知道怎么阅读源码,可以看Source Insight 读取源码使用入门。

linux的内核态到用户态的切换,就是利用中断的特权级切换时,产生栈切换,把用户态的代码段信息压入栈中,当iret返回时,就顺利的切换到了指定的用户程序代码。下面进行下讲解。

​ 在入口函数,就有内核态切换用户态的源码,那我们先从主函数的源码开始

4.1 move_to_user_mode

这个代码在main.c里,这是linux的执行入口。博主把不相关代码省略了,这样看着也比较清爽。

我们这边通过主函数的执行代码,可以看到move_to_user_mode,从代码名称也可看出,是切换为用户模式,那我们看下这个api的详细执行代码。

void main(void)
{			// 前面代码省略....sched_init();					// 初始化0号进程的tss,ldt描述符信息// 中间代码省略...move_to_user_mode();			// SS,SP,EFLAGS,SS,SP初始化,并iret切换栈,cpl=3// 切换到了cpl=3,就不能直接调用内核函数,需要系统调用(可类比B/S架构,规定协议,然后调用接口,接受返回结果)// 需要找到对应的TSS,LDT来开始执行代码// 而他俩的位置:NULL,CS(内核代码段),DS(内核数据段),NULL,TSS0,LDT0(这个排列规则在sched.h写了FIRST_TSS_ENTRY的注释,给出)if (!fork()) {		/* we count on this going ok */init();}for(;;) pause();
}

它的实现是内联汇编,汇编的语法比较简单,这里linux的实现是c,用的gnuc套件,所以是AT&T的语法,是从左到右逻辑。由于它是使用intel提供的中断机制,那么就需要遵守iret返回时的数据信息,也就是上面[3.4.2 不同特权级](#3.4.2 不同特权级)的讲述

大家也可以通过代码和对照图能看出,它压入的顺序,和上面讲述的图片压入的信息是一样的

ss:栈段选择子

esp:栈顶指针

eflags:状态

cs:状态寄存器段选择子

eip:相对代码段位置的下一个偏移指针

#define move_to_user_mode() 
__asm__ (movl %%esp,%%eaxpushl $0x17                  // sspushl %%eax                  // esppushfl                       // eflagspushl $0x0f                  // cspushl $1f                    // eipiret                         // Interrupt return 1:      movl $0x17,%%eax   // movw %%ax,%%ds    // 数据段movw %%ax,%%es    // 扩展段movw %%ax,%%fs    // fsmovw %%ax,%%gs    // gs::: // 无输出,无输入"ax" // 保存到eax里
)

那为什么ss0x17,这个也是对着手册中的段选择子的规则写的,那我们来看下

0x17 -> 0000_0000_0001_0111

再通过图中的对比,可以得出如下信息

  • rpl = 3,
  • ldt
  • ldt表里的位置index=2

只不过它在ldt表项放在第2个位置索引,后面讲INIT_TASK时,会讲ldt表项的排布

在这里插入图片描述

当它执行到iret时,依次弹出,当弹出1f时,会执行下面的1:后面的代码,进行数据段等的初始化。然后弹出对应的代码段,指定的用户态的栈段,这样就顺利切换到了用户态的代码了。

但如果指明是ldt类型的段选择子,会存在ldt表里,那对应ldt表里的下表为什么是2呢?在这节,通过上面的铺垫就已经讲完了内核态到用户态的切换,下面的几节是深入一下相关知识,读者可自行选择。

4.2 0号进程

linux执行进程,它的进程管理是一个数组,更多进程的详解,可看我之前的博客【Linux 源码】进程。那肯定会有一个内核进程先执行,才能做后续的操作,所以我们看下这个0号进程是怎么初始化的ldt就可以了。那我们就在source insight找下task_struct,可以看到它的0号索引存放的是一个task_struct的地址指针,也就是第一个进程。

在这里插入图片描述

🐯那我们看看这里面的ini_task都做了什么

当我们看它的实现,其实是一个union的结构体,那我们先看下这个结构体定义了什么,它定义了2个属性,union会把这个结构体里占用内存最大的空间,作为结构体的总大小,所以也就是4kb的栈大小,而在下面进行的INI_TASK的内容,就放入到了这个栈中。我也通过图片展示了下

>

#define PAGE_SIZE 4096union task_union {struct task_struct task;char stack[PAGE_SIZE];
};static union task_union init_task = {INIT_TASK,};

🐯既然知道了是存储了一个栈上的结构体,那这个INIT_TASK里面的实现又是什么呢,是怎么定义task_struct各种属性呢,那我们点进去看下

点进去是各种眼花缭乱的数据,但博主进行一一标注,因为它是相当于给task_struct的各个属性赋值,就包含上面我们提及到的ldt,可进行观看下,博主也把task_struct对应的属性代码也放置过来

/** Entry into gdt where to find first TSS. 0-nul, 1-cs, 2-ds, 3-syscall* 4-TSS0, 5-LDT0, 6-TSS1 etc ...*/
#define FIRST_TSS_ENTRY 4
// 5
#define FIRST_LDT_ENTRY (FIRST_TSS_ENTRY+1)
// 0 + 5 * 2^3 = 40 -> ldt0的首地址
#define _LDT(n) ((((unsigned long) n)<<4)+(FIRST_LDT_ENTRY<<3))/**  INIT_TASK is used to set up the first task table, touch at* your own risk!. Base=0, limit=0x9ffff (=640kB)*/
#define INIT_TASK 
/* state etc */	{ 0,			// long state15,			// long counter15, 		    // long priority/* signals */		0,			 // long signal{{},},		// struct sigaction sigaction[32];0, 			// long blocked /* bitmap of masked signals *//* ec,brk... */		0,			 // exit_code0,0,0,0,0, 	// unsigned long start_code,end_code,end_data,brk,start_stack;/* pid etc.. */		0,-1,0,0,0,   // long pid,father,pgrp,session,leader;/* uid etc */		0,0,0,		 // unsigned short uid,euid,suid;0,0,0, 		// unsigned short gid,egid,sgid;/* alarm */	    	0,			 // long alarm;0,0,0,0,0,    // long utime,stime,cutime,cstime,start_time;/* math */	    	0, 			 // unsigned short used_math;/* fs info */		-1,			 // int tty;		/* -1 if no tty, so it must be signed */0022,		// unsigned short umaskNULL,		// struct m_inode * pwd;NULL,		// struct m_inode * root;NULL,		// struct m_inode * executable;0, 			// unsigned long close_on_exec;/* filp */	    	{NULL,}, 	 // struct file * filp[NR_OPEN];// struct desc_struct ldt[3];{ {0,0}, 
/* ldt */	      		 {0x9f,0xc0fa00}, {0x9f,0xc0f200}, }, 
/*tss*/	        	{0,							// back_link;	/* 16 high bits zero */PAGE_SIZE+(long)&init_task,	// esp0;0x10,						// ss0;		/* 16 high bits zero */0,							// esp1;0,							// ss1;		/* 16 high bits zero */0,							// esp2;0,							// ss2;		/* 16 high bits zero */(long)&pg_dir,				 // cr3;		0,							// eip;					0,							// eflags;				0,0,0,0,					 // eax,ecx,edx,ebx;							0,							// esp;0,                            // ebp;0,                            // esi;0,					        // edi;0x17,						// es;		/* 16 high bits zero */0x17,						// cs;		/* 16 high bits zero */0x17,						// ss;		/* 16 high bits zero */0x17,						// ds;		/* 16 high bits zero */0x17,						// fs;		/* 16 high bits zero */0x17, 						// gs;		/* 16 high bits zero */_LDT(0),					// ldt;		/* 16 high bits zero */0x80000000, 				 // trace_bitmap;	/* bits: trace 0, bitmap 16-31 */{} 							// i387_struct i387;}, 
}
struct task_struct {
/* these are hardcoded - don't touch */long state;	/* -1 unrunnable, 0 runnable, >0 stopped */long counter;long priority;long signal;struct sigaction sigaction[32];long blocked;	/* bitmap of masked signals */
/* various fields */int exit_code;unsigned long start_code,end_code,end_data,brk,start_stack;long pid,father,pgrp,session,leader;unsigned short uid,euid,suid;unsigned short gid,egid,sgid;long alarm;long utime,stime,cutime,cstime,start_time;unsigned short used_math;
/* file system info */int tty;		/* -1 if no tty, so it must be signed */unsigned short umask;struct m_inode * pwd;struct m_inode * root;struct m_inode * executable;unsigned long close_on_exec;struct file * filp[NR_OPEN];
/* ldt for this task 0 - zero 1 - cs 2 - ds&ss */struct desc_struct ldt[3];
/* tss for this task */struct tss_struct tss;
};struct tss_struct {long	back_link;	/* 16 high bits zero */long	esp0;long	ss0;		/* 16 high bits zero */long	esp1;long	ss1;		/* 16 high bits zero */long	esp2;long	ss2;		/* 16 high bits zero */long	cr3;long	eip;long	eflags;long	eax,ecx,edx,ebx;long	esp;long	ebp;long	esi;long	edi;long	es;		/* 16 high bits zero */long	cs;		/* 16 high bits zero */long	ss;		/* 16 high bits zero */long	ds;		/* 16 high bits zero */long	fs;		/* 16 high bits zero */long	gs;		/* 16 high bits zero */long	ldt;		/* 16 high bits zero */long	trace_bitmap;	/* bits: trace 0, bitmap 16-31 */struct i387_struct i387;
};

看到这小伙伴,就好奇了,task_struct为啥这么多属性,因为它是严格遵守intel手册的属性顺序排布的,也就是下面这张图。

在上面初始化我们就看到了ss初始化就是0x17,这就对应上了之前讲的move_to_user_mode里的0x17的由来。

那既然有了local desciptor相关,但执行代码的流程,是通过段选择子找到GDT,然后根据段选择子来判断是ldt,gdt,那gdtldt是放在哪的呢?还有tss进程的上下文信息的地址在gdt是如何排布的呢?

4.3 TSSLDTGDT表排布

那既然涉及到排布,其实也就是初始化地址的排布,那就在主入口里

void main(void)
{			// 前面代码省略....sched_init();					// 初始化0号进程的tss,ldt描述符信息// 后面代码省略....
}

那我们看看这个代码的实现,这里就进行了ldttss描述符的地址初始化,那我们看看是如何实现的

void sched_init(void)
{int i;struct desc_struct * p;if (sizeof(struct sigaction) != 16)panic("Struct sigaction MUST be 16 bytes");set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));// 后面代码略....
}

点进去后,又是内联汇编,发现设置了怎么多的属性,那这些属性,也不外乎是根据intel手册进行设置的。博主也粘贴了过来,也依次标注了。

TSSLDT是通过TI标志位来判断的

🐯volume 3 - 9.2.2 TSS Descriptor:intel开发手册原话

An attempt to access a TSS using a segment selector with its TI flag set (which indicates the current LDT)

TI标志位设置的话,那么这个描述符是LDT描述符

0x89 -> 1(p) 00(dpl) 0 1001(type):tss

0x82 -> 1(p) 00(dpl) 0 0010(type):ldt

// 初始化tss,ldt描述符
// n后面加的是偏移量,单位是byte
#define _set_tssldt_desc(n,addr,type) 
__asm__ (// 处理TSS Descriptor下面的0 ~ 31"movw $104,%1			// *(n) ,放入104,也就是0~15的SegmentLimit"movw %%ax,%2			// 将0号进程的task.tss地址的低16位,放入*(n+2),也就是16~31的Base Address,(2 * 8 = 16bit)"rorl $16,%%eax			// 处理TSS Descriptor 上面的0 ~ 31"movb %%al,%3			// 0 ~ 7的base addr"movb $type ",%4 		//  8 ~ 16,放入0x89  , 0x89 -> 1(p) 00(dpl) 0 1001(type)"movb $0x00,%5			// 16 ~ 24放入0"movb %%ah,%6			// 24 ~ 31 放入base addr"rorl $16,%%eax::"a(addr), 			// 0"m(*(n)), 			// 1"m(*(n+2)), 		// 2"m(*(n+4)),			// 3 "m(*(n+5)), 		// 4"m(*(n+6)), 		// 5"m(*(n+7)) 			// 6
)// n为tss首地址
set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));
#define set_tss_desc(n,addr) _set_tssldt_desc(((char *) (n)),((i)(addr)),"0x89")set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));
#define set_ldt_desc(n,addr) _set_tssldt_desc(((char *) (n)),((i)(addr)),"0x82")

在这里插入图片描述

看到这,基本上是掌握了linux的用户态从浅入深的所有过程。很多晦涩的点,需要读者多琢磨,因为需要很多前置知识,才能看懂这段源码,当你拥有了这些前置知识GDT,TSS,LDT,interrupt等等,看这个源码在研究研究语法,就可看出是照着intel开发手册写的。

4.4 ldt中的0x17栈段

​ 上面提到了0x17栈段解析,那现在需要知道为什么在ldt表里的索引下标是2。那就要看看ldt表里都初始化了什么,在上面的[4.2 0号进程](# 4.2 0号进程)讲解了各类属性的初始化的值,这里就有ldt表数据的初始化

intel 在gdtidtldt表项中第一个地址是不防值的

所以可以看到0索引下标无值

// struct desc_struct ldt[3];{ {0,0}, 
/* ldt */	      		 {0x9f,0xc0fa00}, // cs  0x0f   {0x9f,0xc0f200}, // ss 0x17}, 

0x17就是对应的是ldt[2],也就是{0x9f,0xc0f200},这个的初始化规则,是通过intel规定的Segment Descriptor的规则,来写的

将其转化为2进制(intel是小端序)

将其转化为2进制(intel是小端序)

0x9f,0xc0f200

0000 0000 1100 0000 1111 0010 0000 0000
0000 0000 0000 0000 0000 0000 1001 1111

能得到的S标志位是描述符类型1

Type0010

就可得出是栈段

在这里插入图片描述

这也得到了0x17的验证。cs的验证同理

5. 总结

​ 内核态和用户态,就是特权级切换,那为什么特权级切换,就有对应的内核态和用户态呢?因为intel规定,每个特权级都有自己的栈信息,相当于你是R0的内核态特权级,有自己的特权级栈,应用程序也是如此。如果他俩都在一个栈,可以遍历敏感信息,一个特权级,那么直接可以像OS操控CPU了

​ 对于CPU来说所有的程序,不管是OS,还是应用程序,对于它来说,都是程序,所以CPU规定了特权级,来隔阂不同的程序代码权限,所以OS程序就是最高特权级的程序了,来通过CPU提供了指令,来控制硬件,而应用程序只能通过OS提供的API来有限的控制CPU硬件,如IO输入输出等。

​ 所以每个特权级都有自己的栈,那如何切换栈呢?切换到我用户态的栈里?这里intel 开发手册提供了中断规则,不同的特权级就可以切换栈,所以linux 0.11利用这个特性,来进行切换栈信息,并压入指定的应用程序的栈地址,也就是用户态代码,当执行完中断时,通过iret指令依次弹出信息,当弹出SS,SP找到linux 0.11指定的栈地址,就完成了内核态到用户态切换

​ 虽然这3段话总结起来简单,但里面涉及的知识,博主研究了很久,才慢慢串通。就比如上面的中断流程涉及的8259A芯片,Segment selector对应的机器码的属性信息等等。

博主这里主要讲了CPU中断场景的硬件中断,没有讲软件方式的INT n中断,后续我会再出2篇文章讲中断,一篇是intel中断的知识,一篇是结合linux源码来讲软件中断。(这里主要讲的是内核态到用户态,用户态到内核态需要系统调用,也就是所谓的软件中断)

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

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

相关文章

雷电9最新版安装Magisk+LSPosd(新手速通)

大家好啊&#xff01;我是NiJiMingCheng 我的博客&#xff1a;NiJiMingCheng 在安卓系统的定制与拓展过程中&#xff0c;获取 ROOT 权限以及安装各类框架是进阶玩家常用的操作&#xff0c;这可以帮助我们实现更多系统层面的个性化功能。今天&#xff0c;我将为大家详细介绍如何…

《Linux服务与安全管理》| 邮件服务器安装和配置

《Linux服务与安全管理》| 邮件服务器安装和配置 目录 《Linux服务与安全管理》| 邮件服务器安装和配置 1.在Server01上安装dns、postfix、dovecot和telnet&#xff0c;并启动 2&#xff0e;在Server01上配置DNS服务器&#xff0c;设置MX资源记录 3&#xff0e;在server1上…

计算机毕业设计Python电商品推荐系统 商品比价系统 电商比价系统 商品可视化 商品爬虫 机器学习 深度学习 京东爬虫 国美爬虫 淘宝爬虫 大数据

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

【银河麒麟高级服务器操作系统】业务访问慢网卡丢包现象分析及处理过程

了解更多银河麒麟操作系统全新产品&#xff0c;请点击访问 麒麟软件产品专区&#xff1a;product.kylinos.cn 开发者专区&#xff1a;developer.kylinos.cn 文档中心&#xff1a;document.kylinos.cn 交流论坛&#xff1a;forum.kylinos.cn 服务器环境以及配置 【内核版本…

鸿蒙开发中的骨架图:提升用户体验的关键一环

大家好&#xff0c;我是小 z&#xff0c;今天要给大家分享一个提升用户体验的超实用技巧 —— 骨架图&#x1f3af; 文章目录 一、什么是骨架图二、骨架图的作用三、鸿蒙开发中实现骨架图的方法1. 利用 opacity 奠定视觉基础2. animateTo 驱动动态变化3. 二者协同触发与展示 四…

leetcode刷题记录(七十三)——543. 二叉树的直径

&#xff08;一&#xff09;问题描述 543. 二叉树的直径 - 力扣&#xff08;LeetCode&#xff09;543. 二叉树的直径 - 给你一棵二叉树的根节点&#xff0c;返回该树的 直径 。二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 r…

【esp32小程序】小程序篇02——连接git

一、创建仓库 进入gitee官网&#xff0c;登录&#xff08;如果没有gitee账号的就自行注册一下&#xff09;。 点击号-->新建仓库 填写好必填信息&#xff0c;然后点击“创建” 二、微信开发者工具配置 在微信开发者工具打开我们的项目。按下面的步骤依次点击 三、验证 点…

Linux的基本指令(上) -- 0基础入门

目录 知识点引入 基本指令 ls指令 pwd 命令 cd 指令 touch 指令 stat指令 mkdir 指令 tree指令 rmdir 指令 rm 命令 man 指令 which 指令 alias 指令 echo指令 输出重定向: > 追加重定向&#xff1a;>> cp 指令 知识点引入 1. Linux中路径用 / 作为路径分隔…

Java测试开发平台搭建(九)前端

1. 搭建前端vue环境 Vue3 安装 | 菜鸟教程 2. 创建项目 1.进入ui vue ui 2. create项目 3. 成功之后添加插件&#xff1a; cli-plugin-router vue-cli-plugin-vuetify 4. 添加依赖 axios 5. 点击任务开始运行 如果报错&#xff1a; 修改vue.config.jsconst { defineConfig }…

基于SpringBoot+Vue的智慧动物园管理系统的设计与实现

获取源码&#xff1a;基于SpringBootVue智慧动物园系统设计与实现: 后台和用户前台。后台包括首页、员工管理、考勤管理、部门管理、角色管理、审核管理、动物管理、演出管理、园区管理、园区设施维修、饲养管理、行为观察管理、疫苗管理、看护管理、个人中心、票务管理、收入管…

55.【5】BUUCTF WEB NCTF2019 sqli

进入靶场 输入admin 123 过滤的这么严格&#xff1f;&#xff1f;&#xff1f; 过滤很严格&#xff0c;此时要么爆破&#xff0c;要么扫描 直接扫描&#xff0c;得到robots.txt 访问后又得到hint.txt 继续访问 图片内容如下 $black_list "/limit|by|substr|mid|,|admi…

【前端】用OSS增强Hexo的搜索功能

文章目录 前言配置 _config.fluid.yml云端实时更新 local-search.xml解决 OSS.Bucket 的跨域问题 前言 原文地址&#xff1a;https://blog.dwj601.cn/FrontEnd/Hexo/hexo-enhance-local-search-with-oss/ 考虑到某著名云服务商提供的云服务器在两年的 99 计划后续费价格高达四…

Hive SQL必刷练习题:留存率问题

首次登录算作当天新增&#xff0c;第二天也登录了算作一日留存。可以理解为&#xff0c;在10月1号登陆了。在10月2号也登陆了&#xff0c;那这个人就可以算是在1号留存 今日留存率 &#xff08;今日登录且明天也登录的用户数&#xff09; / 今日登录的总用户数 * 100% 解决思…

Ubuntu 24.04 LTS 系统语言英文改中文

Ubuntu 24.04 LTS 修改软件源 Ubuntu 更改软件源 修改语言 无需输入命令&#xff0c;为Ubuntu 24.04系统添加中文智能拼音输入法 在 setting 的 system 中按下图操作 点击“Apply Changes”。需要管理员密码&#xff0c;安装完成后&#xff0c;退出登录&#xff0c;重新登…

【从零开始入门unity游戏开发之——C#篇46】C#补充知识点——命名参数和可选参数

考虑到每个人基础可能不一样&#xff0c;且并不是所有人都有同时做2D、3D开发的需求&#xff0c;所以我把 【零基础入门unity游戏开发】 分为成了C#篇、unity通用篇、unity3D篇、unity2D篇。 【C#篇】&#xff1a;主要讲解C#的基础语法&#xff0c;包括变量、数据类型、运算符、…

大模型WebUI:Gradio全解11——Chatbot:融合大模型的多模态聊天机器人(6)

大模型WebUI&#xff1a;Gradio全解11——Chatbot&#xff1a;融合大模型的多模态聊天机器人&#xff08;6&#xff09; 前言本篇摘要11. Chatbot&#xff1a;融合大模型的多模态聊天机器人11.6 为LLM Agent构建UI11.6.1 使用transformers.agents11.6.2 使用Langchain agents11…

springboot基于前后端分离的摄影知识网站

Spring Boot 基于前后端分离的摄影知识网站 一、项目概述 Spring Boot 基于前后端分离的摄影知识网站&#xff0c;是一个专为摄影爱好者、专业摄影师打造的知识共享与交流平台。借助 Spring Boot 强大的后端架构搭建能力&#xff0c;结合前端独立开发的灵活性&#xff0c;整合…

VD:生成a2l文件

目录 前言Simulink合并地址 ASAP2 editor 前言 我之前的方法都是通过Simulink模型生成代码的过程中顺便就把a2l文件生成出来了&#xff0c;这时的a2l文件还没有地址&#xff0c;所以紧接着会去通过elf文件更新地址&#xff0c;一直以为这是固定的流程和方法&#xff0c;今天无…

物联网与前沿技术融合分析

【前言】 在科技发展的滚滚浪潮中&#xff0c;物联网作为连接物理世界与数字世界的桥梁&#xff0c;正日益凸显其关键作用。近年来&#xff0c;物联网与其他前沿技术的融合不断催生新的应用场景与创新模式&#xff0c;为各个领域带来了深刻变革。 物联网与人工智能的深度融合&…

【VRChat · 改模】Unity2019、2022的版本选择哪个如何决策,功能有何区别;

总览 1.Unity2019、2022的版本的选择 2.Unity添加着色器教程 一、Unity2019、2022的版本的选择 1.Unity2019 和 Unity2022 的区别&#xff0c;VRChat SDK 为何要区分两个版本 我是外行&#xff0c;最开始以为的是&#xff0c;2019 和 2022 的变化是基于这个模型本身的。 也…