· 汇编Intel与AT&T
区别
1.在intel格式中大多使用大写字母,而在AT&T格式中都使用小写字母。
2.在AT&T格式中,寄存器名要加上'%'最为前缀,intel不用。
3.指令顺序。Intel目标在前,源在后。AT&T相反。
4.在AT&T格式中,访问指令的操作数大小由操作码名称的最后字母来决定(b=8,w=16,l=32。)。而Intel格式中,在操作数前加上“BYTEPTR”,"
PTR","DWORDPTR"
5.在AT&T格式中,直接操作数要加$做前缀。Intel不用。
6.在AT&T格式中,绝对转移或调用指令jump/call的操作数,要加上"*"作为前缀。而Intel不用。
7.远程的转移指令和子程序调用指令的操作吗名称,在AT&T格式中为“ljmp”和“lcall”,而在Intel格式中,则为"JMPFAR"和"CALLFAR"。
(INTEL)
CALLFARSECTION:OFFSET
JMPFARSECTION:OFFSET
(AT&T)
lcall$section,$offset
ljmp$section,$offset
8.间接寻址的一般格式,两者的区别如下:
SECTION:[BASE+INDEX*SCALE+DISP](intel)
section:disp(base,index,scale)(AT&T)
●操作数的长度用加在指令后的符号表示b(byte, 8-bit), w(word, 16-bits), l(long, 32-bits),如“movb %al, %bl”,“movw %ax, %bx”,“movl %eax, %ebx ”。
如果没有指定操作数长度的话,编译器将按照目标操作数的长度来设置。比如指令“mov %ax, %bx”,由于目标操作数bx的长度为word,那么编译器将把此指令等同于“movw %ax, %bx”,指令“mov $4, %ebx”等同于指令“movl $4, %ebx”,“push %al”等同于“pushb %al”。对于没有指定操作数长度,但编译器又无法猜测的指令,编译器将会报错,比如指令“push $4”。
●bootsect和setup都是独立编译链接的,所以其中的标号都是以地址0为基础的(经调试可知sector地址是0x13d,mesg地址是0x13f,root_dev地址是0x1fc等等)。和arm不同的是x86有段寄存器这个东西,所以如果代码被移动到实际物理地址7c00,只要设置cs=0x7c0就ok,所有指令和标号的地址都是以“段:偏移”这种形式寻址的,也就是说,编译链接形成的指令和标号的地址都只是这个偏移值。同理代码被复制到0x90000,那么设置cs是9000,代码被复制到0x90200,那么设置cs是9020,这样加上链接时确定的偏移值,就可以找到正确的,在内存中的指令或标号了。
●
●
●下图中的boot和setup本来刚刚编译时带有minix执行文件头信息大小32字节,同样system带有a.out格式头信息大小1024字节。Linux0.11的makefile会调用built把这些头信息去掉后,仅留下代码和数据部分,再组成Image文件,Image文件就如下图所示,boot1扇区,setup4扇区,后面是system。
在bochs中已经实现了
●
RAMDISK=#-DRAMDISK=512
CC=gcc$(RAMDISK)
如果Makefile定义了RAMDISK,那么13行
CC=gcc-DRAMDISK=512
gcc会把命令行上用-D定义的符号常量传递给被编译的程序。
默认情况下,第5行被注释掉了,即“RAMDISK=”。因此此时13行
CC=gcc
●Head.s中
1如果在汇编里面定义.global(全局符号),那么在C语言里面应该用extern声明,以引用该符号。Idt gdt pg_dir都在head.s中extern声明了,详见head.s
2在汇编里面声明的时候,符号前应加下划线
Head.h中
extern的使用:
.h文件中extern int n就是要告诉包含该头文件的.cpp文件,这个变量n是在外面定义的。在所有包含这个.h文件的.cpp文件中,这个变量n只能出现一次定义int n。这个变量在任何一个文件中被改变,所有的文件中的函数都会看到它的变化。
●.global使符号symbol对连接器ld可见。如果您在局部过程中定义符号symbol,其它和此
的局部过程都可以访问它的值。另外,symbol从连接到本过程的另一个文件中的同名符号
获取自己的属性。
两种写法都可以(‘.globl’和‘.global’),以便兼容多种汇编器。
· C语言模块使用汇编模块中的变量:该变量在汇编程序中必须是全局符号,即必须用.global声明,然后在C语言中申明该变量的原型(extern声明),最后在使用时与一般的C变量一样。
●
这里最后的_res就是这个表达式的输出值。
· 关于内联函数inline
内联函数嵌入调用者的代码是一种优化操作,可以避免调用时间和开销,因此只有进行优化编译时才会执行代码的嵌入处理,若编译时没有采用优化选项“-0”,那么内联函数只能像普通的函数来调用处理。
· 80x86中关于函数调用的规则
●
看到自己定义了_entry,就省略了桩函数crt0.s
●GDTR IDTR TR LDTR
当任务切换时,处理器会把新任务的LDT的段选择符和段描述符自动的加载进LDTR中,TR与之相同
●
●CR0
PG分页(CR0的第31位)。置1启用分页,置0不启用分页。当禁用分页
时,所有的线性地址都当作物理地址对待。如果PE标志(CR0的第0
位)没有置1,PG标志将不起作用。实际上,如果在PE标志为0的情
况下,将PG标志置1会产生一个一般保护异常(#GP)。
PE启用保护模式(CR0的第0位)。置1则启用保护模式,置0则启用
实地址模式。这个标志并不直接启用分页机制,只是启用了段级保
护。要是启用分页机制,必须将PE和PG标志都设为1。
●
●
●
一般情况下多数代码段是非一致性的~
●
●
●陷阱门和中断门
这两种门是用来描述中断和异常的入口的。其只能出现在IDT中(对于IDT后面将有详细描述),不能出现在GDT和LDT中。
在整个中断处理程序中,CPU会将TF置成0,以禁止中断处理程序单步执行,并将NT置成0,以在使用IRET指令返回时是回到同一个任务。对于中断门和陷阱门,其就在于对EFLAGS寄存器中IF标志的处理方法不同,当调用中断门时,IF被清除。而调用陷阱门时则不对IF进行处理。
对于386cpu只有在特权级0下才能修改eflags寄存器中的VM和IOPL字段值,同时IF(外部中断允许位)只能由具有设置IOPL字段权利的进程修改
●切换到保护模式(SwitchingtoProtectedMode)
把MSW的PE位(CR0内),将使80386工作在保护模式中。起始的当前特权级为0。段寄存器和在实模式下指向了相同的线性地址(在实模式中,线性地址和物理地址相同)。
在设置了PE位以后,初始代码要立即执行一条JMP指令,以刷新处理器预取指令队列。80386会在使用前预取、解码指令和地址。但是,当切换到保护模式时,预取的指令将不再有效(属于实模式的)。JMP指令将会使处理器罢弃无效的信息。
●
image38.emf
●Linux0.11中没有实现386调用门只有中断门和陷阱门
////设置陷阱门函数。
//参数:n-中断号;addr-中断程序偏移地址。
//&idt[n]对应中断号在中断描述符表中的偏移值;中断描述符的类型是15,特权级是0。
36#defineset_trap_gate(n,addr)\
37_set_gate(&idt[n],15,0,addr)
38
////设置系统调用门函数。
//参数:n-中断号;addr-中断程序偏移地址。
//&idt[n]对应中断号在中断描述符表中的偏移值;中断描述符的类型是15,特权级是3。
39#defineset_system_gate(n,addr)\
40_set_gate(&idt[n],15,3,addr)
41
· 通过中断门或陷阱门的转移
如果中断向量号所指示的门描述符是386中断门或386陷阱门,那么控制转移到当前任务的一个处理程序过程,并且可以变换特权级。与其它调用门的CALL指令一样,从中断门和陷阱门中获取指向处理程序的48位全指针。其中16位选择子是对应处理程序或代码段的选择子,它指示全局描述符表GDT或局部描述符表LDT中的代码段描述符;32位偏移指示处理程序入口点在代码段内的偏移量。
中断门或陷阱门中指示处理程序的选择子必须指向描述一个可执行的代码段的描述符。
中断或异常可以转移到同一特权级或内层特权级。指定的中断或异常处理程序代码段的描述符中的类型及DPL字段,决定了这种同一任务内的转移是否要发生特权级变换(一般是要转到更高级的,因为内核不太可能需要用户级程序的服务)。
。(a)是没有变换特权级和没有出错码的情形;(b)是没有变换特权级有出错码的情形;(c)是变换特权级和没有出错码的内层堆栈的情形。(d)是变换特权级和有出错码的内层堆栈情形。这里每一次堆栈操作是一个双字,CS被扩展成32位。在16位段中亦是如此。
特权级变化时,ss esp eflags cs eip(出错码)压入新栈中
特权级无变化时,eflags cs eip(出错码)压入当前栈中
●对于80x86cpu系统
仅当中断或异常由INTn、INT3或INTO指令产生时,处理器才检验中断或陷
阱门的DPL。此时,CPL必须小于或等于门的DPL。这种限制防止运行于特权
级3的应用程序或进程使用软件中断来访问的重要的异常处理程序,如页故障
处理例程,因为这些例程位于特权级更高(数值较小的特权级)的代码段中。
对于由硬件产生的中断和处理器检测到的异常,处理器则忽略掉中断或陷阱门
的DPL。
●TSS的内层堆栈指针区域中有三个堆栈指针,它们都是48位的全指针(16位的选择子和32位的偏移),分别指向0级、1级和2级堆栈的栈顶(没有指向3级堆栈),依次存放在TSS中偏移为4、12及20开始的位置。
当发生向内层转移时,把适当的内层堆栈指针装入SS及ESP寄存器以变换到内层堆栈,外层堆栈的指针则保存在内层堆栈中。
TSS中没有指向3级堆栈的指针,因为3级是最外层,所以任何一个向内层的转移都不可能转移到3级。
但是,当特权级由内层向外层变换时,并不把内层堆栈的指针保存到TSS的内层堆栈指针区域。实际上,处理器从不向该区域进行写入,除非程序设计者认为改变该区域的值。这表明向内层转移时,总是把内层堆栈认为是一个空栈。
●当前特权级(CPL)。
CPL是当前执行程序或任务的特权级。它存在CS段寄存器和SS段寄存器的第0位和第1位中。一般地,CPL与当前指令所在代码段的特权级相等。当进程的控制
转到一个不同特权级的代码段时,处理器就改变CPL。当访问一致性代码段时,对CPL的处理会略有不同(不改变特权级)
●一致性代码段
可以被任何数值上等于或者大于(特权较低)本一致性代码段DPL的代码访问。
同样,当处理器访问一个与CPL特权级不一样的一致性代码段时,不改变CPL。(CS寄存器中的特权级域不变。即装载新代码段的选择子到CS中时,CPL域保持不变。CPL没有变化,栈也不用切换)(当目标代码段是一致性代码段时,不用检验目标代码段的RPL。)一致性代码段主要用于数学函数库和异常处理程序的代码模块,这些代码对应用程序提供支持,但是并不访问受保护的系统设施。这些模块是操作系统或者管理程序的一部分,但是它们可以以更低特权级来执行。当进程切换到一个一致性代码段时,保留当前进程的CPL可以防止应用程序在一致性代码段的特权级上访问非一致性代码段(它的DPL与当前进程的CPL一致),因而防止它访问更高特权级的数据。
●描述符特权级(DPL)。DPL是段或门的特权级。它存储在段或门的描述符的
DPL域中。一旦当前执行的代码段试图访问一个段或门时,处理器会将那个段
或门的DPL与CPL以及那个段或门的选择子的RPL(本节后面进行描述)进行
比较。根据所访问的段或门的类型,对DPL的解释也会不一样
◆数据段。
DPL指明访问这个段的进程或任务的特权级可以具有的最大数值。比如,如果某个数据段的DPL是1,只有运行在CPL为0或1的进程才可以访问它。
◆非一致性代码段(不使用调用门)。
DPL指明访问这个段的进程或任务应该具有的特权级。比如,某个非一致性代码段的DPL为0,那么只有CPL为0的进程才能访问它。指向非一致性代码段的段描述符的RPL对特权级检验的影响是有限的。RPL必须在数值上小于或者等于调用例程的CPL,才能成功地进行控制转移。当CS寄存器装入一个非一致性代码段的段选择子时,不改变特权级域的值。这意味着,调用例程的CPL被保留。即使该段选择子的RPL与CPL不一样时,也是这样。
· 调用门。
DPL指明能够访问这个调用门的当前进程或者任务的特权级应该具有的最大数值。(这个访问规则同样适用数据段。)
· 通过调用门访问的一致性代码段和非一致性代码段。
DPL指明能访问这个段的进程或任务的特权级的最低数值。比如,一个一致性代码段的DPL为2,那么CPL为0或1的进程就不能访问它。
· 对于一致性代码段,允许低级的任务访问,但是保留原来的CPL值,以防止该任务得到更高级的特权级所造成的危险;访问一个更低级的一致性代码段则不行。对于非一致性代码段是不允许低级任务访问的,更高级的也不行,必须是同级的才可以,但是可以通过门来访问。例如非一致段DPL=0,指向该段的门的DPL是3,那么CPL=0,1,2,3都可以访问这个非一致性段了。
关于数据段,访问一个更高级的数据段是不可以的,访问更低级或同级的都可以。
· TSS。
DPL指明能够访问这个TSS的当前进程或者任务应该具有的特权级的最高数值。(这个访问规则同样适用数据段。)请求特权级(RPL)。RPL是赋给段选择子的取代性特权级,存储在段选择子的第0位和第1位。处理器检验RPL的同时也检验CPL来决定对段的访问是否被允许。即使请求访问的进程或任务有足够的特权(也就是说CPL权限够了——译注)去访问一个段,但是,如果RPL(即指向将要去访问的这个段的段描述符的段选择子中的RPL——译注)的特权级不够,访问会被拒绝。也就是说,如果这个段选择子的RPL在数值上大于CPL,RPL就取代CPL,反之亦然。RPL可用来确保特权代码不代表应用程序去访问的一个段,除非这个应用程序本身有这个段的访问特权。更详细的关于RPL的用途和典型用法参看4.10.4.“检验调用者访问特权(ARPL指令)”。
●特权级检验是在段描述符的段选择子被装入段寄存器时进行的。如果DPL在数值上大于或者等于CPL和RPL,则处理器将段选择子装入段寄存器。否则,处理器会产生一个一般保护异常,不装载段寄存器。
●当堆栈段的段选择子被装入SS寄存器时,也会进行特权级检验。所有与堆栈段相关的特权级必须与CPL匹配,也就是说,CPL、堆栈段的段选择子的RPL和段描述符的DPL必须相等。如果RPL和DPL不等于CPL,就会产生一个一般保护异常(#GP)。
●调用门描述符可以在GDT或LDT中,但是不能在中断描述符表(IDT)中。
IDT中的描述符可以是中断门、陷阱门或任务门。处理器必须先从内部硬件、外部中断控制器或者通过诸如INT、INTO、INT3、BOUND指令收到一个中断向量(中断号),才去访问中断或异常处理程序。中断向量是IDT中门描述符的索引。如果选中的门描述符是中断门或者陷阱门,就如同通过调用门调用过程一样去访问相应的处理程序;如果是任务门,就通过任务切换访问其处理程序。
· 只有CALL指令能变换到内层的特权级,JMP指令只能转移到同级的代码。
●仅当中断或异常由INT n、INT 3 或INTO 指令产生时,处理器才检验中断或陷
阱门的DPL。此时,CPL 必须小于或等于门的DPL。这种限制防止运行于特权
级3 的应用程序或进程使用软件中断来访问的重要的异常处理程序,如页故障
处理例程,因为这些例程位于特权级更高(数值较小的特权级)的代码段中。
对于由硬件产生的中断和处理器检测到的异常,处理器则忽略掉中断或陷阱门
的DPL。
处理器不允许执行流转移到一个特权级低于(数值较大的特权级)当前CPL 的异常和中断处理例程。
对于重要的中断和陷阱处理程序,应设门dpl为0,以防止用户程序乱用int n调用关键的内核程序,同时应该为用户留出系统调用的入口,比如int 0x80 门dpl被设为3,就可以由到这个3的用户程序使用。 见trap.c代码中,大部分中断程序被设置为0级,只有几个被设成3级,这些3级的程序可由任意程序直接利用int n调用。
由硬件产生的中断没有dpl检测。
中断门和陷阱门的处理方法和调用门相似。
1.门描述符的DPL域指定调用进程访问门应当具有的特权级的最大数值。
2. 调用门段选择子的RPL必须和调用例程的CPL一样满足同样的条件,也就是RPL必须小于或等于调用门的DPL。(中断门和陷阱门无RPL)
3. 如果调用进程和门之间的特权级检验通过了,处理器紧接着就检验代码段描述符DPL和调用进程的CPL。(因为内核所设的中断门和陷阱门的代码段描述符DPL均为0x08,即0级代码段,所以由3级程序发出的INT 0x80会产生堆栈切换)
在此,对于调用门,CALL指令和JMP指令的特权级检验的规则是不同的。
只有CALL指令可以使用调用门将进程控制转移到一个特权级更高的非一致性代码段。也就是说,可以访问一个DPL小于CPL的非一致性代码段。
JMP指令仅能使用调用门将进程控制转移到一个DPL等于CPL的非一致性代码段。CALL和JMP指令都可以将进程控制转移到一个特权级更高的一致性代码段;也就是,转移到一个DPL小于或等于CPL的一致性代码段。
如果调用特权级更高的非一致性目标代码段,CPL 就降为目标代码段的DPL 特权级,并且会发生栈切换。如果调用或者跳转到一个特权级更高的一致目标代码段,CPL 不会发生变化,也不会发生栈切换。
●.当调度一个任务时,处理器置位新任务的忙标志,当切换到一个新任务(由CALL指令、中断或者异常发动的)时,如果新任务的忙标志已经置位了,则处理器产生一个一般保护异常(#GP),这种情况下,通过阻止一个任务切换到自身或者嵌套链中的一个任务,处理器来阻止递归任务切换。
●所有的数据段都是非一致性的,这就意味着数据段不能被更低特权级的进程访问。,和代码段不同,数据段可以被更高优先级的程序或者过程(特权级数值较小的执行代码)访问,不需要使用特别的访问门。
●无论目标段是否为一致性代码段,进程都不能因为call或jump而转入一个特权级
较低(特权值较大)的代码段执行。试图进行这样的执行转换将导致一个一般保护异常
(#GP)。
●当段描述符的S标志(描述符类型)为0,则描述符为系统描述符。处理器可以识别以下类型的系统描述符:
(系统段描述符)
1局部描述符表(LDT)段描述符。
2任务状态段(TSS)描述符。
(门描述符)
3调用门描述符。
4中断门描述符。
5陷阱门描述。
6任务门描述符。
●类型检验
当把段选择子装入段寄存器时。特定的段寄存器只能容纳特定类型的描述符。比如:
——CS寄存器只能装入代码段的选择子。
——不可读的代码段或者系统段的选择子不能载入数据段寄存器(DS、ES、
FS和GS)。
——只有可写的数据段的选择子才能装入SS寄存器。
当段选择子装入LDTR或任务寄存器时。
——LDTR只能装入LDT的选择子。
——任务寄存器只能装入TSS的段选择子。
●任务切换
下列四种方式中的任何一种都会导致处理器转移执行到另外一个任务:
◆当前进程、任务或者例程执行一个JMP或者CALL指令到GDT中的一个TSS描
述符。
◆当前进程、任务或者例程执行一个JMP或者CALL指令到GDT或者当前LDT中
的一个任务门描述符。
◆一个中断或者异常向量,它指向IDT中的一个任务门描述符。
◆当前任务执行了一个IRET,此时EFLAGS寄存器中的NT标志是置位的。
●
●
●
关于硬盘
与代码联系较紧密的是主引导
的分区表,内核中partition结构完全对应了分区表的字段。
Boot Sector 也就是硬盘的第一个扇区, 它由 MBR (MasterBoot Record),DPT (Disk Partition Table) 和 Boot Record ID 三部分组成.
MBR 又称作主引导记录占用 Boot Sector 的前 446 个字节( 0 to 0x1BD ),存放系统主引导程序 (它负责从活动分区中装载并运行系统引导程序).
DPT 即主分区表占用 64 个字节 (0x1BE to 0x1FD),记录了磁盘的基本分区信息.主分区表分为四个分区项, 每项 16 字节,分别记录了每个主分区的信息(因此最多可以有四个主分区).
Boot Record ID 即引导区标记占用两个字节 (0x1FE and0x1FF), 对于合法引导区,它等于 0xAA55, 这是判别引导区是否合法的标志.
分区表由四个分区项构成, 每一项的结构如下:
BYTE State : 分区状态, 0 =未激活, 0x80 = 激活 (注意此项)
BYTE StartHead : 分区起始磁头号
WORD StartSC : 分区起始扇区和柱面号,底字节的低6位为扇区号,
高2位为柱面号的第 9,10 位, 高字节为柱面号的低 8 位
BYTE Type : 分区类型, 如0x0B = FAT32, 0x83 = Linux 等,00 表示此项未用,07 = NTFS
BYTE EndHead : 分区结束磁头号
WORD EndSC :分区结束扇区和柱面号, 定义同前
DWORD Relative :在线性寻址方式下的分区相对扇区地址(对于基本分区即为绝对地址)
DWORD Sectors : 分区大小 (总扇区数)
由此可见,分区表只是记录分区的起始与结束的扇区、磁头、柱面的值,至于该分区被格式化为哪种文件系统则不关心。因此上图中的
引导扇区应该是不存在的,比如minix文件系统也有boot但是却是1k。也就是说fdisk时不会为分区开辟所谓的引导扇区,都是数据区,所有空间由mkfs类的文件系统初始化操作进行分配。
●
系统启动过程主要由一下几步组成(以硬盘启动为例):
1. 开机
2. BIOS 加电自检 ( Power On Self Test -- POST ),内存地址为 0ffff:0000
3. 将硬盘第一个扇区 (0头0道1扇区, 也就是BootSector)读入内存地址 0000:7c00 处.
4. 检查 (WORD) 0000:7dfe 是否等于 0xaa55,若不等于,则转去尝试其他启动介质,如果没有其他启动介质则显示"No ROM BASIC" 然后死机.
5. 跳转到 0000:7c00 处执行 MBR 中的程序.
6. MBR 首先将自己复制到 0000:0600 处,然后继续执行.
7. 在主分区表中搜索标志为活动的分区.如果发现没有活动分区或有不止一个活动分区, 则转停止.
8. 将活动分区的第一个扇区读入内存地址 0000:7c00 处.
9. 检查 (WORD) 0000:7dfe 是否等于 0xaa55,若不等于则显示 "Missing Operating System" 然后停止,或尝试软盘启动.
10. 跳转到 0000:7c00处继续执行特定系统的启动程序.
11. 启动系统 ...
PS:以上步骤中 2,3,4,5 步是由 BIOS 的引导程序完成.6,7,8,9,10步由MBR 中的引导程序完成.一般多系统引导程序 (如 SmartFDISK, BootStar, PQBoot等)都是将
主引导记录替换成自己的引导程序, 在运行系统启动程序之前让用户选择要启动的分区.而某些系统自带的多系统引导程序 (如 lilo, NT Loader等)则可以将自己的引导程序放在系统所处分区的第一个扇区中, 在 Linux中即为 SuperBlock (其实 SuperBlock 是两个扇区).
Linux0.11中的minix1.0文件系统中,引导扇区是引导块相当于lilo或NT loader 1k
然后是SuperBlock 1k
●硬盘复位基本上是归零的含义.例如把磁头移动到0,寄存器的内容清零等.
硬盘重新校正就如重新测量,重新设定.例如磁头移动到20磁道,但读数据出错,于是试着把磁头移动到0磁道,然后在重新移动到20磁道.
●
关于b_uptodate和b_dirt (初始化时都是0)
b_uptodate是标识着数据的有效性:如果驱动程序在读盘的时候,发生失败,那么数据是无效的,
数据的流向是 buf <----- disk;
b_dirt表示数据是否被更改(脏,不与磁盘上的一致),如果脏了需要写盘,数据的流向是
buf ------> disk;
●
增加一个系统调用的方法:
1. 在 linux/include/Unistd.h 中增加一句
#define __NR_myfunc 72
2. 找到 linux/include/linux/Sys.h
增加
extern int sys_myfunc();
在 sys_call_table 中增加
sys_myfunc (注:在数组的最后加,不要搞错顺序)
3. 在 linux/kernel/Sys.c 中
int sys_myfunc()
{
printk ("this is my func!!\n ");
return 1;
}
4. 在 system_call.s 中第 61 行
nr_system_calls = 72
改为 nr_system_calls = 73
OK 现在你成功的增加了一个系统调用,你所要做的就是把内核重新编译一下
cd linux
make clean
make
好了,新的内核生成了,用新的内核启动就可以了
还要把新image写入启动软盘,但是make disk 似乎不管用,用dd bs=1024 if=Image of=/dev/fd0 就可以,奇怪~
看了makefile发现
disk: Image
dd bs=8192 if=Image of=/dev/PS0
原来写的不对,源代码默认软盘是1.2M的~
下面是测试程序,就是用来试一下你自己的系统调用,按下面的步骤
1. 注意,现在你要用你改过的 Unistd.h ,为了避免这种设 include 路径的麻烦,就把用下面的方式好了,把这个文件 copy 过去,直接编译就行~~~~~
// 文件 test.c
int errno;
#define __NR_myfunc71
#define _syscall0(type,name) \
type name(void) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name)); \
if (__res >= 0) \
return (type) __res; \
errno = -__res; \
return -1; \
}
// OK ,now define our function
_syscall0(int,myfunc) // 这里后面应该是没有分号的,定义好了我们自己的函数
main()
{
myfunc();
}
在redhat 7.2下编译通过了,可是不知道为什么版主的那个带 gcc 的 linux 0.11 中的
gcc 1.4 老是编译要出错
换一种方式好了,直接用汇编,下面的
test2.c
int main(int argc, char *argv[])
{
__asm__ ("mov $72, %%ax\n"
"int $0x80 \n" :: );
return 0;
}
linux 0.11 中用 gcc 1.4 编译通过了~~~
gcc test2.c
编译生成了一个 a.out 文件
用刚才我们的新内核启动系统,然后运行
./a.out
程序正确显示了 this is my func!!
结果正常,表明正确的增加了一个系统调用~~~~
●
关于execve()函数的相关说明。
●关于用户id uid suid等等的解释
· 利用redhat linux来访问文件系统映像文件的内容
●由as86/ld86生成的文件具有minix a.out执行头结构 linux使用的a.out与之相似 由于as86/ld86于minix文件系统的密切关系所以编译链接的结果符合minux执行头格式
而由gas/gld生成的文件具有gnu a.out执行头结构 gas/gld就是gnu工具当然满足gnu a.out执行头结构
linux系统使用自己定义的魔数 0x107 oldmagic 目标文件或不纯的可执行文件(.o文件);0x10b Zmagic 可执行文件
oldmagic 文件的头结构是32字节的后面紧跟code,date,bss
Zmagic 文件的头结构也是32字节但要空出1024字节的长度(填充0),1024字节后才是code,date,bss
内核加载一个可执行文件时,首先会判断魔数是否是Zmagic
附录:内核常用结构图表
●中断门描述符
· linux0.11系统设备号
●文件系统和超级块
●i_mode字段
· i节点
· 字符设备读写过程图
· linux0.11调试笔记
断点(lb)
1,0x7c00 跳转到bootsect开始执行
bootsect文件是单独编译链接的,因此其中的标号应当是相对于地址0x0的偏移(head.s也是这样)。所以程序被bios拷贝到0x7c00的一开始cs寄存器是0,eip是0x7c00程序开始执行,这之前的指令都是地址无关指令,而且不涉及标号。直到“jmpi go INITSEG”后cs=0x9000,eip=0x18,经调试知,go标号语句的地址值就是0x18,结论,编译链接后的标号只是一个偏移值(相对于地址0),由于cd已经设置,所以标号可以正常使用了。
在实验性的linux0.00中把boot和head都拷贝到0x0处,如果不作修改head无法运行,必须把head中的标号相关操作+512各字节(boot的大小),因为当语句涉及到标号时会按照链接时地址查找。
经调试知call,jnc,jc,je等都是地址无关指令。{目标地址=pc当前值+(PC当前值-标号值)}
2,0x90200 跳转到setup开始执行
3,0x9029d setup中 lidt idt_48 可观察idt的值变化(其实没变化,因为段基值和限长都设为0)
4,0x902a2 setup中 lgdt gdt_48 可观察gdt的值变化
5, 进入32位保护模式前
0x902fe mov ax #0x01
Lmsw ax
jmpi 0,8
观察cs值变为选择符值 8 ip=0 此时pc值为0x0,进入head.s
6,(vb) 0x08:0x0d lss _start_stack %esp ,
经调试知 lss ds:0x182a4
7,(vb) 0x08: 0x19 call setup_gdt
可观察段限长的变化
8,(vb) 0x08: 54a7 ret
head.s中“返回”到main
9, main在 0x08: 664c(664c之前的.h包含文件占据了一定空间,所以调试时发现jdt表尾地址并不是664b)
main函数开始会有push ebp;mov ebp,esp ;push edi等操作