GeekOS源代码学习(3)Main函数中Init_BSS() 与Init_Screen()

Main函数的第一个函数Init_BSS()

位于./src/geekos/mem.c中

/*
 * Initialize the .bss section of the kernel executable image.
 */
void Init_BSS(void)
{
    extern char BSS_START, BSS_END;

    /* Fill .bss with zeroes */
    memset(&BSS_START, '\0', &BSS_END - &BSS_START);
}



可以看到Init_BSS函数清空了从BSS_START到BSS_END的内存。

先来看memset的实现

位于./src/common/string.c中

void* memset(void* s, int c, size_t n)
{
    unsigned char* p = (unsigned char*) s;

    while (n > 0) {
        *p++ = (unsigned char) c;
        --n;
    }   

    return s;
}

和C语言的memset功能一样,实现也很简单。

再来看一下外部声明BSS_START,BSS_END

位于/include/geekos/defs.h

/*
 * The windows versions of gcc use slightly different
 * names for the bss begin and end symbols than the Linux version.
 */
#if defined(GNU_WIN32)	
#  define BSS_START _bss_start__
#  define BSS_END _bss_end__
#else
#  define BSS_START __bss_start
#  define BSS_END end
#endif



在project0下使用命令
$find ./ | xargs grep GNU_WIN32 

可以看到,GNU_WIN32标志在./build/Makefile中定义,

用于区别windows下的cygwin编译环境和linux下的gcc,两者生成的elf文件的符号有细微的差别。

明显在Linux中定义的是后者。

再次搜索
$find ./ | xargs grep __bss_start
可以看到__bss_start符号出现在内核符号表kernel.syms中
我把这一段贴出来

0001a5dc A __bss_start
00018b58 r __func__.1314
000185fb r __func__.1319
00018fcd r __func__.1319
00019289 r __func__.1319
00018ad2 r __func__.1331
000188f1 r __func__.1333
0001860c r __func__.1335
00018fde r __func__.1335
0001929a r __func__.1335
00018afc r __func__.1347
00018902 r __func__.1349
00018c00 r __func__.1369
00018c11 r __func__.1385
00018438 r __func__.1386
00018573 r __func__.1393
00018fb1 r __func__.1395
00018fed r __func__.1412
0001861b r __func__.1426
00018cbc r __func__.1430
0001841e r __func__.1433
00018c43 r __func__.1444
000185ef r __func__.1448
00018cd4 r __func__.1469
00018fab r __func__.1469
00018be8 r __func__.1481
00018c9f r __func__.1485
00018ae3 r __func__.1495
00018fa5 r __func__.1499
00018bdf r __func__.1508
00018fa0 r __func__.1523
000188df r __func__.1532
000188d5 r __func__.1557
000188c8 r __func__.1609
00018f9a r __func__.1618
00018b1f r __func__.1635
00018b16 r __func__.1676
000186b8 r __func__.1684
00019312 r __func__.1689
000186d9 r __func__.1700
00018b0b r __func__.1716
00018ac8 r __func__.1743
0001907c r __func__.1743
000192ec r __func__.1747
0001906d r __func__.1774
000186c9 r __func__.1785
00018964 r __func__.1785
0001905c r __func__.1797
000193a4 r __func__.1829
00019094 r __func__.1838
00019087 r __func__.1850
00019052 r __func__.1867
000193e7 r __func__.1887
00019046 r __func__.1901
00019037 r __func__.1927
000193d8 r __func__.2035
00019341 r __func__.2054
00019333 r __func__.2065
000193c3 r __func__.2085
000193d1 r __func__.2115
000192b4 r __func__.2165
00019352 r __func__.2181
00019394 r __func__.2235
0001937f r __func__.2246
0001936d r __func__.2269
00019364 r __func__.2282
0001935e r __func__.2300
0001934d r __func__.2319
0001932e r __func__.2341
0001930d r __func__.2399
00019305 r __func__.2412
000192e0 r __func__.2428
000192d2 r __func__.2443
000192c7 r __func__.2463
000192a9 r __func__.2476
00019274 r __func__.2492
0001a5dc A _edata
0001bbf8 A _end
000180b5 T atoi
00013907 T bget
00013b58 T bgetr
00013bf6 T bgetz
00013410 T bpool
00013558 T brel
00012b2f T crc32
0001b080 b crc_table
0001bbf8 A end


bss段是用来存放程序中未初始化的全局变量的一块内存区域。

在这里,可以看到bss段中存储着一些系统库函数的地址。

具体bss段详细内容,下次再补充。

Init_BSS函数到此结束。

----------------------------------------------------------------------------------------------------

再来看主函数中的下一个函数Init_Screen()的实现

位于./src/geekos/screen.c中

/*
 * Initialize the screen module.
 */
void Init_Screen(void)
{
    bool iflag = Begin_Int_Atomic();

    s_cons.row = s_cons.col = 0;
    s_cons.currentAttr = DEFAULT_ATTRIBUTE;//设置终端界面的当前属性,默认为黑底灰字
    Clear_Screen();

    End_Int_Atomic(iflag);
}

Init_Screen()中使用了Begin_Int_Atomic()和End_Int_Atomic()
来保护临界区代码的执行,仔细来看一下这两个函数的实现。

位于./include/geekos/int.h

/**
 * Start interrupt-atomic region.
 * @return true if interrupts were enabled at beginning of call,
 * false otherwise.
 */
