利用timer interrupt讓兩個Process做context switch一個印出A一個印出B

好不容易有空了,趕快回到自己的興趣抽空學習撰寫OS底層的code。

我在Linux kernel完全剖析的這本書裡面看到一個範例:

他的功能是讓兩個Process利用timer interrupt互相做context switch一個印出A,

當context switch到另一個process則印出B。

當下感到很有興趣。

於是就試著改寫成用usb碟開機的版本。

最後要燒錄到usb disk的image map是長成下面這樣:


boot.bin是組成boot.img的前面1個sector也就是前面512bytes,他是由boot.S編譯而來,

在燒錄到usb隨身碟以後boot.bin就順理成章的變成MBR(master boot record)。

接著head.bin組成後面的11個sectors,他是由head.S編譯而來的。

先不要管code到底在幹麻,首先應該要了解整個code在image裡面的擺放方式,以及在記憶體內

移動的路徑。



知道了code在memory裡移動的路線之後,接著了解一下boot.img是怎麼製作出來的。


# Author: Gavin Guo <mimi0213kimo@gmail.com>
#
# This file is licensed under the GNU General Public License;

CC=gcc
LD=ld
OBJCOPY=objcopy

CFLAGS=-c
#gcc -c的選項是把.S檔編譯成.o檔
TRIM_FLAGS=-R .pdr -R .comment -R.note -S -O binary
#-R的意思是remove掉不需要的section,後面接的是section的名稱
#-S的意思是把symbol table和relocation table拿掉
#-O的意思是要編成怎樣的target format,這邊是binary的格式,也就不帶任何資訊

LDFILE_BOOT=gavin_x86_boot.ld
LDFILE_DOS=gavin_x86_dos.ld
LDFLAGS_BOOT=-e c -T$(LDFILE_BOOT)
LDFLAGS_DOS=-e c -T$(LDFILE_DOS)
#ld的參數接上-e c,e代表的是entry point,c是進入點的symbol,因為code裡面沒有c
#所以會叫說找不到entry point。
#-T是選擇要用的linker script檔案,裡面敘述base address是多少。

all: boot.img head.bin

boot.bin: boot.S
$(CC) $(CFLAGS) boot.S
$(LD) boot.o -o boot.elf $(LDFLAGS_BOOT)
$(OBJCOPY) $(TRIM_FLAGS) boot.elf $@

head.bin: head.S
$(CC) $(CFLAGS) head.S
$(LD) head.o -o head.elf $(LDFLAGS_DOS)
$(OBJCOPY) $(TRIM_FLAGS) head.elf $@

boot.img: boot.bin head.bin
dd if=boot.bin of=boot.img bs=512 count=1
dd if=head.bin of=boot.img seek=1 bs=512 count=11

# You must have the authority to do mount, or you must use "su root" or
# "sudo" command to do "make copy"
copy: boot.img

clean:
@rm -f *.o *.elf *.bin *.BIN *.img

distclean:
@rm -f *.img



objcopy的manual
ld的manual
台大某人寫的objcopy用法
《自写系统》的学习笔记之一 ——实现最小的“操作系统”(1)

gavin_x86_boot.ld長這樣:


SECTIONS
{
. = 0x0000;
.text :
{
_ftext = .;
} = 0
}



gavin_x86_dos.ld長這樣:


SECTIONS
{
. = 0x0000;
.text :
{
_ftext = .;
} = 0
}



看到make file和linker script後就知道boot.img是怎麼做出來的。
接著開始trace source code。先給出boot.S:


BOOTSEG = 0x07c0
SYSSEG = 0x1000

//這邊是16位元的code,所以.code16
.code16
.text

//要記住gavin_x86_boot.ld裡面,base是設為0x0000
jmp $BOOTSEG, $start_code
//因為一開始的offset是0x7c00,所以先jmp到0x07c0:start_code
//把cs改成0x07c0,這樣sp比較好算大小
start_code:
movw %cs, %ax
movw %ax, %ss
movw $0x1000, %sp
xorw %ax, %ax
movw %ax, %ds
//上面把ds設為0,底下的Packet就用自己算的比較不會錯
movw $(Packet + 0x7c00), %si
movb $0x80, %dl
movb $0x42, %ah
int $0x13
//上面ah=0x42h,通常大於8g的硬碟都要用0x42 extension的方法讀。
//這幾行run完以後,11 sectors就存到0x10000了。
cli
//先關掉中斷,待會要準備進入protected mode
cld
//清掉direction flag,到時候movsb就是遞增方式讀取
//從ds:si copy 到es:di
//所以下面這幾行把11 sectors從0x10000 copy到 0x0000
movw $SYSSEG, %ax
movw %ax, %ds
xorw %ax, %ax
movw %ax, %es
xorw %di, %di
xorw %si, %si
//0x1600怎麼算出來的,11x0x200=0x1600
movw $0x1600, %cx
rep movsb
// rep的counter就是%cx

