当前位置:首页 > 物联网 > 《物联网技术》杂志
[导读]摘要:描述了一个简单多任务内核的设计和实现方法。分析了该简单内核的基本结构和加载运行的基本原理,然后描述了其被加载进机器RAM中以及两个任务进行切换的运行方法。

引言

当提到多任务时,人们便会联想到MacOS、Linux、Windows等操作系统。通常情况下,若在操作系统下运行多任务,是由操作系统负责管理和调度各个任务的。本文通过分析一个简单的多任务内核,能够便于更容易地理解操作系统的任务管理机制,以及可以理解计算机系统是如何启动的。

1多任务程序的结构

本文实现的简单多任务内核,主要由两个文件构成:一个是使用as86语言编制的引导启动程序,主要用于在计算机系统加电时,将内核代码从启动盘加载到内存中;另一个便是使用GNUas汇编语言编写的内核程序,其中实现两个运行在特权级2上的任务可在时钟中断控制下相互切换运行,并可通过系统调用在屏幕上实现字符显示。

2多任务内核工作的启动程序原理

计算机系统加电启动后,会把启动程序从启动盘的第一个扇区加载到物理内存0x7c00位置开始处,之后把执行权交给0x7c00初开始运行启动程序。

启动程序的主要功能是将软盘或者镜像文件中的内核程序加载到内存的某个指定位置,实现这个目的的方法是利用ROSBIOS中断int0x13,把软盘或者镜像中的内核代码读入到内存,然后再把这段内核代码移动到内存0开始处。最后设置控制寄存器CR0中的开启保护运行模式标志,并跳转到内存0处开始执行内核代码。启动程序在内存中移动内核代码的示意图如图1所示。

图1启动程序在内存中移动内核代码的示意图

将内核代码移动到物理内存0开始处的主要原因是这是GDT表时可以简单一点。但是,不能让启动程序把内核代码从软盘或映像文件中直接加载到内存0处,因为加载操作需要ROMBIOS提供中断过程,而BIOS使用的中断向量表正处于内存0开始处。若直接把内核代码加载到内存0处,那么,BIOS中断过程将不能正常运行。

3内核程序

3.1初始化任务

内核程序运行在32位保护模式下,初始化阶段主要包括重新设置GDT表,设置系统定时器芯片,重新设置IDT表并且设置时钟和系统调用中断门。内核示例中所有代码和数据段都对应到物理内存同一个区域上,即从物理内存0开始的区域。在虚拟地址空间中内核程序的内核代码和任务代码分配图如图2所示。

3.2启动第一个任务

特权级0的代码不能直接把控制权转移到特权级2的代码中执行,但可以使用中断返回操作来实现,因此当初始化GDT、IDT和定时芯片结束后,就利用中断返回指令IRET来启动第一个任务。

具体的实现方法是在初始堆栈init_stack中人工设置一个返回环境,即把任务0的TSS段选择符加载到任务寄存器TR中,LDT段选择符加载到LDTR中以后,把任务0的用户栈指针和代码指针以及标志寄存器值压入栈中,然后执行中断返回指令IRET。该指令会弹出堆栈上的堆栈指针作为任务0用户栈指针,恢复假设的任务0的标志寄存器内容,并且弹出栈中代码指针放入CS:EIP寄存器中,从而开始执行任务0的代码,以完成从特权级0到特权级3代码的控制转移。

3.3两个任务的切换

内核程序将定时器芯片的通道0设置成每经过10ms就向中断控制芯片发送一个时钟中断请求信号,这样,每个10ms将会切换运行的任务。PC的ROMBIOS开机时已经在定时器芯片中把时钟中断请求信号设置成中断向量8,因此需要在中断8的处理过程中执行任务切换操作。

每个任务在执行时,会首先把一个字符的ASCII码放入寄存器AL中,然后调用系统中断int0x80,而该系统调用处理过程会调用一个简单的字符写屏子程序。在显示过一个字符后,任务代码会使用循环语句延迟一段时间,然后又跳转到任务代码开始处继续循环执行,直到运行了10ms而发生了定时中断,从而代码会切换到另一个任务去运行。

目前,该内核示例已经在Bochs模拟软件中运行测试过,测试结果如图3所示。

4结语

本文分析了一个基于X86平台的简单多任务内核的基本结构和加载运行原理,描述了其被加载进机器RAM中的基本思路,同时给出了两个任务进行切换的运行方法。其主要目的是理解操作系统的启动加载过程。

附 :本文的启动代码及内核代码如下:

#############################################

# 名称 :引导程序 boot.s #

# 说明 :把镜像文件中的 head 内核代码加载到内存某个

指定位置。 #

# #

#############################################

BOOTSEG=0x07c0

SYSSEG=0x1000

