linux内核1-GNU汇编入门_X86-64&ARM(下)

简介: linux内核1-GNU汇编入门_X86-64&ARM(下)

3.8 定义复杂函数


复杂函数必须能够调用其它函数,且能够计算任意复杂度的表达式,还能正确地返回到调用者中。考虑下面的示例,具有3个参数和2个局部变量的函数:

.global func
func:
    pushq %rbp          # 保存基址指针
    movq %rsp, %rbp     # 设置新的基址指针
    pushq %rdi          # 第一个参数压栈
    pushq %rsi          # 第二个参数压栈
    pushq %rdx          # 第三个参数压栈
    subq $16, %rsp      # 给2个局部变量分配栈空间
    pushq %rbx          # 保存应该被调用者保存的寄存器
    pushq %r12
    pushq %r13
    pushq %r14
    pushq %r15
    ### 函数体 ###
    popq %r15           # 恢复被调用者保存的寄存器
    popq %r14
    popq %r13
    popq %r12
    popq %rbx
    movq %rbp, %rsp     # 复位栈指针
    popq %rbp           # 恢复之前的基址指针
    ret                 # 返回到调用者

这个函数需要追踪的信息比较多:函数参数,返回需要的信息,局部变量空间等等。考虑到这个目的,我们使用基址指针寄存器%rbp。栈指针%rsp指向新栈的栈顶,而%rbp指向新栈的栈底。%rsp%rbp之间的这段空间就是函数调用的栈帧。

还有就是,函数需要调用寄存器计算表达式,也就是上面的%rbx%r12%r13%r14%r15%rbp%rsp。这些寄存器可能已经在调用者函数体内被使用,所以我们不希望被调用函数内部破坏这些寄存器的值。这就需要在被调用函数中保存这些寄存器的值,在返回之前,再恢复这些寄存器之前的值。

下图是func函数的栈布局:

图3 X86-64栈布局示例

基址指针寄存器(%rbp)位于栈的起始处。所以,在函数体内,完全可以使用基址变址寻址方式,去引用参数和局部变量。参数紧跟在基址指针后面,所以参数0的位置就是-8(%rbp),参数1的位置就是-16(%rbp),依次类推。接下来是局部变量,位于-32(%rbp)地址处。然后保存的寄存器位于-48(%rbp)地址处。栈指针指向栈顶的元素。

下面是一个复杂函数的C代码示例:

compute: function integer ( a: integer, b: integer, c: integer ) =
{
    x:integer = a+b+c;
    y:integer = x*5;
    return y;
}

将其完整地转换成汇编代码,如下所示:

.global compute
compute:
    ##################### preamble of function sets up stack
    pushq %rbp              # save the base pointer
    movq %rsp, %rbp         # set new base pointer to rsp
    pushq %rdi              # save first argument (a) on the stack
    pushq %rsi              # save second argument (b) on the stack
    pushq %rdx              # save third argument (c) on the stack
    subq $16, %rsp          # allocate two more local variables
    pushq %rbx              # save callee-saved registers
    pushq %r12
    pushq %r13
    pushq %r14
    pushq %r15
    ######################## body of function starts here
    movq -8(%rbp), %rbx     # load each arg into a register
    movq -16(%rbp), %rcx
    movq -24(%rbp), %rdx
    addq %rdx, %rcx         # add the args together
    addq %rcx, %rbx
    movq %rbx, -32(%rbp)    # store the result into local 0 (x)
    movq -32(%rbp), %rbx    # load local 0 (x) into a register.
    movq $5, %rcx           # load 5 into a register
    movq %rbx, %rax         # move argument in rax
    imulq %rcx              # multiply them together
    movq %rax, -40(%rbp)    # store the result in local 1 (y)
    movq -40(%rbp), %rax    # move local 1 (y) into the result
    #################### epilogue of function restores the stack
    popq %r15               # restore callee-saved registers
    popq %r14
    popq %r13
    popq %r12
    popq %rbx
    movq %rbp, %rsp         # reset stack to base pointer.
    popq %rbp               # restore the old base pointer
    ret                     # return to caller

