保护模式 对CPL,RPL,DPL的总结

       学习过程中遇到一个对保护模式总结很好的Blog,转来分享一下。

先说下特权级的概念,在保护模式下,系统依靠特权级来实施代码和数据的保护,相当于权限啦。特权级共有4个级别,0,1,2,3,数字越小表示权限越高。如图:


较为核心的代码和数据放在较高(靠内)的层级中,处理器用此来防止较低特权的任务在不被允许的情况下访问处于高特权级的段。为了防止概念混淆,我们不用特权级大小来说明,改为内层(高),外层(低)来讲。

特权级有3种CPL,DPLRPL,每个都是有4个等级。
我对他们的关系理解是这样:一般来说,CPL代表当前代码段的权限,如果它想要去访问一个段或门,首先要看看对方的权限如何,也就是检查对方的DPL,如果满足当前的权限比要访问的权限高,则有可能允许去访问,有些情况我们还要检查
选择子的权限,即RPL,因为我们通过选择子:偏移量的方式去访问一个段,这算是一个访问请求动作,因此
称为请求访问权限RPL(Requst Privilege Level)。当请求权限也满足条件,那么访问就被允许了。


CPL(Current Privilege Level)
CPL是当前执行的任务的特权等级,它存储在CS和SS的第0位和第1位上。(两位表示0~3四个等级)
通常情况下,CPL等于代码所在段的特权等级,当程序转移到不同的代码段时,处理器将改变CPL
注意:在遇到一致代码段时,情况特殊,一致代码段的特点是:可以被等级相同或者更低特权级的代码访问,当处理器访问一个与当前代码段CPL特权级不同的一致代码段时,CPL不会改变。

DPL(Descriptor Privilege Level)
表示门或者段的特权级,存储在门或者段的描述符的DPL字段中。正如上面说的那样,当当前代码段试图访问一个段或者门时,其DPL将会和当前特权级CPL以及段或门的选择子比较,根据段或者门的类型不同,DPL的含义不同:
    1.数据段的DPL:规定了访问此段的最低权限。比如一个数据段的DPL是1,那么只有运行在CPL为0或1的程序才可能访问它。为什么说可能呢?因为还有一个比较的因素是RPL。访问数据段要满足有效特权级别(上述)高于数据段的DPL.
    2.非一致代码段的DPL(不使用调用门的情况):DPL规定访问此段的特权,只有CPL与之相等才有可能访问。
    3.调用门的DPL,规定了程序或任务访问该门的最低权限。与数据段同。
    4.一致代码段和通过调用门访问的非一致代码段,DPL规定访问此段的最高权限。
     比如一个段的DPL为2,那么CPL为0或者1的程序都无法访问。
   5. TSS的DPL,同数据段。

RPL(Rquest Privilege Level)

RPL是通过选择子的低两位来表现出来的(这么说来,CS和SS也是存放选择子的,同时CPL存放在CS和SS的低两位上,那么对CS和SS来说,选择子的RPL=当前段的CPL)。处理器通过检查RPLCPL来确认一个访问是否合法。即提出访问的段除了有足够的特权级CPL,如果RPL不够也是不行的(有些情况会忽略RPL检查)。
为什么要有RPL
操作系统往往通过设置RPL的方法来避免低特权级的应用程序访问高特权级的内层数据。
例子情景:调用者调用操作系统的某过程去访问一个段。
当操作系统(被调用过程)从应用程序(调用者)接受一个选择子时,会把选择子的RPL设置称调用者的权限等级,于是操作系统用这个选择子去访问相应的段时(这时CPL为操作系统的等级,因为正在运行操作系统的代码),处理器会使用调用者的特权级去进行特权级检查,而不是正在实施访问动作的操作系统的特权级(CPL),这样操作系统就不用以自己的身份去访问(就防止了应用去访问需要高权限的内层数据,除非应用程序本身的权限就足够高)。
那么RPL的作用就比较明显了:因为同一时刻只能有一个CPL,而当低权限的应用去调用拥有至高权限的操作系统的功能来访问一个目标段时,进入操作系统代码段时CPL变成了操作系统的CPL,如果没有RPL,那么权限检查的时候就会用CPL,而这个CPL权限比应用程序高,也就可能去访问需要高权限才能访问的数据,这就不安全了。所以引入RPL,让它去代表访问权限,因此在检查CPL的同时,也会检查RPL.一般来说如果RPL的数字比CPL大(权限比CPL的低),那么RPL会起决定性作用。
说这么多不明白都不行啦~真累
下面是引用的一个超棒的关于权限控制的总结:
引用地址
还有一篇文章在此