SYSLEN=17

entry start

start :

jmpi go,#BOOTSEG

go :

mov ax,cs

mov ds,ax

mov es,ax

mov ss,ax

mov sp,#0x400

mov ax,#0x0600

mov cx,#0x0000

mov dx,#0xFFFF

int 0x10

mov cx,#10

mov dx,#0x0000

mov bx,#0x000c

mov bp,#msg

mov ax,#0x1301

int 0x10

load_system :

mov dx,#0x0000

mov cx,#0x0002

mov ax,#SYSSEG

mov es,ax

xor bx,bx

mov ax,#0x200+SYSLEN

int 0x13

jnc ok_load

mov dx,#0x0000

mov ax,#0x0000

int 0x13

jmp load_system

ok_load :

cli

mov ax,#SYSSEG

mov ds,ax

xor ax,ax

mov es,ax

mov cx,#0x1000

sub si,si

sub di,di

rep

movw

mov ax,cs

mov ds,ax

lidt idt_48

lgdt gdt_48

mov ax,#0x0001

lmsw ax

jmpi 0,8

msg :

.ascii "Loading..."

.byte 13,10

gdt :

.word 0,0,0,0

.word 0x07FF

.word 0x0000

.word 0x9A00

.word 0x00C0

.word 0x07FF

.word 0x0000

.word 0x9200

.word 0x00C0

idt_48 :

.word 0

.word 0,0

gdt_48 :

.word 0x7FF

.word 0x7c00+gdt,0

.org 510

.word 0xAA55

#############################################

# 名称 :内核程序 head.s

# 说明 :包含 32 位保护模式初始化设置代码,时钟中断代码,系统调用中断代码和两个任务的代码。在初始化完成之后程序移动到任务 0 开始执行,并在时钟中断控制下进行任务 0 和 1 之间的切换操作。

#############################################

LATCH = 11930 # 定时器初始计数值,即每隔 10ms 发送一次中断请求。

SCRN_SEL = 0x18 # 屏幕显示内存段选择符

TSS0_SEL = 0x20 # 任务 0 的 TSS 段选择符

LDT0_SEL = 0x28 # 任务 0 的 LDT 段选择符

TSS1_SEL = 0x30 # 任务 1 的 TSS 段选择符

LDT1_SEL = 0x38 # 任务 1 的 LDT 段选择符

.globl startup_32

.text

startup_32 :

# 首先加载数据段寄存器 DS、堆栈段寄存器 SS 和堆栈

指针 ESP。所有段的线性基地址都是 0.

movl $0x10,%eax # 0x10 是 GDT 中数据段选择符。

mov %ax,%ds

lss init_stack,%esp # 把 init_stack 地址处的内容既

init_stack 有效地址给 esp,同时把 0x10 给 ss 段寄存器

# 在新的位置重新设置 IDT 和 GDT 表。

call setup_idt # 设置 IDT,先把 256 个中断门都填

默认处理过程的描述符

call setup_gdt

movl $0x10,%eax # 在改变了 GDT 之后 重新加载

所有段寄存器 .

mov %ax,%ds

mov %ax,%es

mov %ax,%fs

mov %ax,%gs

lss init_stack,%esp

# 设置 8253 定时芯片。把计数器通道 0 设置成每个 10ms

向中断控制器发送一个中断请求信号。

movb $0x36,%al # 控制字:设置通道0工作在方式3、

计数处置采用二进制

movl $0x43,%edx #8253 芯片控制字寄存器写端口

outb %al,%dx

movl $LATCH,%eax # 初始计数值设置为 LATCH

(1193180/100),即频率 100HZ

movl $0x40,%edx # 通道 0 的端口

outb %al,%dx # 分两次把初始计数值写入通道 0

movb %ah,%al

outb %al,%dx

# 在 IDT 表第 8 和第 128(

0x80)项处分别设置定时中断

门描述符和系统调用陷阱门描述符

movl $0x00080000,%eax # 中断处 理 属内核,即

EAX 高字节是内核代码段选择符 0x0008.

movw $timer_interrupt,%ax # 设置定时中断门描述

符。取定时中断处理程序地址。

movw $0x8E00,%dx # 中断门类型是 14(屏蔽中断),

特权级 0 或硬件使用。

movl $0x08,%ecx # 开机时 BIOS 设置的时钟中断

向量号 8. 这里直接使用它。

lea idt(,%ecx,8),%esi # 把 IDT 描 述 符 0x08 地

址放入 ESI 中,然后设置该描述符

movl %eax,(%esi)

movl %edx,4(%esi)

movw $system_interrupt,%ax # 设置系统调用陷阱

门描述符。取系统调用处理程序地址。

movw $0xef00,%dx # 陷阱门类型是 15,特权级 3

