kernel源码

0 教材

kernel源码对应的参考教材是《Linux内核完全注释:基于0.11内核(修正版V3.0).pdf》这本书,基于0.11内核。已上传到git私有仓库中

(一)AT&T汇编语法

1 寄存器引用

引用寄存器要在寄存器号前面加%,如mov %eax,%ebx

2 操作数顺序

操作数排列是从源(左)到目的(右),如mov %eax,%ebx

3 常数/立即数的格式

使用立即数,要在数前面加上$,如mov $4,%ebx

符号常数直接引用 如mov value,%ebx

引用符号地址在符号前加$,如mov $value,%ebx

4 操作数长度

操作数长度用加在指令后的符号表示

b(byte),w(word),l(long) 如movw %ax,%bx

5 转移和跳转指令

绝对转移和调用指令(jmp/call)的操作数前要加上*作为前缀。

远转移指令和远调用指令的操作码为ljump和lcall,而intel汇编格式中则为jmp far和call far

比如,AT&T格式

ljump $section,$offset
lcall $section,$offset

Intel格式

jmp far section:offset
call far section:offset

远程返回指令

lret $stack_adjust //AT&T格式
ret far stack_adjust //Intel格式

6 寻址方式

section:disp(base,index,scale)表示,计算方法是:base+index*scale+disp

对应的Intel汇编格式:section:[base+index*scale+disp]

例子:

movl -4(%ebp),%eax                 mov eax,[ebp-4] //ebp减去4放到eax中
movl array(,%eax,4),%eax          mov eax,[eax*4+array] //eax*4+array,放到eax中
movw array(%ebx,%eax,4),%cx       mov cx,[ebx+4*eax+array] //ebx+4*eax+array,放到cx中
movb $4,%fs:(%eax)                 mov fs:eax,4 //把立即数4放到fs段偏移为eax的地址

 

7 嵌入式汇编

参考:https://blog.csdn.net/yt_42370304/article/details/84982864

_asm_("asm statements":outputs:inputs:registers-modified);

举例

_asm_("pushl %%eax \n\t"
"movl $0,%%eax \n\t"
"popl %eax");

这段代码没有什么意义,纯粹是为了演示。而且省略了outputs、inputs等参数。有两个%%原因是编译嵌入式汇编语句的时候,会去掉一个%。

 

例子:

{register char _res;\
asm("push %%fs\n\t"
"movw %%ax,%%fs\n\t"
movb %%fs:%2,%%al\n\t"
pop %%fs"
:"=a"(_res):"0"(seg),"m"(*(addr)));\
_res;}

(注:在嵌入式汇编中,"a"、"b"、"c"、"d"分别表示eax、ebx、ecx和edx。详情见https://blog.csdn.net/yt_42370304/article/details/84982864

在这个例子中首先定义一个寄存器变量_res

接着是一个嵌入式汇编语句,先看输出寄存器 "=a"(_res) ,=表示是一个输出寄存器,a表示eax,意思是,输出之后把eax中的内容赋给_res

输入寄存器: "0"(seg),"m"(*(addr)) ,seg和addr是前面定义的变量,代码里没表现出来。"0"表示和前面输出寄存器同一位置的寄存器,输出寄存器是eax,所以这里也使用eax。"0"(seg)表示把seg赋给eax,"m"表示一个内存地址,"m"(*(addr))表示把addr赋给一个内存地址。

输出寄存器和输入寄存器按照编号0-9排序的,所以"=a"(_res)序号是0,"0"(seg)是1,"m"(*(addr))是2。

我们再看主体,

push %%fs把fs压入堆栈

movw %%ax,%%fs把ax中的内容(即seg变量)放到fs变量中。

movb %%fs:%2,%%al //%2表示序号2也就是"m"(*(addr)),fs所指向的段,也就是addr所指向的地址取1字节放到al中

最后弹出fs

返回_res

 

例子:

int main(){
int a1=10,b1=0;
_asm_("movl %1,%%eax;\\n\\r"
"movl %%eax,%%ecx;"
:"=a"(b1)
:"b"(a1)
:"%eax");
printf("Result:%d,%d\\n",a1,b1);}

 

 8 伪指令

.开头的为伪指令

.section .data //表示数据段,可读写
//.data段定义的数据
.section .text //表示代码段,只读可执行
//.text段相关代码
.globl aaa //我们首先会把.s文件编译成.o文件。然后使用链接器链接这些.o文件,.globl aaa表示链接时,aaa对连接器可见

例子

movl $1, %eax
movl $4, %ebx
int $0x80

int为软中断,int $0x80,在linux内核中$0x80为系统调用,上面两条movl为传给该系统调用的参数。eax的值是系统调用号,1表示_exit系统调用,ebx的值则是传给_exit系统调用的参数,也就是退出状态。_exit这个系统调用会终止掉当前进程,而不会返回它继续执行

例子2

.section .data //声明数据段
data_items:
.long 3,67,34,222,45,75,54,34,44,33,22,11,66,0 //.long声明一组数,数组的首地址作为data_items符号所代表的地址
.section .text //声明代码段
.globl _start //声明globl标签_start,表示链接器可访问到
_start:
movl $0, %edi //把立即数0放入edi,edi存放下标供循环中使用
movl data_items(,%edi,4), %eax //把edi*4+data_items的值放入eax,eax存放数组当前元素
movl %eax, %ebx //把eax放入ebx
start_loop: //开始循环
cmpl $0, %eax //比较eax是否为0,为0则跳到loop_exit,(因为data_items最后一个元素为0,以此标识是否遍历完)
je loop_exit //为0则跳到loop_exit
incl %edi //edi增1
movl data_items(, %edi,4), %eax //把edi*4+data_items的值放入eax,eax存放数组当前元素
cmpl %ebx, %eax
jle start_loop //jle也是一个条件跳转指令,le表示less than or equal
movl %eax, %ebx
jmp start_loop //jmp是一个无条件跳转指令
loop_exit:
mov $1, %eax
int $0x80 //触发中断,在linux内核中$0x80为系统调用,系统调用类型为eax中的值1(标识_exit系统调用),参数为eax2

 

(二)IO端口寻址

IO端口就是一个编号,通过IO端口我们可以读写IO设备。这些编号基本上是定死在内核代码中的。

IO端口的编址分为统一编址和独立编址。

统一编址的原理是把IO控制器的地址归入存储器寻址地址空间范围,因此这种编址方式也称为存储器映射编址,CPU访问IO控制器就像访问内存一样,使用的指令页一样。

IO端口独立编址是把IO控制器的寻址空间单独作为一个独立的地址空间对待。每个IO控制器端口有一个IO地址与之对应,并且使用专门的IO指控来访问端口。

1 IO端口地址分配

对于独立编址,其地址分配可以在/proc/ioports中查看。一般情况是:

 

 

(三)实模式和保护模式

实模式体现在内核中用到的地址都是真实物理地址。也就是说段基址:段内偏移地址产生的逻辑地址就是物理地址。也就是说程序员可见的地址就是真实的内存地址。

在内核启动时,开始阶段都是以实模式启动,然后再切换到保护模式。

posted @ 2022-02-22 23:25  zhenjingcool  阅读(281)  评论(0编辑  收藏  举报