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

来源:互联网 发布:淘宝开店可以不交钱吧 编辑:程序博客网 时间:2024/05/11 15:11

好不容易有空了,趕快回到自己的興趣抽空學習撰寫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。

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 小米手机变卡了怎么办 主持时说错话了怎么办 小鲜肉老了不红怎么办 同学聚会大家玩手机你怎么办 率土之滨被掠夺怎么办 戒指戴手上取不下来怎么办 择离开我我该怎么办 解小手解不出来怎么办 学生把班里的班费弄丢了怎么办 班里选的班长成绩差怎么办 幼儿园班里孩子发生传染病怎么办 小仓鼠生了该怎么办 把老公生日忘了怎么办 老公说老婆脑子不好怎么办 和上司暧昧被同事发现怎么办 减肥不吃晚餐饿了怎么办 小孩晚饭吃多了怎么办 减肥晚上不吃饭饿了怎么办 两个人在一起性格不合怎么办 赌在你身上输了怎么办 苹果7lcould满了怎么办 e招贷不用了怎么办 牙活动了怎么办还疼 30岁掉了一颗牙怎么办? 在淘宝上交话费交错了怎么办 演出队在小区旁边扰民怎么办 雷雨天加了油怎么办 戴ok镜眼睛重影怎么办 乌龟背上长白色的花纹怎么办? 全自动洗衣机里面掉个硬币怎么办 跆拳道课上孩子乱动说话怎么办? 孩子不愿意上跆拳道课了怎么办 车座位里面倒了汤怎么办 腿被棍子打肿了怎么办 刚买的手机碎屏怎么办 被木棍么么打到头项怎么办 大王卡用到40g怎么办 王卡40g用完了怎么办 父亲把母亲打成重伤怎么办 狗狗脖子摔歪了怎么办 吃鸡游戏中重伤怎么办