的程序可执行。

movl $0x80,%ecx # 系统调用向量号是 0x80.

lea idt(,%ecx,8),%esi # 把 IDT 描 述 符 项 0x80

地址放入 ESI 中,然后设置该描述符.

movl %eax,(%esi)

movl %edx,4(%esi)

# 现在,为移动到任务 0 中执行来操作堆栈内容,在堆

栈中人工建立中断返回时的场景

pushfl

andl $0xffffbfff,(%esp)

popfl

movl $TSS0_SEL,%eax # 把任务 0 的 TSS 段选择

符加载到任务寄存器 TR

ltr %ax

movl $LDT0_SEL,%eax # 把任务 0 的 LDT 段选择

符加载到局部描述符表寄存器 LDTR

lldt %ax

movl $0,current

sti

pushl $0x17 # 任务 0 当前局部空间数据段选择符入栈

pushl $init_stack # 堆栈指针入栈

pushfl # 标志寄存器值入栈

pushl $0x0f # 任务 0 局部空间代码段选择符入栈

pushl $task0 # 把代码指针入栈

iret # 执行中断返回指令,从而切换到特权级 3 的任

务 0 中执行.

# 一下是设置 GDT 和 IDT 中描述符项的子程序

setup_gdt :

lgdt lgdt_opcode

ret

# 设置 IDT 表中所有 256 个中断们描述符都为统一个默

认值,均使用默认的中断处理过程 ignore_int。

setup_idt :

lea ignore_int,%edx

movl $0x0008000,%eax

movw %dx,%ax

movw $0x8E00,%dx

lea idt,%edi

mov $256,%ecx

rp_idt :

movl %eax,(%edi)

movl %edx,4(%edi)

addl $8,%edi

dec %ecx

jne rp_idt

lidt lidt_opcode

ret

# 显示字符子程序

write_char :

push %gs

pushl %ebx

mov $SCRN_SEL,%ebx

mov %bx,%gs

mov src_loc,%bx

shl $1,%ebx

mov %ax,%gs :

(%ebx)

shr $1,%ebx

incl %ebx

cmpl $2000,%ebx

jb 1f

movl $0,%ebx

1 :

movl %ebx,src_loc

popl %ebx

pop %gs

ret

# 以下是 3 个中断处理程序 :默认中断、定时中断和系统

调用中断

#ignore_int 是默认的中断处理程序,若系统产生了其他

中断,会在屏幕上显示一个字符“C”

.align 2

ignore_int :

push %ds

pushl %eax

movl $0x10,

%eax

mov %ax,

%ds

mov $0x0c98,

%ax /* print 'C' */

call write_char

popl %eax

pop %ds

iret

# 这是定时中断处理程序,主要执行任务切换操作 .

.align 2

timer_interrupt :

push %ds

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

pop %ds

iret

# 系统调用中断 int 0x80 处理程序 . 显示字符功能

.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

src_loc :

.long 0

.align 2

lidt_opcode :

.word 256*8-1

.long idt

lgdt_opcode :

.word (end_gdt-gdt)-1

.long gdt

.align 2

idt :

.fill 256,8,0

gdt :

.quad 0x0000000000000000 #GDT 表, 第

一个描述符不用

.quad 0x00c09a00000007ff # 第二个是内核代码段描

述符,其选择符是 0x08.

.quad 0x00c09200000007ff # 第三个是内核数据段描

述符,其选择符是 0x10.

.quad 0x00c0920b80000002 # 第 4 个是显示内存段

描述符,其选择符是 0x18.

.word 0x68,tss0,0xe900,0x0 # 第 5 个是 TSS0 段

的描述符,其选择符是 0x20.

.word 0x40,ldt0,0xe200,0x0 # 第 6 个 是 LDT0

段的描述符,其选择符是 0x28

.word 0x68,tss1,0xe900,0x0 # 第 7 个是 TSS1 段

的描述符,其选择符是 0x30

.word 0x40,ldt1,0xe200,0x0 # 第 8 个是 LDT1 段

的描述符,其选择符是 0x38

end_gdt :

.fill 128,4,0 # 初始内核堆栈空间

init_stack :

# 刚进入保护

模式时用于加载 SS :ESP 堆栈指针值

.long init_stack

.word 0x10

# 下面是任务 0 的 LDT 表段中的局部段描述符

.align 2

ldt0 :

.quad 0x0000000000000000

.quad 0x00c0fa00000003ff # 第 2 个是局部代码段

描述符,对应选择符是 0x0f.

.quad 0x00c0f200000003ff # 第 3 个是局部数度段

描述符,

对应选择符是 0x17.

# 下面是任务 0 的 TSS 段的内容

tss0 :

.long 0

