揭开程序运行神秘面纱
已有 1254 次阅读2008-4-14 21:54
|系统分类:原稿与翻译|
程序, 运行, 启动, 揭秘
作者:yuyixuantai 首发:21ic 系列:OS探究2
主要内容:详细探讨了程序初始化三种模式。
现在考虑一个问题,C程序为什么会从main函数开始,从规范的角度来说,标准以及早期的设计采用的起点恰好是main函数,如果不是采用它,也会有另一个函数出现来取代main的位置。而另一个质疑观点就是,C程序,并不总是从main开始,至少在win32应用程序上,开始点就是Winmain函数。然而,这些远远不是答案,我们,需要了mian之前到底发生了什么,需要明确的是IC上电后怎样才能跳到main函数地址处。
IC上电后一般都是从一个固定的地址处开始,然后按条执行随后的指令。很明显,一个最简单的实现的就是在使用跳转指令,使程序跳转至main。但简单的跳转是不够的,在进入mian函数之后,我们可能会使用全局变量,这就要求它们此时已经被正确的初始化,为了使程序正确运行,堆栈也应该准备就绪,甚至一些也要为外部设备(例如NORFLASH)提供相应信息。这些必须在mian函数之前完成的工作,往往都是在在一些名如startup之类的文件中进行,稍微熟练的嵌入C程序员都有过阅读乃至修改它们的经历。下面我unspIC的startup文件中部分关键代码
sp = __sn_sp_val
r1 = 0x0703
[P_MCS0_Ctrl] = r1
r1 = 0x0703
[P_MCS1_Ctrl] = r1
r1 = 0x01c2
[P_MCS3_Ctrl] = r1
[P_MAPSEL] = r1
sr &= 0x3f
sr |= HIGH6 __sn_init_table
r1 = offset __sn_init_table
r2 = DS:[r1++] // item count
jmp __judge_itcount
__next_item:
push sr to [sp]
push r2 to [sp]
r3 = DS:[r1++] // DS of iram
r4 = DS:[r1++] // dest
r2 = DS:[r1++] // DS
r5 = DS:[r1++] // src
push r1 to [sp]
r1 = DS:[r1] // block size
cmp r1, 0
je __move_ok
r2 = r2 lsl 4
r2 = r2 lsl 4
r2 = r2 lsl 2
r3 = r3 lsl 4
r3 = r3 lsl 4
r3 = r3 lsl 2
sr &= 0x3f
sr |= r2 // Initialize DS of src
__move_data:
r2 = DS:[r5++]
push sr to [sp] // store DS of src
sr &= 0x3f
sr |= r3
DS:[r4++] = r2
r3 = sr & 0xfc00
r1 = r1 - 1
pop sr from [sp] // restore DS of src
__judge_bksize:
cmp r1, 0
jne __move_data
__move_ok:
pop r1 from [sp]
r1 = r1 + 1
pop r2 from [sp]
pop sr from [sp]
r2 = r2 - 1
__judge_itcount:
cmp r2,0
jne __next_item
call _main
程序首先初始话堆栈,然后界定Nor Flash地址,接着导入初始化表,最后从只读段读入数据对全局变量进行初始化,在这些工作就绪之后,它调用mian函数。其他的单片机有着类似的操作。
然而,这样的操作是最简单化的,它的前提是程序代码已经被下载到了合适的地址处(ROM或者Nor Flash)并且这些地址能够被IC迅速地按字节进行访问。有时候IC并不会外接这些存储设备,而是用Nand Flash之类的设备来代替,很遗憾,Nand Flash需要按扇区进行读取,在它身上不能直接执行指令。所有需要被执行的代码都必须被拷贝到内存(RAM)之中,原来的处理首先必须加入初始化代码拷贝到RAM的操作,(需要执行的程序代码也将被拷贝到内存中,内存是有限的,执行代码经常远远大于内存,幸运的是,我们并不需要一开始就读入所有的代码,在初始读入之后,我们只需要反复执行这样的操作,将需要执行的代码调入内存,将暂时不需要的代码切出内存。)mian函数将被切入固定的地址处(并不是确保要在物理上规定,只要是逻辑上固定就可以了),然后调用main函数。Z80系列往往都是这样操作的。由于Nand Flash大发展,很多单片机开始执行这种模式,如Sunplus的GPL2004。而PC上大型OS的初始化代码也都是这样的,从Linux源代码中能够清楚地看到这一点。
在PC操作系统中,都能调用其他程序,这些程序都有一个mian函数,这些程序被用前述类似的模式导入。VC是通过一个_tmainCRTStartup(void)的函数来执行这样的操作。以下是这个函数的源代码,在微软SDK下crt中可以看到更多多的信息。
int _tmainCRTStartup(void)
{
int initret;
int mainret;
OSVERSIONINFOA *posvi;
int managedapp;
#ifdef _WINMAIN_
_TUCHAR *lpszCommandLine;
STARTUPINFO StartupInfo;
#endif /* _WINMAIN_ */
/*
* Dynamically allocate the OSVERSIONINFOA buffer, so we avoid
* triggering the /GS buffer overrun detection. That can't be
* used here, since the guard cookie isn't available until we
* initialize it from here!
*/
posvi = (OSVERSIONINFOA *)_alloca(sizeof(OSVERSIONINFOA));
/*
* Get the full Win32 version
*/
posvi->dwOSVersionInfoSize = sizeof(OSVERSIONINFOA);
(void)GetVersionExA(posvi);
_osplatform = posvi->dwPlatformId;
_winmajor = posvi->dwMajorVersion;
_winminor = posvi->dwMinorVersion;
/*
* The somewhat bizarre calculations of _osver and _winver are
* required for backward compatibility (used to use GetVersion)
*/
_osver = (posvi->dwBuildNumber) & 0x07fff;
if ( _osplatform != VER_PLATFORM_WIN32_NT )
_osver |= 0x08000;
_winver = (_winmajor << 8) + _winminor;
/*
* Determine if this is a managed application
*/
managedapp = check_managed_app();
#ifdef _MT
if ( !_heap_init(1) ) /* initialize heap */
#else /* _MT */
if ( !_heap_init(0) ) /* initialize heap */
#endif /* _MT */
fast_error_exit(_RT_HEAPINIT); /* write message and die */
#ifdef _MT
if( !_mtinit() ) /* initialize multi-thread */
fast_error_exit(_RT_THREAD); /* write message and die */
#endif /* _MT */
/*
* Initialize the Runtime Checks stuff
*/
#ifdef _RTC
_RTC_Initialize();
#endif /* _RTC */
/*
* Guard the remainder of the initialization code and the call
* to user's main, or WinMain, function in a __try/__except
* statement.
*/
__try {
if ( _ioinit() < 0 ) /* initialize lowio */
_amsg_exit(_RT_LOWIOINIT);
/* get wide cmd line info */
_tcmdln = (_TSCHAR *)GetCommandLineT();
/* get wide environ info */
_tenvptr = (_TSCHAR *)GetEnvironmentStringsT();
if ( _tsetargv() < 0 )
_amsg_exit(_RT_SPACEARG);
if ( _tsetenvp() < 0 )
_amsg_exit(_RT_SPACEENV);
initret = _cinit(); /* do C data initialize */
if (initret != 0)
_amsg_exit(initret);
#ifdef _WINMAIN_
StartupInfo.dwFlags = 0;
GetStartupInfo( &StartupInfo );
lpszCommandLine = _twincmdln();
mainret = _tWinMain( GetModuleHandleA(NULL),
NULL,
lpszCommandLine,
StartupInfo.dwFlags & STARTF_USESHOWWINDOW
? StartupInfo.wShowWindow
: SW_SHOWDEFAULT
);
#else /* _WINMAIN_ */
_tinitenv = _tenviron;
mainret = _tmain(__argc, _targv, _tenviron);
#endif /* _WINMAIN_ */
if ( !managedapp )
exit(mainret);
_cexit();
}
__except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) )
{
/*
* Should never reach here
*/
mainret = GetExceptionCode();
if ( !managedapp )
_exit(mainret);
_c_exit();
} /* end of try - except */
return mainret;
}
这里会比前述执行一些额外的操作,它们在程序中已经非常清楚,不需多述,唯一值得说明的是,应用程序总是被导入一个固定的逻辑地址之处,而每个应用程序在被切入不同的物理之中。
至此,三种常见的基本模式探讨完毕。