■ 数据访问时的权限check

一、 访问data segment时(ds、es、fs 及gs)
1、 程序指令要访问数据时,data segment selector 被加载进 data segment register(ds、es、fs 和 gs)前,处理器会进行一系列的权限检查,通过了才能被加载进入segment register。处理器分为两步进行检查:
CPL(当前程序运行的权限级别)与 RPL(位于selector中的 RPL)作比较,并设置有效权限级别为低权限的一个。
★ 得出的有效权限级别与 DPL(segment descriptor 中的 DPL)作比较,有效权限级别高于 DPL,那么就通过。低于就不允许访问。
2、举个例子:
如果: CPL = 3、 RPL = 2、 DPL = 2。那么
EPL = CPL > RPL ? CPL : RPL;
if (EPL <= DPL) {
/* 允许访问 */
} else {
/* 失败,#GP 异常生产,执行异常 */
}
或者:
if (( CPL <= DPL) && ( RPL <= DPL)) {
/* 允许访问 */
} else {
/* 失败,#GP 异常生产,执行异常 */
}

   也就是要访问目标data segment,那么必须要有足够的权限,这个足够的权限就是:当前运行的权限级别及选择子的请求权限级别要高于等于目标data segment的权限级别。

二、 访问stack segment时
1、 该问stack 时的权限检查更严格,CPLRPLDPL三者必须相等才能通过该问请求。

2、 举个例子:

if ( CPL == RPL && RPL == DPL && CPL == DPL) {
      /* 允许访问 */
} else {
     /* 失败,#GP 异常生产,执行异常 */
}

也就是说每个权限级别有相对应的statck segment。不能越权访问,即使高权限访问低权限也是被拒绝的

■ 控制权的转移及权限检查。

   权限检查的4个要素:

CPL:处理器当前运行的级别,也就是:当前 CS 的级别,在 CS 的 BIT0 ~ Bit1

DPL:访问目标代码段所需的级别。定义在 segment descriptor 的 DPL 域中

RPL: 通过 selector 进行访问时,selector 内定义的级别。

★ conforming/nonconforming:目标代码属于 nonconforming 还是 conforming 定义在segment descritptor 的 C 标志位中

x86 的各方面检查依赖于目标代码段是 nonconforming(不一致) 还是 conforming(一致) 类型


一、 直接转移(far call 及 far jmp)
    1、 直接转移定义为不带gate selector或 taskselector的远调用。当执行一条 call cs:eip 或 jmp cs:eip 指令时,cs 是目标代码段的selector,处理器在加载指令操作数中的cs进cs register前,要进行一系列的权限检查,控制权的转移权限分两部分,根据目标代码段descriptor定义的两种情况:
1)、nonconforming target code segment
★ 直接转移后的权限级别是不能必改变的。因此,CPL 必须要等于目标代码段的 DPL
★ 要有足够的请求权限进行访问。因此,目标代码段选择子的RPL <= CPL

2)、conforming target code segment
★ conforming code segment 允许访问高权限级别的代码。这里只需检查 CPL >= DPL 即可,RPL 忽略不检查。
★ 转移后CPL不会改变。

   2、 以上两步通过后,处理器加载目标代码段的CS 进入CS register,但权限级别不改变,继而RPL被忽略。

★ 处理器根据CS selector在相应的descriptor table 找到 code segment descriptor。CS 的Bit2(TI域) 指示在哪个descriptor table 表查找,CS.TI = 0 时在GDT查找,CS.TI = 1时在LDT 查找。
★ CS的Bit15~Bit3 是selector index 值,处理器基于GDT或LDT来查找segment descriptor。具体是:GDTR.base 或 LDTR.base + CS.SI × 8 得出code segment descritpro。
★ 处理器自动加载code segment descriptor 的 base address、segment limit及attribute 域进入 CS register的相应的隐藏域。
★ 转到CS.base + eip 处执行指令

总结:用代码形式来说明直接转移 call cs:eip 这条指令
例: call 1a:804304c (即cs = 1a, eip = 804304c)