//因為ds剛剛改掉了,所以ds這邊要清成0
xorw %ax, %ax
movw %ax, %ds
lgdtw gdt_48 + 0x7c00
//gdt_48 + 0x7c00這邊是參考ds的位置
inb $0x92, %al
orb $0b00000010, %al
outb %al, $0x92
//上面開啟A20 gate

movl %cr0, %eax
orl $1, %eax
movl %eax, %cr0
//把cr0的0bit on,所以會進入保護模式
//因為進入保護模式,所以要把pipeline裡面的16bits的code清掉
//,後面就要馬上接一個jmp
ljmpl $0x8, $(start_32 + 0x7c00)

.code32
start_32:
/*
mov $0x10, %ax
mov %ax, %gs
mov $0x0768, %ax
xor %edi, %edi
mov %ax, %gs:(%edi)
jmp .
*/
//jmp到code segment的base address,因為code segment存在gdt table的第
//8個bytes所以是8。之後就是jmp到0x0000開始head.s的code
ljmpl $0x8, $0x0

.p2align 2
//align 2是2^2=4所以底下的code會4bytes對齊,ex:0,4,8,12這樣
//cpu存取的時候只需要一次就可以,若是在跨界,則需要存取兩次。
//因為gdt, ldt在座context switch時需要大量的讀取,所以要做
//align加速讀取的速度。

//這邊如果看不懂就去查gdt code,data segment descriptor怎麼填
gdt:
//gdt第一個entry為空,intel定的
.long 0, 0
code:
.word 0xffff
.word 0x0000
.word 0x9800
.word 0x00cf
data:
.word 0xffff
.word 0x0000
.word 0x9200
.word 0x00cf

/* Video Descriptor */
//這個selector的base address是0xb8000,以方便我門印出字串做debug
video:
.word 0xffff
.word 0x8000
.word 0x920b
.word 0x00c0
gdt_end:


gdt_48:
.word (gdt_end - gdt - 1) //gdt的長度
.long gdt + 0x7c00 //gdt放在哪邊
//下面int $0x13h, ah=0x42h所以看不懂的話就去查ralf brown's interrupt list
Packet:
size_packet: .byte 0x10 //0x10代表這個Packet有16個bytes
reserved: .byte 0 //這個byte沒有用
SectorToTran: .2byte 11 //總共要抓11個sectors的data
Offset: .2byte 0x0000 //抓來後放在0x1000:0x0000這個地方
Segment: .2byte 0x1000
SectorLow: .4byte 1 //從usb disk的第二個sector開始抓,
SectorHigh: .4byte 0 //因為sector從0開始算

.org 510
.2byte 0xaa55



heads.S:


/*
* head.s contains the 32-bit startup code.
* Two L3 task multitasking. The code of tasks are in kernel area,
* just like the Linux. The kernel code is located at 0x10000.
*/
KRN_BASE = 0x0
TSS0_SEL = 0x20
LDT0_SEL = 0x28
TSS1_SEL = 0x30
LDT1_SEL = 0x38

.text
startup_32:
movl $0x10,%eax
mov %ax,%ds
mov %ax,%es
mov %ax,%fs
mov %ax,%gs
/*
mov $0x0768, %ax
xor %edi, %edi
mov %ax, %gs:(%edi)
*/
lss stack_ptr,%esp

# setup base fields of descriptors.
movl $KRN_BASE, %ebx
movl $gdt, %ecx
lea tss0, %eax
movl $TSS0_SEL, %edi
call set_base
lea ldt0, %eax
movl $LDT0_SEL, %edi
call set_base
lea tss1, %eax
movl $TSS1_SEL, %edi
call set_base
lea ldt1, %eax
movl $LDT1_SEL, %edi
call set_base

call setup_idt
call setup_gdt
movl $0x10,%eax # reload all the segment registers
mov %ax,%ds # after changing gdt.
mov %ax,%es
mov %ax,%fs
mov %ax,%gs
lss stack_ptr,%esp

# setup up timer 8253 chip.
movb $0x36, %al
movl $0x43, %edx
outb %al, %dx
movl $11930, %eax # timer frequency 100 HZ
movl $0x40, %edx
outb %al, %dx
movb %ah, %al
outb %al, %dx

