【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
版本,因为这是linus
对intel 开发手册
最纯粹的编写,也就可以理解为照着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
芯片阵脚置为高电频,并对应的中断程序位置为高电频,当中断检测周期检测到后,会把CPU
的INTR
阵脚置为高电频,通过后续的检测,来执行中断程序,执行完后通过指定的中断返回指令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
指令类似。在中断处理程序的同个特权级,当执行来自中断/异常处理器的返回时,处理器会做如下动作:
- 将CS和EIP寄存器恢复到中断或异常之前的值。
- 恢复
eflags
寄存器。- 适当的增加栈针偏移量。
- 恢复中断程序的执行。
3.4.2 不同特权级
如果产生了切换,就需要额外存储原先的栈的地址信息。
🐯volume 1 - 6.5 INTERRUPTS AND EXCEPTIONS:栈切换的步骤流程(特权级不同)
当处理完后,需要返回到原先的执行代码
🐯volume 1 - 6.5 INTERRUPTS AND EXCEPTIONS:不同特权级,栈切换,返回的处理
执行一个特权级别不同的返回,处理器会有如下动作:
- 执行权限校验。
- 将CS和EIP寄存器恢复到中断或异常之前的值。
- 恢复
eflags
寄存器。- 将SS和ESP寄存器恢复到中断或异常之前的值,从而导致堆栈切换回被中断过程的堆栈。
- 恢复被中断的程序。
3.5 中断流程
那既然发生了中断,那intel 开发手册
是如何规定的中断运转流程呢?看下下面这张图。
CPU
首先正在运行当前程序,中断检测周期定期通知,CPU
当执行完一个原子性的操作后,然后根据中断检测周期来的提醒,检测CPU
的INTR
阵脚,和IF
标志位(后面会说),进行决定是否运行中断程序,当产生中断后,会有中断程序被执行,那这些程序是如何产生的?又怎么找到的呢?
intel 开发手册
规定了18个预定义的中断和异常,224用户定义的中断,这些预定义的程序的入口在IDT
表里里。在IDT
里的每个中断和异常被一个向量数字所标识。
目录位置:🐯volume 1 - 6.5 INTERRUPTS AND EXCEPTIONS
有CPU
和OS
自己定义的中断程序,通过IDT
来寻找对应的程序,简简单单的一句话,解决了2个疑问。那下来我来介绍下如何执行中断流程代码的步骤
3.6 定位中断程序
这里会涉及很多概念,如GDT
,Segmenet Selector
,Segment 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 中断流程步骤总结
来梳理下流程:
- 首先8259A芯片会产生一个中断标志,然后阵脚
INT
置为高电频,对应的中断向量位置置为高电频。或者int
指令进行中断 - 中断检测周期发现后,将CPU的
INTR
置为1,当CPU执行完最后一个指令(IF,ID,EXEC,MEM,WB)时,开始判断是否可以中断 - 检查
eflags
的if
标志位,是否是被清除的,是的话,不能中断 - CPU和8259A芯片连接的数据总线,解码获取到中断向量(与就是中断的程序位置)
- 获取到后,和
IDTR
(base addr + offset)找到对应IDT
表,专表专用,通过中断向量找到对应的IDT
描述符 - 进行特权级检查,是否发生栈切换,(CPL和中断程序的dpl判断),然后压入对应的
SS,SP,ELFAGS,CS,IP,ERROR CODE
等信息 - 通过中断描述符段选择子找到对应的
GDT
里对应的各种段信息 - 然后开始执行
code segment
中断程序 - 执行后,通过
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里
)
那为什么ss
是0x17
,这个也是对着手册中的段选择子的规则写的,那我们来看下
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
,那gdt
里ldt
是放在哪的呢?还有tss
进程的上下文信息的地址在gdt
是如何排布的呢?
4.3 TSS
和LDT
在GDT
表排布
那既然涉及到排布,其实也就是初始化地址的排布,那就在主入口里
void main(void)
{ // 前面代码省略....sched_init(); // 初始化0号进程的tss,ldt描述符信息// 后面代码省略....
}
那我们看看这个代码的实现,这里就进行了ldt
和tss
描述符的地址初始化,那我们看看是如何实现的
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
手册进行设置的。博主也粘贴了过来,也依次标注了。
TSS
和LDT
是通过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 在
gdt
,idt
,ldt
表项中第一个地址是不防值的所以可以看到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
Type
:0010
就可得出是栈段
这也得到了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
源码来讲软件中断。(这里主要讲的是内核态到用户态,用户态到内核态需要系统调用,也就是所谓的软件中断)
相关文章:
【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_mode4.2 0号进程4.3 TSS和LDT在GDT表排布4.4 ldt中的0x17栈段 5. 总结 1. 由来 首…...
goland map学习-实践使用练习:判断存在及遍历
对于数据: type Person struct {Address stringAge intJob stringName string }type People map[string]Personvar per People{"1": Person{Address: "1",Age: 1,Job: "1",Name: "1",},"2&quo…...
【威联通】FTP服务提示:服务器回应不可路由的地址。被动模式失败。
FTP服务器提示:服务器回应不可路由的地址。被动模式失败。 问题原因网络结构安全管理配置服务器配置网关 问题 FTP服务器提示:服务器回应不可路由的地址…...
两份PDF文档,如何比对差异,快速定位不同之处?
PDF文档比对是通过专门的工具或软件,自动检测两个PDF文件之间的差异,并以可视化的方式展示出来。这些差异可能包括文本内容的修改、图像的变化、表格数据的调整、格式的改变等。比对工具通常会标记出新增、删除或修改的部分,帮助用户快速定位…...
Vue.js 组件之间的通信模式
Vue.js 组件之间的通信模式 组件之间的通信模式 在 Vue.js 中,组件之间的通信是构建复杂应用的关键。根据组件之间的关系和需求,Vue 提供了多种通信方式。本文介绍了常见的通信模式及其详细示例。 一、父子组件通信 1. 父组件向子组件传递数据&#…...
【Linux 重装】Ubuntu 启动盘 U盘无法被识别,如何处理?
背景 U盘烧录了 Ubuntu 系统作为启动盘,再次插入电脑后无法被识别 解决方案(Mac 适用) (1)查找 USB,(2)格式化(1)在 terminal 中通过 diskutil list 查看是…...
.Net Core微服务入门全纪录(四)——Ocelot-API网关(上)
系列文章目录 1、.Net Core微服务入门系列(一)——项目搭建 2、.Net Core微服务入门全纪录(二)——Consul-服务注册与发现(上) 3、.Net Core微服务入门全纪录(三)——Consul-服务注…...
pyautogui自动化鼠标键盘操作
pyautogui,用来写自动化脚本,比按键精灵更方便。pyautogui.position()可以获取当前鼠标位置。pyautogui不支持中文输入,利用 pyperclip从剪切板粘贴输入。 # -*- coding: utf-8 -*- import time import os import traceback import logging …...
2024年AI大模型技术年度总结与应用实战:创新与突破并进
前言 回顾2024年,我一共发布了286篇博文,粉丝数也达到了43000多。这一年里,我收获颇丰,始终坚持AI大模型的研究方向,并且积极开展大模型的实战应用,也取得了一系列令人振奋的突破。 在286篇博文中&#…...
HTML中相对路径和绝对路径详解
文章目录 HTML中相对路径和绝对路径详解一、引言二、绝对路径1、定义2、使用场景3、代码示例 三、相对路径1、定义2、使用方法3、代码示例 四、使用示例1、图片路径2、CSS和JavaScript文件路径3、页面内部链接 五、总结 HTML中相对路径和绝对路径详解 一、引言 在HTML开发中&a…...
联通用户管理系统(一)
#联通用户管理系统(一) 1.新建项目 如果你是windows的话,界面应该是如下的: 2.创建app python manage.py startapp app01一般情况下:我们是在pycharm的终端中运行上述指令,但是pychrm中为我们提供了工具…...
STM32-CAN总线
1.CAN总线简介 CAN总线是由BOSCH公司开发的一种简洁易用、传输速度快、易扩展、可靠性高的串行通信总线 2.CAN总线特征 两根通信线(CAN_H、CAN_L),线路少,无需共地差分信号通信(相对的是单端信号)&#…...
mac m1下载maven安装并配置环境变量
下载地址:Download Apache Maven – Maven 解压到一个没有中文和空格的文件夹 输入pwd查看安装路径 输入cd返回根目录再输入 code .zshrc 若显示 command not found: code你可以通过以下步骤来安装和配置 code 命令: 1. 确保你已经安装了 Visual Studio…...
Linux -- HTTP 请求 与 响应 报文
目录 请求报文: 请求方法 响应报文: 状态码 与 状态码描述 共性 常见的报头 请求报文: 请求方法 方法说明GET获取资源POST传输实体主体PUT传输文件HEAD获得报文首部DELETE删除文件OPTIONS询问支持的方法TRACE追踪路径CONNECT要求用…...
oneplus3t-lineage-14编译-android7
lineageOS-14.1-oneplus3t-build.md lineageOS-14(android7)的开发者模式/usb调试(adb)有root功能, 而lineageOS-16(android9)无 oneplus3t-lineage-14编译-android7 1 清华linageos镜像 x lineage-14.1-20180223-nightly-oneplus3-signed.zip ntfs分区挂载为普通用户目录…...
Spring Boot与Spring的区别
在当今的Java开发领域,Spring框架无疑是最为重要且广泛应用的框架之一。而随着技术的不断发展和开发者对效率与便捷性的追求,基于Spring框架的Spring Boot应运而生。接下来,将详细阐述Spring Boot与Spring的主要区别,并通过实际的…...
阿九的python 爬虫进阶课18.3 学习笔记
文章目录 前言1. 爬取大标题2. 爬取小标题3. 证券栏下的标题4. 某篇文章里的具体内容 前言 网课链接:https://www.bilibili.com/video/BV1kV4y1576b/新浪财经网址:https://finance.sina.com.cn/需先下载库: conda install lxml布置爬取的一…...
对人型机器人的研究和展望
目录 概述 1 核心软硬件部件 1.1 运动控制部分 1.1.1 减速机 1.1.2 编码器 1.1.3 直流无刷电机 1.2 智能仿生手 1.3 控制板卡 2 人型机器人的应用 3 未来展望 概述 如果现在有人问:当前那个行业最火?毫无疑问答案肯定是人型机器人了。当前各类机…...
docker 使用远程镜像启动一个容器
使用前提: 首先你得安装docker,其次你得拥有一个远程镜像 docker run --name io_11281009 --rm -it -p 2233:22 -v .:/root/py -e ed25519_rootAAAAC3NzaC1lZDI1********Oy7zR7l7aUniR2rul ghcr.lizzie.fun/fj0r/io srv对上述命令解释: 1.docker run:…...
VTK知识学习(37)-频域处理
1、前言 在图像处理和分析中,经常会将图像从图像空间转换到其他空间中,并利用这些空间的性质对转换后的数据进行分析处理。图像频域处理借助空间变换将图像从图像空间转换到频域空间,根据频域空间的性质对数据进行处理(如滤波),最…...
什么是软件架构
什么是软件架构 程序员说,软件架构是要决定编写哪些C程序或OO类、使用哪些库和框架 程序经理说,软件架构就是模块的划分和接口的定义 系统分析员说,软件架构就是为业务领域对象的关系建模 配置管理员说,软件架构就是开发出来的…...
RoCE网络及其协议栈详解(没有中间商赚差价的网络)
引言 随着数据中心对高性能、低延迟通信需求的不断增长,传统的TCP/IP以太网连接已经难以满足现代应用的要求。为了解决这些问题,RDMA(Remote Direct Memory Access)技术应运而生。RDMA是一种允许网络中的不同计算机直接访问对方内…...
el-dialog弹窗的@open方法中,第一次引用ref发现undefined问题,第二次后面又正常了
解决方法 直接不用这个open方法,转而用opened,代码例子: <el-dialog title"单个新增" :visible.sync"PlacardShowSingle" opened"openpbSingle()" width"1100px" top"1%" :close-on-c…...
基于阿里云视觉智能平台实现换脸程序
简介 阿里云视觉智能平台提供了一种强大的换脸功能,能够将视频中的人脸替换成其他图片中的脸。这种功能广泛应用于视频编辑、特效制作等领域。本文将介绍如何使用阿里云视觉智能平台进行视频换脸。 核心工作流程 整个换脸程序的实现可分为以下几个主要步骤&#…...
【2024年华为OD机试】(A卷,100分)- 完美走位 (Java JS PythonC/C++)
一、问题描述 题目解析 题目描述 在第一人称射击游戏中,玩家通过键盘的 A、S、D、W 四个按键控制游戏人物分别向左、向后、向右、向前进行移动。假设玩家每按动一次键盘,游戏人物会向某个方向移动一步。如果玩家在操作一定次数的键盘并且各个方向的步数相同时,此时游戏人…...
一文夯实垃圾收集的理论基础
如何判断一个引用是否存活 引用计数法 给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任何时候计数器为 0 的对象就是不可能再被使用的。 优点:可即刻回收垃圾&a…...
jenkins-api操作
一. 简述: 在一个比较复杂的环境中, 往往会有自己开发的运维管理平台。在代码发布这块,尽管jenkins有一个比较方便的UI, 但很多团队还是喜欢集中式管理, 将发布功能(仅仅把jenkins作为一个发布组件使用)嵌入运维管理平…...
SpringBoot3+Vue3学习
什么是Spring Boot? Spring Boot 是Spring 提供的一个子项目,用于快速构建Spring应用程序 传统方式弊端:之前的项目都用Spring FrameWork构建,需要手动引入依赖,依赖之间有可能存在冲突,较为麻烦;在配置…...
SQL刷题快速入门(三)
其他章节: SQL刷题快速入门(一) SQL刷题快速入门(二) 承接前两个章节,本系列第三章节主要讲SQL中where和having的作用和区别、 GROUP BY和ORDER BY作用和区别、表与表之间的连接操作(重点&…...
Flutter鸿蒙化中的Plugin
Flutter鸿蒙化中的Plugin 前言鸿蒙项目内PluginFlutter端实现鸿蒙端实现创建Plugin的插件类注册Plugin 开发纯Dart的package为现有插件项目添加ohos平台支持创建插件配置插件编写插件内容 参考资料 前言 大家知道Flutter和鸿蒙通信方式和Flutter和其他平台通信方式都是一样的&…...
Ubuntu 22.04.5 修改IP
Ubuntu22.04.5使用的是netplan管理网络,因此需要在文件夹/etc/netplan下的01-network-manager-all.yaml中修改,需要权限,使用sudo vim或者其他编辑器,修改后的内容如下: # Let NetworkManager manage all devices on …...
后端:MyBatis
文章目录 1. MyBatis1-1. Mybatis 工具类的封装1-2. Mybatis 通过集合或实体类传递参数-实现插入数据(增)1-3. MyBatis 实现删除数据(删)1-4. MyBatis 实现修改数据(改)1-5. MyBatis 实现查询数据(查) 2. MyBatis 配置文件中的一些标签和属性2-1.environments标签2-2. dataSour…...
CBAM-2018学习笔记
名称: Convolutional Block Attention Module (CBAM) 来源: CBAM: Convolutional Block Attention Module 相关工作: #ResNet #GoogleNet #ResNeXt #Network-engineering #Attention-mechanism 创新点: 贡献: 提…...
HTML根元素<html>的语言属性lang:<html lang=“en“>
诸神缄默不语-个人CSDN博文目录 在编写HTML页面时,通常会看到<html lang"en">这行代码,特别是在网页的开头部分,就在<!DOCTYPE html>后面。许多开发者可能对这个属性的含义不太了解,它到底有什么作用&…...
解决github无法clone的问题
问题背景 (base) ~$ git clone https://github.com/isaac-sim/IsaacLab.git 正克隆到 IsaacLab... fatal: 无法访问 https://github.com/isaac-sim/IsaacLab.git/:gnutls_handshake() failed: Error in the pull function.解决办法 我使用了代理,需要配…...
第1章:Python TDD基础与乘法功能测试
写在前面 这本书是我们老板推荐过的,我在《价值心法》的推荐书单里也看到了它。用了一段时间 Cursor 软件后,我突然思考,对于测试开发工程师来说,什么才更有价值呢?如何让 AI 工具更好地辅助自己写代码,或许…...
【华为路由/交换机的ftp文件操作】
华为路由/交换机的ftp文件操作 PC:10.0.1.1 R1:10.0.1.254 / 10.0.2.254 FTP:10.0.2.1 S1:无配置 在桌面创建FTP-Huawei文件夹,里面创建config/test.txt。 点击上图中的“启动”按钮。 然后ftp到server,…...
【HBuilderX 中 Git 的使用】
目录: 一:安装必要的版本控制工具二:把Github上的项目克隆到本地三:将本地的项目上传到Github上 一:安装必要的版本控制工具 1️⃣ 安装 TortoiseGit 工具,下载地址:https://tortoisegit.org/do…...
语言模型的价值定位与技术突破:从信息处理到创新认知
标题:语言模型的价值定位与技术突破:从信息处理到创新认知 文章信息摘要: 当前语言模型的核心价值主要体现在信息综合与处理能力上,用户友好的交互界面是其成功关键。在模型计算机制方面,推理能力的实现包括chain-of-…...
使用Websocket进行前后端实时通信
1、引入jar,spring-websocket-starter <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId> </dependency> 2、配置websocket config import org.springframe…...
【Leetcode 热题 100】70. 爬楼梯
问题背景 假设你正在爬楼梯。需要 n n n 阶你才能到达楼顶。 每次你可以爬 1 1 1 或 2 2 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? 数据约束 1 ≤ n ≤ 45 1 \le n \le 45 1≤n≤45 解题过程 昨天刚刚当成扩展题做过,今天遇到了再写一次。…...
STM32更新程序OTA
STM32的OTA(Over-The-Air)更新程序是一种通过无线通信方式,为设备分发新软件、配置甚至更新加密密钥的技术。以下是关于STM32 OTA更新程序的详细介绍: 一、OTA升级流程 STM32的OTA升级流程通常包括以下几个关键步骤:…...
【海贼王航海日志:前端技术探索】一篇文章带你走进JavaScript(三)
目录 1 -> WebAPI背景知识 1.1 -> 什么是WebAPI 1.2 -> 什么是API 1.3 -> 什么是DOM 1.3.1 -> DOM树 2 -> 获取元素 2.1 -> querySelector 2.2 -> querySelectorAll 3 -> 事件初识 3.1 -> 基本概念 3.2 -> 事件三要素 4 -> 操…...
计算机创造的奇迹——C语言
一.简介 C语言是一种较早的程序设计语言,诞生于1972年的贝尔实验室。1972 年,Dennis Ritchie 设计了C语言,它继承了B语言的许多思想,并加入了数据类型的概念及其他特性。 尽管C 语言是与 UNIX 操作系统一起被开发出来的ÿ…...
TypeScript - 利用GPT辅助学习
TypeScript 一、基础1. 安装 TypeScript2. 创建你的第一个 TypeScript 文件3. 编译 TypeScript 代码4. 变量声明与类型注解5. 函数与类型注解6. 总结 二、进阶常用类型1. 类型别名2. 对象类型3. 类型断言4.typeof 操作符 高级类型1. 类2. 交叉类型3. 泛型与 keyof4. 索引签名类…...
Node.js 与 JavaScript 是什么关系
JavaScript 是一种编程语言,而 Node.js 是 JavaScript 的一个运行环境,它们在不同的环境中使用,具有一些共同的语言基础,但也有各自独特的 API 和模块,共同推动着 JavaScript 在前后端开发中的广泛应用。 一、基础语言…...
Spring MVC:设置响应
目录 引言 1. 返回静态页面 1.1 Spring 默认扫描路径 1.2 RestController 1.2.1 Controller > 返回页面 1.2.2 ResponseBody 2. 返回 HTML 2.1 RequestMapping 2.1.1 produces(修改响应的 Content-Type) 2.1.2 其他属性 3. 返回 JSON 4. 设置状态码 4.1 HttpSer…...
c#实现当捕获异常时自动重启程序
首先,需要说明这并不是一个推荐的做法,只有在你确实有这样的需求时才考虑这么做。 以下是AI的回答,为什么不推荐这么做,供参考。 在C#中,如果你在catch语句中尝试重启程序自身,可能会遇到以下几个问题&…...
游戏引擎学习第84天
仓库:https://gitee.com/mrxiao_com/2d_game_2 我们正在试图弄清楚如何完成我们的世界构建 上周做了一些偏离计划的工作,开发了一个小型的背景位图合成工具,这个工具做得还不错,虽然是临时拼凑的,但验证了背景构建的思路。这个过…...
Python----Python高级(文件操作open,os模块对于文件操作,shutil模块 )
一、文件处理 1.1、文件操作的重要性和应用场景 1.1.1、重要性 数据持久化: 文件是存储数据的一种非常基本且重要的方式。通过文件,我们可 以将程序运行时产生的数据永久保存下来,以便将来使用。 跨平台兼容性: 文件是一种通用…...