target_cs = 1a;
target_eip = 0x0804304c;
CPL = CS. RPL;            /* 当前执行的代码段的权限级别就是 CPL */
RPL = target_cs. RPL;     /* 目标段 selector 的低3位是 RPL */
target_si = target_cs.SI;    /* 目标段 selector 的索引是Bit15~Bit3 */
target_ti = target_cs.TI;    /* 目标段selector的描述符表索引是Bit2 */
CODESEG_DESCRIPTOR target_descriptor;

if (target_ti == 0) { /* target_cs.TI为0 就是参考到 GDT(全局描述符表) */
/* 以GDTR寄存器的base 为基地址加上selector的索引乘以8即得出目标
       段描述符,目标描述符的 DPL就是目标段所需的访问权限 */
    target_descriptor = GDTR.base + target_si * 8

} else {               /* 否则就是参考 LDT (局部描述符表)*/
    /* 以 LDTR寄存器的base 为基地址得出目标段描述符 */

target_descriptor = LDTR.base + target_si * 8;
}
DPL = target_descriptor. DPL;     /* 获取 DPL */
if (target_descriptor.type & 0x06) { /* conforming */
if ( CPL >= DPL) {   /* 允许执行高权限代码 */
     /* go ahead */
    } else {
     /* 引发 #GP 异常 */
    goto DO_GP_EXCEPTION;
}

} else {                  /* nonconforming */
    if ( CPL == DPL && RPL <= CPL) {  
        /* go ahead */
    } else {
        /* 引发 #GP 异常 */
       goto DO_GP_EXCEPTION;
    }
  
}
/****** go ahead … …******/
CS = target_cs;           /* 加载目标段CS 进入 CS 寄存器 */
EIP = target_eip;        /* 加载目标指令EIP 进入 EIP 寄存器 */
                 /* 当前执行权限 CPL 不改变 */

goto target_descriptor.base + target_eip; /* 跳转到目标地址执行 */

DO_GP_EXCEPTION:     /* 执行 #GP异常点 */
           … …

二、 使用call gate 进行控制权的转移
使用call gate进行转移控制,目的是建立一个利用gate进行向高权限代码转移的一种保护机制。gate符相当一个进入高权限代码的一个通道。


对于指令 call cs:eip 来说:
★ 目标代码的selector是一个门符的选择子。用来获取门描述符。
★ 门描述符含目标代码段的selector及目标代码的偏移量。
★ 目标代码的ip值被忽略。因为门符已经提供了目标代码的偏移量。