# setup timer & system call interrupt descriptors.
movl $0x00080000, %eax
movw $timer_interrupt, %ax
movw $0x8E00, %dx
movl $0x08, %ecx
lea idt(,%ecx,8), %esi
movl %eax,(%esi)
movl %edx,4(%esi)
movw $system_interrupt, %ax
movw $0xef00, %dx
movl $0x80, %ecx
lea idt(,%ecx,8), %esi
movl %eax,(%esi)
movl %edx,4(%esi)

# unmask the timer interrupt.
movl $0x21, %edx
inb %dx, %al
andb $0xfe, %al
outb %al, %dx

# Move to user mode (task 0)
pushfl
andl $0xffffbfff, (%esp)
popfl
movl $TSS0_SEL, %eax
ltr %ax
movl $LDT0_SEL, %eax
lldt %ax
movl $0, current
sti
//這邊不是很好理解,而且可能發生一種情形。比方說,sti打開interrupt以後。有可能
//馬上就會有interrupt進來,因為已經填了tr和ldt,所以當interrupt進來後,會把
//所有的register存到tss segment descriptor。但要記得,現在的stack是kernel
//mode的,所以ss & sp會被存到tss裡面。所以即使現在stack裡沒有ss sp flag cs
//ip。到時候在timer interrupt被ljmp $TSS0_SEL, $0回來的時候,還是會從下面
//這邊的push開始run,之後還是會把user mode的堆疊啊,flag啊,cs ip給push進去
pushl $0x17
pushl $stack0_ptr
pushfl
pushl $0x0f
pushl $task0
// debug char 'h'
mov $0x0018, %ax
mov %ax, %gs
mov $0x0768, %ax
xor %edi, %edi
mov %ax, %gs:(%edi)
iret

/****************************************/
setup_gdt:
lgdt lgdt_opcode
ret

setup_idt:
lea ignore_int,%edx
movl $0x00080000,%eax
movw %dx,%ax /* selector = 0x0008 = cs */
movw $0x8E00,%dx /* interrupt gate - dpl=0, present */
lea idt,%edi
mov $256,%ecx
rp_sidt:
movl %eax,(%edi)
movl %edx,4(%edi)
addl $8,%edi
dec %ecx
jne rp_sidt
lidt lidt_opcode
ret

# in: %eax - logic addr; %ebx = base addr ;
# %ecx - table addr; %edi - descriptors offset.
set_base:
addl %ebx, %eax
addl %ecx, %edi
movw %ax, 2(%edi)
rorl $16, %eax
movb %al, 4(%edi)
movb %ah, 7(%edi)
rorl $16, %eax
ret

write_char:
push %gs
pushl %ebx
pushl %eax
mov $0x18, %ebx
mov %bx, %gs
movl scr_loc, %ebx
shl $1, %ebx
movb %al, %gs:(%ebx)
shr $1, %ebx
incl %ebx
cmpl $2000, %ebx
jb 1f
movl $0, %ebx
1: movl %ebx, scr_loc
popl %eax
popl %ebx
pop %gs
ret

/***********************************************/
/* This is the default interrupt "handler" :-) */
.align 2
ignore_int:
push %ds
pushl %eax
movl $0x10, %eax
mov %ax, %ds
movb $0x20, %al
outb %al, $0x20
movl $67, %eax /* print 'C' */
call write_char
popl %eax
pop %ds
iret

/* Timer interrupt handler */
.align 2
timer_interrupt:
push %ds
pushl %edx
pushl %ecx
pushl %ebx
pushl %eax
movl $0x10, %eax
mov %ax, %ds
movb $0x20, %al
outb %al, $0x20
movl $1, %eax
cmpl %eax, current
je 1f
movl %eax, current
ljmp $TSS1_SEL, $0
jmp 2f
1: movl $0, current
ljmp $TSS0_SEL, $0
2: popl %eax
popl %ebx
popl %ecx
popl %edx
pop %ds
iret
//注意上面這邊的ljmp是真的會jmp過去另外一個task。假設被timer中斷的是task1,重點
//在於當ljmp執行當下,要記得的被儲存的ss,sp是kernel的且cs ip是指向ljmp的下一行
//以task1來說是"2: popl %eax"。所以可以當作被儲存的是task1被中斷在kernel
//mode的狀態,整個被搬到TSS segment descriptor,之後回來就是"2: popl %eax"
/* system call handler */
.align 2
system_interrupt:
push %ds
pushl %edx
pushl %ecx
pushl %ebx
pushl %eax
movl $0x10, %edx
mov %dx, %ds
call write_char
popl %eax
popl %ebx
popl %ecx
popl %edx
pop %ds
iret