static __inline__ bool Begin_Int_Atomic(void)
{
    bool enabled = Interrupts_Enabled();
    if (enabled)
        Disable_Interrupts();
    return enabled;
}   

__inline__标志表示内联,意思就是直接把内联函数的代码嵌入到调用它的函数里面去,就像宏定义一样,直接使用,省下了进入子函数,CPU保护上下文还原上下文的开销。

再来看Begin_Int_Enable()下的Interrupts_Enabled()函数

位于/src/geekos/int.c中

/*
 * Query whether or not interrupts are currently enabled.
 */ 
bool Interrupts_Enabled(void)
{   
    ulong_t eflags = Get_Current_EFLAGS();
    return (eflags & EFLAGS_IF) != 0;
}     
接着看Interrupts_Enabled()中的Get_Current_EFLAGS()

位于/src/geekos/lowlevel.asm

; Return current contents of eflags register.
align 16
Get_Current_EFLAGS:
	pushfd			; push eflags
	pop	eax		; pop contents into eax
	ret

EFLAGS_IF定义在./include/geekos/int.h
/*
 * The interrupt flag bit in the eflags register.
 * FIXME: should be in something like "cpu.h".
 */ 

#define EFLAGS_IF (1 << 9)

基本上看懂了,Get_Current_EFLAGS()函数返回eflags寄存器的值,eflags的第9位是IF中断许可控制位,1开启可屏蔽中断响应,0关闭可屏蔽中断响应。
Interrupts_Enabled()函数返回1说明中断开启,0说明中断未开启。

再回到内联函数Begin_Int_Atomic中,它完成:若中断状态为开启,则执行Disable_Interrupts()关闭中断响应,返回1,否则返回0。

Disable_Interrupts()函数位于./include/geekos/int.h中

#define Disable_Interrupts()            \
do {                                    \
    KASSERT(Interrupts_Enabled());      \
    __Disable_Interrupts();             \
} while (0)

这里有个宏KASSERT(),它证实表达式为真,否则死机,来看一下它的实现。

位于./include/geekos/kassert.h

define KASSERT(cond) 					\
do {							\
    if (!(cond)) {					\
	Set_Current_Attr(ATTRIB(RED, GRAY|BRIGHT));	\
	Print("Failed assertion in %s: %s at %s, line %d, RA=%lx, thread=%p\n",\
		__func__, #cond, __FILE__, __LINE__,	\
		(ulong_t) __builtin_return_address(0),	\
		g_currentThread);			\
	while (1)					\
	   ; 						\
    }							\
} while (0)

确保中断必须开着之后,使用__Disable_Interrupts()关闭中断.
__Disable_Interrupts()为内联函数,并嵌入汇编代码cli进行关闭中断。

位于./include/geekos/int.h

/*
 * Block interrupts.
 */
static __inline__ void __Disable_Interrupts(void)
{
    __asm__ __volatile__ ("cli");
}

这么多终于把Begin_Int_Atomic()函数完整的讲完了。

其实它就是完成:如果之前中断是开着的,则关中断,关中断之前判断中断确实是开着的;如果中断是关的,不做操作。


再来看临界区结束的End_Int_Atomic()开中断函数
位于./include/geekos/int.h中

/**
 * End interrupt-atomic region.
 * @param iflag the value returned from the original Begin_Int_Atomic() call.
 */
static __inline__ void End_Int_Atomic(bool iflag)
{
    KASSERT(!Interrupts_Enabled());
    if (iflag) {
        /* Interrupts were originally enabled, so turn them back on */
        Enable_Interrupts();
    }   
}

函数Enable_Interrupts()用于开中断,它再次确认了中断是关闭的
位于./include/geekos/int.h

/*
 * Unblock interrupts.
 */
static __inline__ void __Enable_Interrupts(void)
{
    __asm__ __volatile__ ("sti");
}
#define Enable_Interrupts()		\
do {					\
    KASSERT(!Interrupts_Enabled());	\
    __Enable_Interrupts();		\
} while (0)


看到这里基本上理解了临界区确保原子执行的原理了:
iflag是进入代码前的一个中断的状态标志,0标志中断关闭,1标志中断开启。


如果在进入临界区之前中断是关闭的(iflag==0),那么Begin_Int_Atomic()不做任何事情,它只是返回了中断标志iflag。
End_Int_Atomic()则会再次确认在临界区中的代码运行时,中断确实是关闭的,然后返回(如果你在临界区中开启了中断,系统死机)。


如果在进入临界区之前中断是开启的(iflag==1),那么Begin_Int_Atomic()会关闭中断,返回iflag。
临界区代码结束之后,End_Int_Atomic()会再次确认中断确实是关闭的,然后开启中断,还原进入互斥区之前的中断状态。


注意
1. Begin_Int_Atomic()和End_Int_Atomic(),它们本身属于内联函数,内部代码的调用的方法要么也是内联函数,要么就是宏,总之最后编译出的是一个整体,不会调用外部的代码。
2. 在临界区也可以调用Begin_Int_Atomic()和End_Int_Atomic(),只要他们成对出现就可以。
3. KASSERT()中调用了子函数Set_Current_Attr(),不过在子函数中同样使用了成对的Begin_Int_Atomic()和End_Int_Atomic()。


至此main.c中的第二个函数Init_Screen()分析完毕。


下一篇来分析下一个函数Init_Mem()。



你可能感兴趣的:(GeekOS源代码学习(3)Main函数中Init_BSS() 与Init_Screen())