yuyixuantai的笔记 https://bbs.21ic.com/?476561 [收藏] [复制] [RSS] 我思,我在,我行于固件程序之旅

日志

揭开程序运行神秘面纱

已有 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;
}
    这里会比前述执行一些额外的操作,它们在程序中已经非常清楚,不需多述,唯一值得说明的是,应用程序总是被导入一个固定的逻辑地址之处,而每个应用程序在被切入不同的物理之中。
    至此,三种常见的基本模式探讨完毕。

路过

鸡蛋

鲜花

握手

雷人

评论 (0 个评论)