/*********************************************/
current:.long 0
scr_loc:.long 0

.align 2
.word 0
lidt_opcode:
.word 256*8-1 # idt contains 256 entries
.long idt + KRN_BASE # This will be rewrite by code.
.align 2
.word 0
lgdt_opcode:
.word (end_gdt-gdt)-1 # so does gdt
.long gdt + KRN_BASE # This will be rewrite by code.

.align 2
idt: .fill 256,8,0 # idt is uninitialized

gdt: .quad 0x0000000000000000 /* NULL descriptor */
.quad 0x00c09a00000007ff /* 8Mb 0x08, base = 0x0000 */
.quad 0x00c09200000007ff /* 8Mb 0x10 */
.quad 0x00c0920b80000002 /* screen 0x18 - for display */

.quad 0x0000e90000000068 # TSS0 descr 0x20
.quad 0x0000e20000000040 # LDT0 descr 0x28
.quad 0x0000e90000000068 # TSS1 descr 0x30
.quad 0x0000e20000000040 # LDT1 descr 0x38
end_gdt:
.fill 128,4,0
stack_ptr:
.long stack_ptr
.word 0x10

/*************************************/
.align 2
ldt0: .quad 0x0000000000000000
.quad 0x00c0fa00000003ff # 0x0f, base = 0x10000
.quad 0x00c0f200000003ff # 0x17
tss0:
.long 0 /* back link */
.long stack0_krn_ptr, 0x10 /* esp0, ss0 */
.long 0, 0 /* esp1, ss1 */
.long 0, 0 /* esp2, ss2 */
.long 0 /* cr3 */
.long task0 /* eip */
.long 0x200 /* eflags */
.long 0, 0, 0, 0 /* eax, ecx, edx, ebx */
.long stack0_ptr, 0, 0, 0 /* esp, ebp, esi, edi */
.long 0x17,0x0f,0x17,0x17,0x17,0x17 /* es, cs, ss, ds, fs, gs */
.long LDT0_SEL /* ldt */
.long 0x8000000 /* trace bitmap */

.fill 128,4,0
stack0_krn_ptr:
.long 0

/************************************/
.align 2
ldt1: .quad 0x0000000000000000
.quad 0x00c0fa00000003ff # 0x0f, base = 0x10000
.quad 0x00c0f200000003ff # 0x17
tss1:
.long 0 /* back link */
.long stack1_krn_ptr, 0x10 /* esp0, ss0 */
.long 0, 0 /* esp1, ss1 */
.long 0, 0 /* esp2, ss2 */
.long 0 /* cr3 */
.long task1 /* eip */
.long 0x200 /* eflags */
.long 0, 0, 0, 0 /* eax, ecx, edx, ebx */
.long stack1_ptr, 0, 0, 0 /* esp, ebp, esi, edi */
.long 0x17,0x0f,0x17,0x17,0x17,0x17 /* es, cs, ss, ds, fs, gs */
.long LDT1_SEL /* ldt */
.long 0x8000000 /* trace bitmap */

.fill 128,4,0
stack1_krn_ptr:
.long 0

/************************************/
task0:
movl $0x17, %eax /*這邊之所以要換掉ds是因為之後的stack0_ptr*/
movw %ax, %ds /*相對位置是用ldt裡面的第二個entry,所存的*/
/*base address為基準*/
mov $65, %al /*65就是0x41所以 print 'A' */
int $0x80
movl $0x08ffffff, %ecx
//因為task0的ecx的loop值大概只有task1的一半,所以
//印出A的數量大概會是B的兩倍
1: loop 1b
jmp task0

.fill 128,4,0
stack0_ptr:
//stack0的userspace stack所在位置。
.long 0

task1:
movl $0x17, %eax
movw %ax, %ds
mov $66, %al /* 0x42所以print 'B' */
int $0x80
movl $0x0fffffff, %ecx
1: loop 1b
jmp task1

.fill 128,4,0
stack1_ptr:
//上面是task1的user space stack所在的位置。
.long 0

/*** end ***/


可以看出主要的組成就是上面這五隻檔案:
Makefile, gavin_x86_boot.ld, gavin_x86_dos.ld, boot.S, head.S

剛開始做實驗的時候會覺得很奇怪,為什麼按下鍵盤只會出現一個C(也就是ignore_int),

後來發現因為keyboard interrupt要回去重新清除flag。下完io以後才能再繼續收到interrupt。

在這個實驗的過程,可以多多利用objdump xxx.elf(xxx.o) -d -M addr(16)32,data(16)32。如果已經

build成binary可以用hexedit。若是不知道如何使用=>man hexedit。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值