程序如何執行和程序入口

[摘要] 我們的程序進入到入口函數之前,是發生了很多事情的。操作系統的安排,啓動運行時庫,運行時庫再初始化好環境,然後啓動你的入口函數,你的程序才正常的運行起來。等你的程序運行結束後,就退回到運行時庫,然後再退回到操作系統,然後系統再調度其他程序執行。
    在系統把使用權交給我們的這個過程,就是系統安排我們程序運行的過程,也就是準備進入我們程序的入口函數main或者WinMain的過程。操作系統時刻都在運行中,除非你關機斷電了。而負責管理各個程序運行的部分就是系統的調度程序。它一直和交通警察一樣的,管理進程的運作。當你雙擊的exe程序時,系統會檢測到你的鼠標的動作,從而進行處理。如果發現你雙擊的是某個exe,系統發現你想要執行一個程序,便會安排讓你的程序執行。而這個安排的人就是系統的調度程序。調度程序分析我們的exe,獲取程序的類型,然後才能知道我們程序需要什麼基礎環境。這裏說的基礎環境,指的是,程序要運行需要的基礎運行庫。我們用C語言寫的程序需要C運行時庫,C++的則需要C++運行時庫等等,其他的程序自然也需要這些基本庫。這些庫與系統無關。你在開發時,選用的開發環境和工具,都會決定程序是什麼類型,這個與前面說的程序的運行平臺不一樣。Windows程序運行的平臺環境是Windows操作系統,而這個系統中還有各種基礎環境,保證這個程序能夠正常運行的。一般這些都叫做運行時庫。我們用C/C++開發的,如果沒有C/C++運行時庫的支持,系統就無法啓動你的程序了。
    下面來看一個圖示。
程序如何啓動如何進入入口函數示意圖 
    圖中展示的是一個操作系統的調度程序的示意圖。我們雙擊了exe,系統先捕獲的這個動作,將這個請求放入調度隊列,然後調度程序再調度運行。調度程序要先要根據程序的類型,來啓動對應需要的運行時庫,然後才進入到我們程序執行。而這運行時庫,是我們程序運行起來的基礎支持,就像需要先打開嘴巴,才能吃飯一樣。運行時庫簡單來說,就好像是你這個程序需要的管家。它時刻在關注程序的運行,如果程序崩潰異常,這個運行時庫會知道的,從而做出處理。當然,運行時庫運行在系統的監控之內。運行時庫有點像你的程序的保姆,同時與操作系統保持聯繫,算是操作系統和你程序的中間聯繫人。如此來理解一下運行時庫,也就不難懂了吧。爲什麼要做運行時庫,因爲你程序運行時需要用到這個基本庫咯。而這個運行時庫,需要由系統來啓動運行。
    總結來看,我們的程序進入到入口函數之前,是發生了很多事情的。操作系統的安排,啓動運行時庫,運行時庫再初始化好環境,然後啓動你的入口函數,你的程序才正常的運行起來。等你的程序運行結束後,就退回到運行時庫,然後再退回到操作系統,然後系統再調度其他程序執行。
    下面一個簡單的程序,從代碼上看看這個效果。我們寫這個代碼如下:
void main()
{
    int i = 0;
}

    然後再這個唯一幾句代碼裏打個斷點。光標放在這句代碼上,按F9即可。打了斷點後,按F5進入調試,調試的界面如下:
    調試程序,設置斷點
    這個箭頭表示,程序已經進入了我們的程序,那麼我們來看看進入的過程的代碼執行過程。在VS界面上找到調用哦堆棧小窗口,然後你會找到以下調用堆棧窗口:
    main啓動過程調用堆棧
    如果你看到的不是這樣的,有很多問號的,或者顯示什麼不可用符號等等,在對應的那條上面,右擊點擊顯示或導入“符號”的菜單,然後VS自動更新符號,這樣就可以顯示出這些函數分符號名了。
    堆棧的特點就是先進後出,先進的在底部,這裏就是這樣的。
    執行的順序從底部到頂部,從頂部可以看出,後面的main()表示正在執行到main函數中了。我們從最底部開始往上看。底部的兩條,ntdll.dll是Windows系統的一個核心庫,也是系統的核心功能庫之一,後面的RtlUserThreadStart表示的就是系統在啓動我們的exe,並創建了一個進程主線程。然後,第三句kernel.dll這個庫裏執行了BaseThreadInitThunk執行了我們的進程的主線程的初始化工作,包括分配線程內存等。
    然後基本的系統初始化工作都執行完畢,然後就要開始啓動我們的主線程執行了。這個過程就是圖中說的啓動程序到調度程序做一些初始化工作。接下來就會去啓動運行時庫。在接下來的五個函數執行中,都可以看到前面ConsoleApplication3開頭,這個是我們的程序文件名,這表示這幾個函數都是爲我們程序服務的,這些都是運行在我們程序的進程空間的,其實就是我們程序所佔的內存塊中。mainCRTStartup()函數的CRT就是C RunTime(C運行時庫)的意思,這裏就是C運行時庫的函數了,它在準備啓動main函數的執行了。不過這裏纔剛剛啓動,是在做初始化運行時環境,就是調用後面的函數__scrt_common_main()。這個函數中做了基本的運行時環境初始化後,又調用__scrt_common_main_seh()。這個函數也做了一系列的初始化工作,然後調用invoke_main()函數,去調用main函數運行。
    invoke_main()函數代碼如下:
static int __cdecl invoke_main() throw()
{
    return main(__argc, __argv, _get_initial_narrow_environment());
}

    你可以看到,這個就是一個簡單的調用而已,就這樣就進入了我們的main函數的執行。而對於這個幾個函數的代碼,你可以直接在調用堆棧中雙擊就可以看到了。
    調用堆棧中,上一個函數是被底下那個函數所調用的,所以這個叫做調用堆棧。
    綜上所述,你可以從上部分描述中感受到這個過程,在下面的代碼級別中,又再一次驗證了這個過程,想必對此過程一定更加影響深刻了。而我們的程序代碼就是在這個過程完成後,進入到我們的入口函數開始執行的。
    然後程序執行完畢後,調用堆棧的函數依次執行完退出,最終又回到了系統的調度函數中執行其他程序。
   
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章