下面有转换为汇编的代码段。代码是正确的,但不是完美的。事实证明,这个函数不需要使用寄存器%rbx%r15,所以不需要保存和恢复他们。同样的,我们也可以把参数就保留在寄存器中而不必把它们压栈。结果也不必存入局部变量y中,而是可以直接写入到%rax寄存器中。这其实就是编译器优化功能的一部分。


4 ARM汇编


最新的ARM架构是ARMv7-A(32位)和ARMv8-A(64位)。本文着重介绍32位架构,最后讨论一下64位体系架构的差异。

ARM是一个精简指令计算机(RISC)架构。相比X86,ARM使用更小的指令集,这些指令更易于流水线操作或并行执行,从而降低芯片复杂度和能耗。但由于一些例外,ARM有时候被认为是部分RISC架构。比如,一些ARM指令执行时间的差异使流水线不完美,为预处理而包含的桶形移位器引入了更复杂的指令,还有条件指令减少了一些潜在指令的执行,导致跳转指令的使用减少,从而降低了处理器的能耗。我们侧重于编写编译器常用到的指令,更复杂的内容和程序语言的优化留到以后再研究。


4.1 寄存器和数据类型


32位ARM架构拥有16个通用目的寄存器,r0~r15,使用约定如下所示:

名称 别名 目的
r0 - 通用目的寄存器
r1 - 通用目的寄存器
... - -
r10 - 通用目的寄存器
r11 fp 栈帧指针,栈帧起始地址
r12 ip 内部调用临时寄存器
r13 sp 栈指针
r14 lr 链接寄存器(返回地址)
r15 pc 程序计数器

除了通用目的寄存器,还有2个寄存器:当前程序状态寄存器(CPSR)和程序状态保存寄存器(SPSR),它们不能被直接访问。这两个寄存器保存着比较运算的结果,以及与进程状态相关的特权数据。用户态程序不能直接访问,但是可以通过一些操作的副作用修改它们。

ARM使用下面的后缀表示数据大小。它们与X86架构不同!如果没有后缀,汇编器假设操作数是unsigned word类型。有符号类型提供正确的符号位。任何word类型寄存器不会再有细分且被命名的寄存器。

后缀 数据类型 大小
B Byte 8 位
H Halfword 16 位
W WORD 32 位
- Double Word 64 位
SB Signed Byte 8 位
SH Signed Halfword 16 位
SW Signed Word 32 位
- Double Word 64 位


4.2 寻址模式


与X86不同,ARM使用两种不同的指令分别搬运寄存器之间、寄存器与内存之间的数据。MOV拷贝寄存器之间的数据和常量,而LDR和STR指令拷贝寄存器和内存之间的数据。

MOV指令可以把一个立即数或者寄存器值搬运到另一个寄存器中。ARM中,用#表示立即数,这些立即数必须小于等于16位。如果大于16位,就会使用LDR指令代替。大部分的ARM指令,目的寄存器在左,源寄存器在右。(STR是个例外)。具体格式如下:

模式 示例
立即数 MOV r0, #3
寄存器 MOV r1, r0

MOV指令后面添加标识数据类型的字母,确定传输的类型和如何传输数据。如果没有指定,汇编器假定为word。

从内存中搬运数据使用LDR和STR指令,它们把源寄存器和目的寄存器作为第一个参数,要访问的内存地址作为第二个参数。简单情况下,使用寄存器给出地址并用中括号[]标记:

LDR Rd, [Ra]
STR Rs, [Ra]

Rd,表示目的寄存器;Rs,表示源寄存器;Ra,表示包含内存地址的寄存器。(必须要注意内存地址的类型,可以使用任何内存地址访问字节数据,使用偶数地址访问半字数据等。)

ARM寻址模式