1、 权限的检查
★ 首先,必须要有足够的权限来访问gate符,所以:CPL <= DPLg(门符的DPL)且:RPL <= DPLg
★ 进前代码向高权限代码转移,所以,对于conforming类型的代码段来说,必须CPL >= DPLs(目标代码段的DPL
★ 对于nonconforming类型代码段来说,必须CPL = DPLs
★ call 指令改变当前权限,而jmp指令不改变当前权限。

总结:

if (( CPL <= DPLg) && ( RPL <= DPLg)) { /* 足够的权限访问门符 */
if (target.C == CONFORMING) {   /* 目标代码属于 conforming类型 */
    /* 向高权限级别代码转移控制 */
    if ( CPL >= DPLs) {
           /* 通过访问 */
      } else {
            /* 失败,#Gp异常发生 */
    }

    } else {               /* 目标代码属于 nonconforming 类型 */
       /* 平级转移 */
    if ( CPL == DPLs) {
            /* 通过访问 */
        } else {
             /* 失败,#GP 异常发生 */
        }
}

} else {   /* 没有足够权限访问门符 */
/* #GP 异常发生 */
}

2、 控制权的转移
指令:call 1a:804304c (其中1a是call gate selector)

gate_selector = 0x1a;               /* call gate selector */
RPL = gate_selector. RPL;            /* 门符选择子 RPL */
CPL = CS. RPL;                     /* 当前代码段低3位是 CPL*/
CALLGATE_DESCRIPTOR call_gate_descriptor;      /* 门符的描述符 */
CODESEG_DESCRIPTOR target_cs_descritpor;      /* 目标代码段的描述符 */
call_gate_si = gate_selector.SI;      /* 门符 selector 的索引 */
call_gate_ti = gate_selector.TI;      /* 门符selector的描述符表索引 */
                                                                         
/* 获取call gate descriptor */
if (call_gate_ti == 0) {      /* TI为0 就是参考到 GDT(全局描述符表) */
/* 以GDTR寄存器的base 为基地址加上selector的索引乘以8即得出门符的描述符,门符的 DPL就是门符的访问权限 */
    call_gate_descriptor = GDTR.base + call_gate_si * 8;
} else {               /* 否则就是参考 LDT (局部描述符表)*/
     /* 以 LDTR寄存器的base 为基地址得出目标段描述符 */
call_gate_descriptor = LDTR.base + call_gate_si * 8;
}

/* 获取 target code segment descriptor */
target_cs = call_gate_descriptor.selector;    /* 获取门符的目标代码段选择子 */
target_cs_si = target_cs.SI;         /* 目标代码段的索引 */
target_cs_ti = target_cs.TI;                  /* 目标代码描述符表索引 */
if (target_cs_ti == 0)
    target_cs_descriptor = GDTR.base + target_cs_si * 8;
else
    target_cs_descriptor = LDTR.base + target_cs_si * 8;

DPLg = call_gate_descriptor. DPL;     /* 获取门符的 DPL */
DPLs = target_cs_descriptor. DPL;     /* 获取目标代码段的 DPL */

if ( CPL <= DPLg && RPL <= DPLg) {   /* 有足够权限访问门符 */
     if (target_cs_descriptor.type & 0x06) { /* conforming */
          if ( CPL >= DPLs) {
                 /* 允许访问目标代码段 */
          } else {
                 /* #GP 异常产生 */
          }
     } else if ( CPL == DPLs) { /* nonconforming */
         /* 允许访问目标代码段 */
     } else {
          /* 拒绝访问,#GP 异常发生 */
          goto DO_GP_EXCEPTION;
     }
} else { /* 无权限访问门符 */
     /* 拒绝访问, #GP异常发生 */
     goto DO_GP_EXCEPTION;
}
/* 允许访问 */
current_CS = target_cs;       /* 加载目标代码段进入CS 寄存器 */
current_CS. RPL = DPLs;        /* 改变当前执行段权限 */
current_EIP = call_gate_descriptor.offset;   /* 加载EIP */
/* 跳转到目标代码执行 */
/* goto current_CS:current_EIP */
goto target_cs_descriptor.base + call_gate_descriptor.offset;
return;
DO_GP_EXCEPTION:         /* 执行异常 */

三、 使用中断门或陷井门进行转移
   中断门符及陷井门必须存放在IDT中,IDT表也可以存放call gate。

1、 中断调用时的权限检查
   用中断门符进行转移时,所作的权限检查同call gate相同,区别在于intterrupt gate 转移不需要检查RPL,因为,没有RPL需要检查。
★ 必须有足够的权限访问门符,CPL <= DPLg
★ 向同级权限代码转移时,CPL == DPLs,向高权限代码转移时,CPL > DPLs

总结

if ( CPL <= DPLg) { /* 有足够权限访问门符 */
    if ( CPL >= DPLs) {
        /* 允许访问目标代码头 */
    } else {
         /* 失败,#GP异常发生 */
    }

} else {
/* 失败,#GP异常发生 */
}

2、 控制权的转移
   发生异常或中断调用时
★ 用中断向量在中断描述符表查找描述符:中断向量×8,然后加上IDT表基址得出描述符表。
★ 从查找到的描述符中得到目标代码段选择子,并在相应的GDT或LDT中获取目标代码段描述符。
★ 目标代码段描述符的基址加上门符中的offset,确定最终执行入口点。


中断或陷井门符转移的总结:
例: int 0x80 指令发生的情况

vector = 0x80;
INTGATE_DESCRIPTOR gate_descriptor = IDTR.base + vector * 8;
CODESEG_DESCRIPTOR target_descriptor;
TSS tss = TR.base;               /* 得到TSS 内存块 */
DPLg = gate_descriptor. DPL;
target_cs = gate_descriptor.selector;
if ( CPL <= DPLg) {            /* 允许访问门符 */

if (target_cs.TI == 0) {   /* index on GDT */
    target_descriptor = GDTR.base + target_cs.SI * 8;
} else {              /* index on LDT */
target_descriptor = LDTR.base + target_cs.SI * 8;
    }

DPLs = target_descriptor. DPL;


if ( CPL > DPLs) {     /* 向高权限代码转移 */

    /* 根据目标代码段的 DPL值来选取相应权限的stack结构 */
    switch (DPLs) {
    case 0 :     /* 假如目标代码处理0级,则选0级的stack结构 */
             SS = tss.ss0;
             ESP = tss.esp0;
             break;
        case 1:
             SS = tss.ss1;
            ESP = tss.esp1;
             break;
        case 2:
             SS = tss.ss2;
             ESP = tss.esp2;
             break;
    }

       /* 以下必须保护旧的stack结构,以便返回 */
    *--esp = SS;          /* 将当前SS入栈保护 */
    *--esp = ESP;         /* 将当前ESP入栈保护 */

} else if ( CPL == DPLs) {
     /* 同级转移,继续向下执行 */
} else {
    /* 失败,#GP异常产生,转去处理异常 */
}


*--esp = EFLAGS;          /* eflags 寄存器入栈 */

     /* 分别将 NT、NT、RF及VM标志位清0 */
EFLAGS.TF = 0;           
EFLAGS.NT = 0;
EFLAGS.RF = 0;
EFLAGS.VM = 0;

if (gate_descriptor.type == I_GATE32) { /* 假如是中断门符 */
EFLAGS.IF = 0;          /* 也将IF标志位清0,屏蔽响应中断 */
     }
            
     *--esp = CS;              /* 当前段选择子入栈 */
     *--esp = EIP;             /* 当前EIP 入栈 */
  CS = target_selector;      /* 加载目标代码段 */
CS. RPL = DPLs;            /* 改变当前执行权限级别 */
EIP = gate_descriptor.offset; /* 加载进入EIP */

/* 执行中断例程 */
goto target_descritptor.base + gate_descriptor.offset; 

} else {
/* 失败,#GP 异常产生,转去处理异常 */
}

■ 堆栈的切换


控制权发生转移后,处理器自动进行相应的堆栈切换。
1、 当转向到同权限级别的代码时,不会进行堆栈级别的调整,也就是不进行堆栈切换。
2、 当转向高权限级别时,将发生相应级别的堆栈切换。从TSS块获取相应级别的stack结构。

例:假如当前运行级别CPL为2时,发生了向0级代码转移时:

TSS tss = TR.base;         /* 从TR寄存器中获取TSS 块 */
CPL = 2;                 /* 当前运行级别为2 级*/
DPL = 0;                 /* 目标代码需要级别为 0 级 */

/* 根据目标代码需要的级别进行选取相应的权限级别的stack结构 */
switch ( DPL) {
case 0:
     SS = tss.ss0;
     ESP = tss.esp0;
     break;
case 1:
     SS = tss.ss1;
     ESP = tss.esp1;
     break;
case 2:
     SS = tss.ss2;
     ESP = tss.esp2;
     break;
}
*--esp = SS;          /* 保存旧的stack结构 */
*--esp = ESP;
/* 然后再作相当的保存工作,如保存参数等 */
*--esp = CS;         /* 最后保存返回地址 */
*--esp = EIP;

■ 控制权的返回


当目标代码执行完毕,需要返回控制权给原代码时,将产生返回控制权行为。返回控制权行为,比转移控制权行为简单得多。因为,一切条件已经在交出控制权之前准备完毕,返回时仅需出栈就行了。

1、 near call 的返回
近调用情况下,段不改变,即CS不改变,权限级别不改变。仅需从栈中pop返回地址就可以了。


2、 直接控制权转移的返回(far call或far jmp)
   直接控制权的转移是一种不改变当前运行级别的行为。只是发生跨段的转移。这时,CS被从栈中pop出来的CS值加载进去,处理器会检查CPL与这个pop出来的选择子中的RPL进行检查,相符则返回。不相符则发生 #GP异常。
  
总结:假如当前运行的目标代码执行完毕后,将要返回。这时CPL为2

CPL = 2;      /* 当前代码运行级别为 2 */
… …
EIP = *esp++;   /* pop出原EIP 值 */
CS = *esp++;    /* pop出原CS值 */
if ( CPL == CS. RPL) {  
/* CS. RPL 代表是原来的运行级别,与 CPL相符则返回 */
return ;
} else {
/* #GP异常产生,执行异常处理 */
}

3、 利用各种门符进行向高权限代码转移后的返回
从高权限代码返回低权限代码,须从stack中pop出原来的stack结构。这个stack结构属于低权限代码的stack结构。然后直接pop 出原返回地址就可以了。

总结:

CPL = 0;        /* 当前运行级别为 0 级 */
… …
EIP = *esp++;    /* 恢复原地址 */
CS = *esp++;     /* 恢复原地址及运行级别 */

ESP = *esp ++;     /* 恢复原stack结构 */
SS = *esp++;     /* 恢复原stack 结构,同时恢复了原stack访问级别 */
return ;       /* 返回 */
  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 这段视频介绍了x86汇编语言下,如何从实模式切换到保护模式。 首先,视频介绍了实模式的特点和局限性。实模式下,CPU只能寻址1MB的内存空间,同时所有的程序都需要运行在同一个特权级下,对于大型程序和操作系统而言非常不利。 接着,视频介绍了如何切换到保护模式保护模式下,CPU可以通过基地址和段选择器来访问4GB的内存空间,同时程序可以运行在不同的特权级下。切换到保护模式的关键步骤包括:设置GDT和IDT表、开启A20线、设置CR0寄存器、跳转到保护模式代码段等。 最后,视频演示了如何编写汇编代码来实现保护模式的切换。代码包括定义GDT和IDT表、开启A20线的函数、设置CR0寄存器的函数,以及跳转到保护模式代码段的函数。通过这段视频的学习,我们了解了从实模式到保护模式的切换原理和实现方法,对于深入理解操作系统和底层编程有着重要的意义。 ### 回答2: x86汇编语言是计算机的底层语言,包括实模式和保护模式两种运行模式。实模式是早期计算机的一种运行模式,它的内存访问方式简单,容易理解。在实模式下,整个物理内存空间可以直接寻址,但同时也带来一些安全问题。为了保护内存和提高计算机运行的稳定性,最终出现了保护模式保护模式是x86汇编语言的一种运行模式,它可以让计算机利用内存管理单元(MMU)来对内存进行隔离和保护。在保护模式下,内存空间被分为多个段,并为每个段赋予相应的权限和特权级别。这样可以有效保护计算机的内存空间,避免程序之间相互侵入和破坏。 保护模式的运行需要经过一系列的初始化,包括:打开地址线扩展、禁用中断、打开分页等操作。这些操作可以通过设置控制寄存器和描述符表来实现。 在保护模式下,程序运行的特权级别由RPL(Requested Privilege Level)和CPL(Current Privilege Level)两个标志位来控制。CPL位于代码段和数据段的选择子中,代表当前运行程序的权限级别,而RPL位于代码选择子和数据选择子中,用来判断程序是否有权访问对应的段。 在保护模式下,虚拟地址需要经过地址转换才能转换成物理地址,这需要使用页表来对虚拟地址进行映射。页表和页目录存储在内存中,需要通过控制寄存器来取出相应的值进行地址转换。 总之,x86汇编语言的实模式和保护模式都有各自的特点和应用场合,学习x86汇编语言需要深入理解这两种运行模式的原理和实现过程。 ### 回答3: x86汇编语言是计算机硬件操作指令的编程语言,是本质上依赖于CPU架构和指令集的语言。根据其中对内存的处理方式的不同,x86汇编语言可以分为实模式和保护模式。 实模式是指CPU直接访问物理内存,地址总线是20位,内存寻址空间最大为1MB。实模式下的程序可以访问系统资源,例如中断处理、内存读写等操作。但实模式下内存空间过小,程序出错容易导致系统崩溃。 保护模式是一种更加灵活和安全的工作状态,地址总线扩展到了32位,理论内存访问空间可以达到4GB以上。保护模式下内存被划分为多个段,程序可以在相互独立的段之间进行操作,可以在段级别上分配权限,从而提高代码的安全性。同时,保护模式也支持虚存技术,通过硬件的支持,可以将程序的实际运行数据放入物理内存和虚拟内存的组合体内,从而可以利用硬盘来进行虚拟内存的支持,使得多个程序在同一系统上运行时不会相互干扰,同时提高系统的稳定性。 因此,在现代操作系统上,保护模式是必需的。x86汇编语言需要不同的功能选项来在实模式和保护模式之间进行转换。在视频中,我们可以学习如何从实模式切换到保护模式,并掌握如何在保护模式下使用不同的功能选项。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值