这个答案只是其他答案的延伸。如
C标准所述,有关初始化的规则:
10) 如果具有自动存储期限的对象没有明确初始化,则其值是不确定的。如果没有明确初始化具有静态存储期限的对象,则:
- 如果它具有指针类型,则初始化为空指针;
- 如果它具有算术类型,则将其初始化为(正数或无符号)零;
- 如果是聚合的,则每个成员都按照这些规则递归地初始化;
- 如果它是一个联合,则第一个命名成员根据这些规则被初始化(递归)。
您代码中的问题在于计算机的内存可能不总是初始化为零。您需要确保在类似于操作系统和引导加载程序的自由环境中初始化BSS部分为零。
BSS部分通常不会(默认情况下)在二进制文件中占用空间,并且通常会占用超出二进制中出现的代码和数据的限制的区域的内存。这样做是为了减小必须读入内存的二进制文件的大小。
我知道您正在为使用遗留BIOS引导的x86编写操作系统。 我知道你正在使用你其他最近的问题中提到的GCC, 我知道你正在使用GNU汇编器来处理你的引导程序的一部分, 我知道你有一个链接脚本, 但我不知道它看起来像什么。通常通过链接器脚本来实现这一点,该脚本将BSS数据放置在末尾,并创建开始和结束符号以定义该部分的地址范围。 一旦链接器定义了这些符号,就可以由C代码(或汇编代码)使用它们循环遍历该区域并将其设置为零。
我提供了一个相当简单的MCVE来做到这一点。 代码使用Int 13h / AH = 2h读取具有内核的额外扇区; 使用快速A20方法启用A20行; 使用32位描述符加载GDT; 启用受保护模式; 完成过渡到32位受保护模式; 然后调用名为C的内核入口点中的kmain
。 kmain
调用名为zero_bss
的C函数,根据自定义链接器脚本生成的起始和结束符号(__bss_start
和__bss_end
)初始化BSS部分。
boot.S
:
.extern kmain
.globl mbrentry
.code16
.section .text
mbrentry:
# If trying to create USB media, a BPB here may be needed
# At entry DL contains boot drive number
# Segment registers to zero
xor %ax, %ax
mov %ax, %ds
mov %ax, %es
# Set stack to grow down from area under the place the bootloader was loaded
mov %ax, %ss
mov $0x7c00, %sp
cld # Ensure forward direction of MOVS/SCAS/LODS instructions
# which is required by generated C code
# Load kernel into memory
mov $0x02, %ah # Disk read
mov $1, %al # Read 1 sector
xor %ch, %ch # Cylinder 0
xor %dh, %dh # Head 0
mov $2, %cl # Start reading from second sector
mov $0x7e00, %bx # Load kernel at 0x7e00
int $0x13
# Quick and dirty A20 enabling. May not work on all hardware
a20fast:
in $0x92, %al
or $2, %al
out %al, $0x92
loadgdt:
cli # Turn off interrupts until a Interrupt Vector
# Table (IVT) is set
lgdt (gdtr)
mov %cr0, %eax
or $1, %al
mov %eax, %cr0 # Enable protected mode
jmp $0x08,$init_pm # FAR JMP to next instruction to set
# CS selector with a 32-bit code descriptor and to
# flush the instruction prefetch queue
.code32
init_pm:
# Set remaining 32-bit selectors
mov $DATA_SEG, %ax
mov %ax, %ds
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
mov %ax, %ss
# Start executing kernel
call kmain
cli
loopend: # Infinite loop when finished
hlt
jmp loopend
.align 8
gdt_start:
.long 0 # null descriptor
.long 0
gdt_code:
.word 0xFFFF # limit low
.word 0 # base low
.byte 0 # base middle
.byte 0b10011010 # access
.byte 0b11001111 # granularity/limit high
.byte 0 # base high
gdt_data:
.word 0xFFFF # limit low (Same as code)
.word 0 # base low
.byte 0 # base middle
.byte 0b10010010 # access
.byte 0b11001111 # granularity/limit high
.byte 0 # base high
end_of_gdt:
gdtr:
.word end_of_gdt - gdt_start - 1
# limit (Size of GDT)
.long gdt_start # base of GDT
CODE_SEG = gdt_code - gdt_start
DATA_SEG = gdt_data - gdt_start
kernel.c
:
#include <stdint.h>
extern uintptr_t __bss_start[];
extern uintptr_t __bss_end[];
static void zero_bss(void)
{
uint32_t *memloc = __bss_start;
while (memloc < __bss_end)
*memloc++ = 0;
}
int kmain(){
zero_bss();
return 0;
}
link.ld
ENTRY(mbrentry)
SECTIONS
{
. = 0x7C00;
.mbr : {
boot.o(.text);
boot.o(.*);
}
. = 0x7dfe;
.bootsig : {
SHORT(0xaa55);
}
. = 0x7e00;
.kernel : {
*(.text*);
*(.data*);
*(.rodata*);
}
.bss : SUBALIGN(4) {
__bss_start = .;
*(COMMON);
*(.bss*);
}
. = ALIGN(4);
__bss_end = .;
/DISCARD/ : {
*(.eh_frame);
*(.comment);
}
}
要编译、链接并生成一个可用于磁盘映像的二进制文件,您可以使用如下命令:
as --32 boot.S -o boot.o
gcc -c -m32 -ffreestanding -O3 kernel.c
gcc -ffreestanding -nostdlib -Wl,--build-id=none -m32 -Tlink.ld \
-o boot.elf -lgcc boot.o kernel.o
objcopy -O binary boot.elf boot.bin
int 13
从磁盘读取内核到内存中,那么您需要自己负责清零内存(如果您使用GRUB / multiboot作为引导程序,则不需要,因为GRUB会为您执行此操作)。最好的方法是在链接器脚本中创建一个BSS部分,其中包含可用于在_C_代码中将BSS区域初始化为零的开始和结束符号。 - Michael Petch