模式 示例
文本 LDR Rd, =0xABCD1234
绝对地址 LDR Rd, =label
寄存器间接寻址 LDR Rd, [Ra]
先索引-立即数 LDR Rd, [Ra, #4]
先索引-寄存器 LDR Rd, [Ra, Ro]
先索引-立即数&Writeback LDR Rd, [Ra, #4]!
先索引-寄存器&Writeback LDR Rd, [Ra, Ro]!
后索引-立即数 LDR Rd, [Ra], #4
后索引-寄存器 LDR Rd, [Ra], Ro

如上表所示,LDR和STR支持多种寻址模式。首先,LDR能够加载一个32位的文本值(或绝对地址)到寄存器。(完整的解释请参考下一段内容)。与X86不同,ARM没有可以从一个内存地址拷贝数据的单指令。为此,首先需要把地址加载到一个寄存器,然后执行一个寄存器间接寻址:

LDR r1, =x
LDR r2, [r1]

为了方便高级语言中的指针、数组、和结构体的实现,还有许多其它可用的寻址模式。比如,先索引模式可以添加一个常数(或寄存器)到基址寄存器,然后从计算出的地址加载数据:

LDR r1, [r2, #4] ;  # 载入地址 = r2 + 4
LDR r1, [r2, r3] ;  # 载入地址 = r2 + r3

有时候可能需要在把计算出的地址中的内容读取后,再把该地址写回到基址寄存器中,这可以通过在后面添加感叹号!实现。

LDR r1, [r2, #4]! ; # 载入地址 = r2 + 4 然后 r2 += 4
LDR r1, [r2, r3]! ; # 载入地址 = r2 + r3 然后 r2 += r3

后索引模式做相同的工作,但是顺序相反。首先根据基址地址执行加载,然后基址地址再加上后面的值:

LDR r1, [r2], #4 ;  # 载入地址 = r2 然后 r2 += 4
LDR r1, [r2], r3 ;  # 载入地址 = r2 然后 r2 += r3

通过先索引和后索引模式,可以使用单指令实现像我们经常写的C语句b = a++。STR使用方法类似。

在ARM中,绝对地址以及其它长文本更为复杂些。因为每条指令都是32位的,因此不可能将32位的地址和操作码(opcode)一起添加到指令中。因此,长文本存储在一个文本池中,它是程序代码段中一小段数据区域。使用与PC寄存器相关的加载指令,比如LDR,加载文本类型数据,这样的文本池可以引用靠近load指令的±4096个字节数据。这导致有一些小的文本池散落在程序中,由靠近它们的指令使用。

ARM汇编器隐藏了这些复杂的细节。在绝对地址和长文本的前面加上等号=,就代表向汇编器表明,标记的值应该存储在一个文本池中,并使用与PC寄存器相关的指令代替。

例如,下面的指令,把x的地址加载到r1中,然后取出x的值,存入r2寄存器中。

LDR r1, =x
LDR r2, [r1]

下面的代码展开后,将会从相邻的文本池中加载x的地址,然后加载x的值,存入r2寄存器中。也就是,下面的代码与上面的代码是一样的。

LDR r1, .L1
LDR r2, [r1]
B   .end
.L1:
    .word x
.end:


4.3 基本算术运算


ARM的ADDSUB指令,使用3个地址作为参数。目的寄存器是第一个参数,第二、三个参数作为操作数。其中第三个参数可以是一个8位的常数,或者带有移位的寄存器。使能进位的加、减法指令,将CPSR寄存器的C标志位写入到结果中。这4条指令如果分别后缀S,代表在完成时是否设置条件标志(包括进位),这是可选的。

指令 示例
ADD Rd, Rm, Rn
带进位加 ADC Rd, Rm, Rn
SUB Rd, Rm, Rn
带进位减 SBC Rd, Rm, Rn

乘法指令的工作方式与加减指令类似,除了将2个32位的数字相乘能够产生一个64位的值之外。普通的MUL指令舍弃了结果的高位,而UMULL指令把结果分别保存在2个寄存器中。有符号的指令SMULL,在需要的时候会把符号位保存在高寄存器中。

指令 示例
乘法 MUL Rd, Rm, Rn
无符号长整形 UMULL RdHi, RdLo, Rm, Rn
有符号长整形 SMULL RdHi, RdLo, Rm, Rn

ARM没有除法指令,因为它不能在单个流水线周期中执行。因此,需要除法的时候,调用外部标准库中的函数。

逻辑指令在结构上和算术指令非常相似,如下图所示。特殊的是MVN指令,执行按位取反然后将结果保存到目的寄存器。

指令 示例
位与 AND Rd, Rm, Rn
位或 ORR Rd, Rm, Rn
位异或 EOR Rd, Rm, Rn
位置0 BIC Rd, RM, Rn
取反并移动 MVN Rd, Rn


4.4 比较和跳转


比较指令CMP比较2个值,将比较结果写入CPSR寄存器的N(负)和Z(零)标志位,供后面的指令读取使用。如果比较一个寄存器值和立即数,立即数必须作为第二个操作数:

CMP Rd, Rn
CMP Rd, #imm

另外,也可以在算术指令后面添加S标志,以相似的方式更新CPSR寄存器的相应标志位。比如,SUBS指令是两个数相减,保存结果,并更新CPSR。

ARM跳转指令

操作码 意义 操作码 意义
B 无条件跳转 BL 设置lr寄存器为下一条指令的地址并跳转
BX 跳转并切换状态 BLX BL+BX指令的组合
BEQ 相等跳转 BVS 溢出标志设置跳转
BNE 不等跳转 BVC 溢出标志清除跳转
BGT 大于跳转 BHI 无符号>跳转
BGE 大于等于跳转 BHS 无符号>=跳转
BLT 小于跳转 BLO 无符号<跳转
BLE 小于等于跳转 BLS 无符号<=跳转
BMI 负值跳转 BPL >= 0跳转

各种跳转指令参考CPSR寄存器中之前的值,如果设置正确就跳到相应的地址(标签表示)执行。无条件跳转指令就是一个简单的B

比如,从0累加到5:

MOV r0, #0
loop:   ADD r0, r0, 1
        CMP r0, #5
        BLT loop

再比如,如果x大于0,则给y赋值为:10;否则,赋值为20:

LDR r0, =x
        LDR r0, [r0]
        CMP r0, #0
        BGT .L1
.L0:
        MOV r0, #20
        B .L2
.L1:
        MOV r0, #10
.L2:
        LDR r1, =y
        STR r0, [r1]

BL指令用来实现函数调用。BL指令设置lr寄存器为下一条指令的地址,然后跳转到给定的标签(比如绝对地址)处执行,并将lr寄存器的值作为函数结束时的返回地址。BX指令跳转到寄存器中给定的地址处,最常用于通过跳转到lr寄存器而从函数调用中返回。

BLX指令执行的动作跟BL指令一样,只是操作对象换成了寄存器中给定的地址值,常用于调用函数指针,虚函数或其它间接跳转的场合。

ARM指令集的一个重要特性就是条件执行。每条指令中有4位表示16中可能的条件,如果条件不满足,指令被忽略。上面各种类型的跳转指令只是在最单纯的B指令上应用了各种条件而已。这些条件几乎可以应用到任何指令。

例如,假设下面的代码片段,哪个值小就会自加1:

if(a<b) { a++; } else { b++; }

代替使用跳转指令和标签实现这个条件语句,我们可以前面的比较结果对每个加法指令设置条件。无论那个条件满足都被执行,而另一个被忽略。如下面所示(假设a和b分别存储在寄存器r0和r1中):

CMP r0, r1
ADDLT r0, r0, #1
ADDGE r1, r1, #1


4.5 栈


栈是一种辅助数据结构,主要用来存储函数调用历史以及局部变量。按照约定,栈的增长方向是从髙地址到低地址。sp寄存器保存栈指针,用来追踪栈顶内容。

为了把寄存器r0压入栈中,首先,sp减去寄存器的大小,然后把r0存入sp指定的位置:

SUB sp, sp, #4
STR r0, [sp]

或者,可以使用一条单指令完成这个操作,如下所示:

STR r0, [sp, #-4]!

这儿,使用了先索引并write-back的寻址方式。也就是说,sp先减4,然后把r0的内容存入sp-4指向的地址处,然后再把sp-4写入到sp中。

ARM调用习惯总结

  1. 前4个参数存储在r0、r1、r2 和r3中;
  2. 其余的参数按照相反的顺序存入栈中;
  3. 如果需要,调用者必须保存r0-r3和r12;
  4. 调用者必须始终保存r14,即链接寄存器;
  5. 如果需要,被调用者必须保存r4-r11;
  6. 结果存到r0寄存器中。

PUSH伪指令可以压栈的动作,还可以把任意数量的寄存器压入栈中。使用花括号{}列出要压栈的寄存器列表:

PUSH {r0,r1,r2}

出栈的动作正好与压栈的动作相反:

LDR r0, [sp]
ADD sp, sp, #4

使用后索引模式

LDR r0, [sp], #4

使用POP指令弹出一组寄存器:

POP {r0,r1,r2}

与X86不同的是,任何数据项(从字节到双word)都可以压入栈,只要遵守数据对齐即可。


4.6 函数调用


《The ARM-Thumb Procedure Call Standard》描述了ARM的寄存器调用约定,其摘要如下:

ARM寄存器分配:

寄存器 目的 谁保存
r0 参数0 不保存
r1 参数1 调用者保存
r2 参数2 调用者保存
r3 参数3 调用者保存
r4 临时 被调用者保存
... ... ...
r10 临时 被调用者保存
r11 栈帧指针 被调用者保存
r12 内部过程 调用者保存
r13 栈指针 被调用者保存
r14 链接寄存器 调用者保存
r15 程序计数器 保存在r14

为了调用一个函数,把参数存入r0-r3寄存器中,保存lr寄存器中的当前值,然后使用BL指令跳转到指定的函数。返回时,恢复lr寄存器的先前值,并检查r0寄存器中的结果。

比如,下面的C语言代码段:

int x=0;
int y=10;
int main() {
    x = printf("value: %d\n",y);
}

其编译后的ARM汇编格式为:

.data
    x: .word 0
    y: .word 10
    S0: .ascii "value: %d\012\000"
.text
    main:
        LDR r0, =S0         @ 载入S0的地址
        LDR r1, =y          @ 载入y的地址
        LDR r1, [r1]        @ 载入y的值
        PUSH {ip,lr}        @ 保存ip和lr寄存器的值
        BL printf           @ 调用printf函数
        POP {ip,lr}         @ 恢复寄存器的值
        LDR r1, =x          @ 载入x的地址
        STR r0, [r1]        @ 把返回的结果存入x中
.end


4.7 定义叶子函数


因为使用寄存器传递函数参数,所以编写一个不调用其它函数的叶子函数非常简单。比如下面的代码:

square: function integer ( x: integer ) =
{
    return x*x;
}

它的汇编代码可以非常简单:

.global square
square:
        MUL r0, r0, r0  @ 参数本身相乘
        BX lr           @ 返回调用者

但是,很不幸,对于想要调用其他函数的函数,这样的实现就无法工作,因为我们没有正确建立函数使用的栈。所以,需要一种更为复杂的方法。


4.8 定义复杂函数


复杂函数必须能够调用其它函数并计算任意复杂度的表达式,然后正确地返回到调用者之前的状态。还是考虑具有3个参数和2个局部变量的函数:

func:
        PUSH {fp}           @ 保存栈帧指针,也就是栈的开始
        MOV fp, sp          @ 设置新的栈帧指针
        PUSH {r0,r1,r2}     @ 参数压栈
        SUB sp, sp, #8      @ 分配2个局部变量的栈空间
        PUSH {r4-r10}       @ 保存调用者的寄存器
        @@@ 函数体 @@@
        POP {r4-r10}        @ 恢复调用者的寄存器
        MOV sp, fp          @ 复位栈指针
        POP {fp}            @ 恢复之前的栈帧指针
        BX lr               @ 返回到调用者

通过上面的代码,我们可以看出,不管是ARM架构的函数实现还是X86架构系列的函数实现,本质上都是一样的,只是指令和寄存器的使用不同。

640.png


图4 ARM栈布局示例

同样考虑下面一个带有表达式计算的复杂函数的C代码:

compute: function integer
        ( a: integer, b: integer, c: integer ) =
{
    x: integer = a+b+c;
    y: integer = x*5;
    return y;
}

将其完整地转换成汇编代码,如下所示:

.global compute
compute:
    @@@@@@@@@@@@@@@@@@ preamble of function sets up stack
    PUSH {fp}               @ save the frame pointer
    MOV fp, sp              @ set the new frame pointer
    PUSH {r0,r1,r2}         @ save the arguments on the stack
    SUB sp, sp, #8          @ allocate two more local variables
    PUSH {r4-r10}           @ save callee-saved registers
    @@@@@@@@@@@@@@@@@@@@@@@@ body of function starts here
    LDR r0, [fp,#-12]       @ load argument 0 (a) into r0
    LDR r1, [fp,#-8]        @ load argument 1 (b) into r1
    LDR r2, [fp,#-4]        @ load argument 2 (c) into r2
    ADD r1, r1, r2          @ add the args together
    ADD r0, r0, r1
    STR r0, [fp,#-20]       @ store the result into local 0 (x)
    LDR r0, [fp,#-20]       @ load local 0 (x) into a register.
    MOV r1, #5              @ move 5 into a register
    MUL r2, r0, r1          @ multiply both into r2
    STR r2, [fp,#-16]       @ store the result in local 1 (y)
    LDR r0, [fp,#-16]       @ move local 1 (y) into the result
    @@@@@@@@@@@@@@@@@@@ epilogue of function restores the stack
    POP {r4-r10}            @ restore callee saved registers
    MOV sp, fp              @ reset stack pointer
    POP {fp}                @ recover previous frame pointer
    BX lr                   @ return to the caller

构建一个合法栈帧的形式有多种,只要函数使用栈帧的方式一致即可。比如,被调函数可以首先把所有的参数和需要保存的寄存器压栈,然后再给局部变量分配栈空间。(当然了,函数返回时,顺序必须正好相反。)

还有一种常用的方式就是,在将参数和局部变量压栈之前,为被调函数执行PUSH {fp,ip,lr,pc},将这些寄存器压入栈中。尽管这不是实现函数的严格要求,但是以栈回溯的形式为调试器提供了调试信息,可以通过函数的调用栈,轻松地重构程序的当前执行状态。

与前面描述X86_64的示例时一样,这段代码也是有优化的空间的。事实证明,这个函数不需要保存寄存器r4和r5,当然也就不必恢复。同样的,参数我们也不需要非得保存到栈中,可以直接使用寄存器。计算结果可以直接写入到寄存器r0中,不必再保存到变量y中。这其实就是ARM相关的编译器所要做的工作。


4.9 ARM-64位


支持64位的ARMv8-A架构提供了两种扩展模式:A32模式-支持上面描述的32位指令集;A64模式-支持64位执行模式。这就允许64位的CPU支持操作系统可以同时执行32位和64位程序。虽然A32模式的二进制执行文件和A64模式不同,但是有一些架构原理是相同的,只是做了一些改变而已:

  1. 字宽度
    A64模式的指令还是32位大小的,只是寄存器和地址的计算是64位。
  2. 寄存器
    A64具有32个64位的寄存器,命名为x0-x31。x0是专用的0寄存器:当读取时,总是返回0值;写操作无效。x1-x15是通用目的寄存器,x16和x17是为进程间通信使用,x29是栈帧指针寄存器,x30是lr链接寄存器,x31是栈指针寄存器。(程序寄存器(PC)用户态代码不可直接访问)32位的值可以通过将寄存器命名为w#来表示,而不是使用数据类型后缀,在这儿#代表0-31。
  3. 指令
    A64模式的指令大部分和A32模式相同,使用相同的助记符,只是有一点小差异。分支预测不再是每条指令的一部分。相反,所有的条件执行代码必须显式地执行CMP指令,然后执行条件分支指令。LDM/STM指令和伪指令PUSH/POP不可用,必须通过显式地加载和存储指令序列实现。(使用LDP/STP,在加载和存储成对的寄存器时更有效率)。
  4. 调用习惯
    当调用函数的时候,前8个参数被存储到寄存器x0-x7中,其余的参数压栈。调用者必须保留寄存器x9-x15和x30,而被调用者必须保留x19-x29。返回值的标量部分存储到x0中,而返回值的扩展部分存储到x8中。


5 参考


本文对基于X86和ARM架构的汇编语言的核心部分做了阐述,可以满足大部分需要了。但是,如果需要了解各个指令的细节,可以参考下面的文档。

  1. Intel64 and IA-32 Architectures Software Developer Manuals. Intel Corp., 2017. http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html
  2. System V Application Binary Interface, Jan Hubicka, Andreas Jaeger, Michael Matz, and Mark Mitchell (editors), 2013. https://software.intel.com/sites/default/files/article/402129/mpx-linux64-abi.pdf
  3. ARM Architecture Reference Manual ARMv8. ARM Limited, 2017. https://static.docs.arm.com/ddi0487/bb/DDI0487B_b_armv8_arm.pdf.
  4. The ARM-THUMB Procedure Call Standard. ARM Limited, 2000. http://infocenter.arm.com/help/topic/com.arm.doc.espc0002/ATPCS.pdf.

回到顶部

相关文章
|
4天前
|
Linux
Linux(1)arm64根目录扩容
Linux(1)arm64根目录扩容
9 0
|
16天前
|
Linux C语言
Linux内核队列queue.h
Linux内核队列queue.h
|
1月前
|
Shell Linux C语言
【Shell 命令集合 系统设置 】⭐Linux 卸载已加载的内核模块rmmod命令 使用指南
【Shell 命令集合 系统设置 】⭐Linux 卸载已加载的内核模块rmmod命令 使用指南
29 1
|
4天前
|
Ubuntu Linux 数据安全/隐私保护
Linux(7)Ubuntu20.04 arm64安装Docker
Linux(7)Ubuntu20.04 arm64安装Docker
18 0
|
9天前
|
算法 Linux 调度
深入理解Linux内核的进程调度机制
【4月更文挑战第17天】在多任务操作系统中,进程调度是核心功能之一,它决定了处理机资源的分配。本文旨在剖析Linux操作系统内核的进程调度机制,详细讨论其调度策略、调度算法及实现原理,并探讨了其对系统性能的影响。通过分析CFS(完全公平调度器)和实时调度策略,揭示了Linux如何在保证响应速度与公平性之间取得平衡。文章还将评估最新的调度技术趋势,如容器化和云计算环境下的调度优化。
|
14天前
|
算法 Linux 调度
深度解析:Linux内核的进程调度机制
【4月更文挑战第12天】 在多任务操作系统如Linux中,进程调度机制是系统的核心组成部分之一,它决定了处理器资源如何分配给多个竞争的进程。本文深入探讨了Linux内核中的进程调度策略和相关算法,包括其设计哲学、实现原理及对系统性能的影响。通过分析进程调度器的工作原理,我们能够理解操作系统如何平衡效率、公平性和响应性,进而优化系统表现和用户体验。
20 3
|
18天前
|
Linux 计算机视觉
Linux交叉编译opencv并移植ARM端
通过以上步骤,你可以在Linux上交叉编译OpenCV,并将生成的库文件和头文件移植到ARM平台上,从而在ARM上使用OpenCV。 买CN2云服务器,免备案服务器,高防服务器,就选蓝易云。百度搜索:蓝易云
36 0
|
21天前
|
负载均衡 算法 Linux
深度解析:Linux内核调度器的演变与优化策略
【4月更文挑战第5天】 在本文中,我们将深入探讨Linux操作系统的核心组成部分——内核调度器。文章将首先回顾Linux内核调度器的发展历程,从早期的简单轮转调度(Round Robin)到现代的完全公平调度器(Completely Fair Scheduler, CFS)。接着,分析当前CFS面临的挑战以及社区提出的各种优化方案,最后提出未来可能的发展趋势和研究方向。通过本文,读者将对Linux调度器的原理、实现及其优化有一个全面的认识。
|
21天前
|
Ubuntu Linux
Linux查看内核版本
在Linux系统中查看内核版本有多种方法:1) 使用`uname -r`命令直接显示版本号;2) 通过`cat /proc/version`查看内核详细信息;3) 利用`dmesg | grep Linux`显示内核版本行;4) 如果支持,使用`lsb_release -a`查看发行版及内核版本。
36 6
|
24天前
|
Linux 内存技术
Linux内核读取spi-nor flash sn
Linux内核读取spi-nor flash sn
18 1