.long krn_stk0,0x10 /*esp0,ss0*/

.long 0,0,0,0,0

/*esp1,ss1,esp2,ss2,

cr3*/

.long 0,0,0,0,0

/ *e i p,e f l a g s,e a x,

ecx,edx*/

.long 0,0,0,0,0

/*ebx,esp,ebp,esi,

edi*/

.long 0,0,0,0,0,0

/*es,cs,ss,ds,fs,

gs*/

.long LDT0_SEL,0x8000000

/ * l d t,t r a c e

bitmap*/

.fill 128,4,0

# 这是任务 0 的内核栈

空间

krn_stk0 :

# 下面是任务 1 的 LDT 表段内容和 TSS 段内容

.align 2

ldt1 :

.quad 0x0000000000000000# 第 1 个 描 述

符,不用

.quad 0x00c0fa00000003ff # 选择符是 0x0f,基地

址 = 0x00000

.quad 0x00c0f200000003ff # 选择符是 0x17,基地

值 = 0x00000

tss1 :

.long 0

.long krn_stk1,0x10

.long 0,0,0,0,0

.long task1,0x200

.long 0,0,0,0

.long usr_stk1,0,0,0

.long 0x17,0x0f,0x17,0x17,0x17,0x17

.long LDT1_SEL,0x8000000

.fill 128,4,0

# 这是任务 1 的内核栈

空间,其用户栈直接使用初始栈空间

krn_stk1 :

# 下面是任务 0 和任务 1 的程序,它们分别循环显示字

符“A”和“B”

task0 :

movw $0x0c61,

%ax

# 把需要显示

的字符 A 放入 AL 寄存器中

int $0x80

movl $0xfff,%ecx # 执行循环,起延时作用

1 :

loop 1b

jmp task0

task1 :

movw $0x0d62,

%ax

int $0x80

movl $0xfff,%ecx

1 :

loop 1b

jmp task1

.fill 128,4,0

# 这是任务 1 的用户栈

空间

usr_stk1 :

.long 0

20211117_6193e118b949a__基于X86平台的简单多任务内核的分析与实现

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

9月2日消息,不造车的华为或将催生出更大的独角兽公司,随着阿维塔和赛力斯的入局,华为引望愈发显得引人瞩目。

关键字: 阿维塔 塞力斯 华为

加利福尼亚州圣克拉拉县2024年8月30日 /美通社/ -- 数字化转型技术解决方案公司Trianz今天宣布,该公司与Amazon Web Services (AWS)签订了...

关键字: AWS AN BSP 数字化

伦敦2024年8月29日 /美通社/ -- 英国汽车技术公司SODA.Auto推出其旗舰产品SODA V,这是全球首款涵盖汽车工程师从创意到认证的所有需求的工具,可用于创建软件定义汽车。 SODA V工具的开发耗时1.5...

关键字: 汽车 人工智能 智能驱动 BSP

北京2024年8月28日 /美通社/ -- 越来越多用户希望企业业务能7×24不间断运行,同时企业却面临越来越多业务中断的风险,如企业系统复杂性的增加,频繁的功能更新和发布等。如何确保业务连续性,提升韧性,成...

关键字: 亚马逊 解密 控制平面 BSP

8月30日消息,据媒体报道,腾讯和网易近期正在缩减他们对日本游戏市场的投资。

关键字: 腾讯 编码器 CPU

8月28日消息,今天上午,2024中国国际大数据产业博览会开幕式在贵阳举行,华为董事、质量流程IT总裁陶景文发表了演讲。

关键字: 华为 12nm EDA 半导体

8月28日消息,在2024中国国际大数据产业博览会上,华为常务董事、华为云CEO张平安发表演讲称,数字世界的话语权最终是由生态的繁荣决定的。

关键字: 华为 12nm 手机 卫星通信

要点: 有效应对环境变化,经营业绩稳中有升 落实提质增效举措,毛利润率延续升势 战略布局成效显著,战新业务引领增长 以科技创新为引领,提升企业核心竞争力 坚持高质量发展策略,塑强核心竞争优势...

关键字: 通信 BSP 电信运营商 数字经济

北京2024年8月27日 /美通社/ -- 8月21日,由中央广播电视总台与中国电影电视技术学会联合牵头组建的NVI技术创新联盟在BIRTV2024超高清全产业链发展研讨会上宣布正式成立。 活动现场 NVI技术创新联...

关键字: VI 传输协议 音频 BSP

北京2024年8月27日 /美通社/ -- 在8月23日举办的2024年长三角生态绿色一体化发展示范区联合招商会上,软通动力信息技术(集团)股份有限公司(以下简称"软通动力")与长三角投资(上海)有限...

关键字: BSP 信息技术
关闭
关闭