Table of Contents
2.4.2 爲什麼調用exit、return正常終止時,會刷新標準io的緩存
0.那麼進程所需的運行環境有哪些?
所需環境有:啓動代碼、環境變量、c程序的內存空間佈局、庫等。
1. 啓動代碼
1.1 啓動代碼的作用
所有高級語言的程序,都有自己的啓動代碼。
C程序運行時,最開始運行的是啓動代碼,啓動代碼再去調用main函數,然後整個C程序都已運行。
總之,高級語言程序 = 啓動代碼 + 自己代碼。
1.2 啓動代碼是由誰提供的
(1)啓動代碼一般都是由編譯器提供的,一般有兩種提供方式
1)源碼形式
以源碼形式提供時,編譯器會將啓動代碼的源文件和自己程序的源文件一起編譯。
像開發單片機這種沒有OS的計算機的C程序時,啓動代碼一般是源碼形式提供
2)二級制的.o(目標文件)形式
直接以.o形式提供時,省去了我自己對“啓動代碼”的編譯。
如果開發的程序是運行在OS上時,那麼編譯器一般是以.o形式來提供啓動代碼
比如我們gcc a.c時,gcc就是以.o形式提供的,基於OS運行的程序的啓動代碼,相對而言,自然比較複雜些。
gcc時加一個-v選項,查看gcc編譯鏈接的詳細情況時,可以看到有很多.o,這些.o就是gcc提供的啓動代碼。
(2)gcc -v
gcc -v a.c
在編譯的詳細信息裏面,有很多的事先就被編譯好的.o文件,這些.o文件就是用來生成啓動代碼的
1.3 啓動代碼做了些什麼
1.3.1 啓動代碼使用什麼語言編寫的
基本都是彙編寫的。
1.3.2 啓動代碼大致做了些什麼呢?
大致上有兩件重要的事情:
· 對c程序的內存空間進行佈局,得到c程序運行所需要的內存空間結構。
· 留下相應庫接口
(1)對c內存空間進行佈局
c等高級語言程序在運行時,函數調用需要“棧”,啓動代碼就需要在c內存空間上建立“棧”,
說白了就是從從c內存空間中劃出一段空間,然後以“棧”的形式來進行管理。
· 思考:爲什麼啓動代碼,基本都是使用匯編來編寫?
- 在程序的內存空間結構還沒有佈局起來之前,高級語言程序還無法運行,此時只能使用匯編
- 當利用匯編編寫的啓動代碼將高級語言的內存空間結構建立起來後,自然就可以運行c/c++等高級語言的程序了。
(2)爲庫的調用預留接口
如果程序使用的是動態庫的話,編譯時,動態庫代碼並不會被直接編譯到程序中,只會留下相應的接口
程序運行起來後,纔會去對接庫代碼,爲了能夠對接動態庫,啓動代碼會留下動態庫的對接接口。
1.4 程序是如何運行起來的
1.4.1 裸機的情況
(1)內存和硬盤一體式
1)典型的比如51單片機,51沒有單獨的內存和單獨硬盤,使用的是內存和硬盤功能二合一的norflash。
(a)爲什麼能身兼內存的功能?
因爲norflash的訪問速度很快,因此cpu能夠直接從norflash上讀取指令並執行,此時norflash就是一個內存。
(b)爲什麼身兼硬盤的功能?
因爲norflash能夠永久保存數據,設備關電後,數據依然存在
2)程序運行的過程
(2)內存和硬盤分開式
比如arm芯片
1)爲什麼傳統的計算機,硬盤和內存都是分開的
2)內存和硬盤分開的這種情況,如果直接以裸機方式使用的話,程序是如何運行起來的
兩種:
第一種:直接將下載到內存中,然後運行。
第二種:先下載硬盤永久保存,開機時自動從硬盤中將代碼拷貝到內存上,然後運行。(a)將程序直接下載到內存
這種方式最大的缺點就是掉電就沒了,所以這種方式只適合於平時的測試 。
(b)將程序下載到硬盤
1.4.1 有OS的情況
上OS的計算機,基本都是內存和硬盤分開式的情況。
OS是怎麼運行啓動起來的呢?
(1)有OS時,可執行程序都是直接放在了硬盤上
(2)有OS時,程序如何運行起來
1)有OS支持時,如何啓動程序呢
(a)裸機時,是怎麼啓動程序的呢?
(b)有OS支持時,啓動程序方式有三種
· 雙擊快捷圖標運行
· 在命令行運行
· 設置爲開機自啓動
2)OS是怎麼實現拷貝的對於我們自己寫的裸機程序來說,我們需要自己寫拷貝代碼
對於OS這個裸機程序來說,由啓動程序來負責代碼的拷貝。
2. 進程(程序)的終止方式
2.1 正常終止
進程主動調用終止函數/返回關鍵字所實現的結束,就是正常終止。
· main調用return關鍵字結束
· 程序任何位置調用exit函數結束
· 程序任何位置調用_exit函數結束
1)main函數調用return關鍵字
return關鍵字的作用是返回上一級函數,如果main函數的子函數調用return的話,返回的上一級是main函數。
如果main函數調用return的話,main函數所返回的上一級是啓動代碼。
(a)顯式調用
· 返回值的意義
疑問:如果return時我不寫返回值會怎樣呢?
(b)隱式調用
就是不明寫出return,當main函數中的最後一句代碼執行完畢後,會默認的調用return返回
不過隱式return時,默認返回0。
2)在程序的任意位置調用exit函數
其實,main函數調用return返回到啓動代碼後,啓動代碼也是調用exit函數來實現正常終止的。
#include <stdlib.h> void exit(int status);
這個參數就是返回值(進程終止狀態)。
main函數調用return將返回值返回給啓動代碼後,啓動代碼又會調用exit(返回值),將返回值返回。
3)在程序的任意位置調用_exit函數
_exit是一個系統函數(系統API),而exit是c庫函數,exit就是調用_exit來實現的。
4)return、exit和_exit的返回值問題
return、exit和_exit的返回值,也被稱爲進程終止狀態。
(a)裸機時:
(b)有OS時:
return、exit、_exit,使用哪種來返回都行。
2.2、異常終止
進程不是因爲return、exit和_exit函數而終止的,而是被強行發送了一個信號給無條件終止了,這就是異常終止。
2.3 atexit函數
函數原型
#include <stdlib.h> int atexit(void (*function)(void));
登記“進程終止處理函數”有什麼意義?
2.4 有OS時,進程從啓動 到 正常終止的全過程
2.4.1 圖示
2.4.2 爲什麼調用exit、return正常終止時,會刷新標準io的緩存
有關標準IO的庫緩存的緩衝有三種,無緩衝、行緩衝、全緩衝
(1)回顧行緩衝
標準輸出(printf)的庫緩存就是行緩衝的,在緩存中積壓數據,直到以下情況時,纔會刷新輸出,否則一直擠壓】
(2)爲什麼調用exit正常終止時,會刷新標準io的緩存呢?
- 因爲exit會調用fclose關閉所有的標準io,關閉時會自動調用fflush來刷新數據。
- 這裏要注意:如果進程時異常終止的話,是不會刷新緩存區的,因爲異常退出時,跟exit函數半毛錢關係都沒有。
2.5 命令行參數
第一個參數永遠都是程序名
將命令行參數傳遞給main函數形參的過程
3. 環境變量表
3.1 windows的環境變量
3.1.1 爲什麼在命令行執行我自己的程序,需要指明路徑
在windows下,如果你不加路徑的話,會道默認到當前路徑下找程序,沒有的話就找不到你的程序。
3.1.2 能不能不加路徑,我隨便在什麼目錄下都可執行我的程序呢?
當然可以,只要把程序所在路徑,加入windows的path環境變量即可。
3.1.3 爲什麼設置了path環境變量後,可以不加路徑就能執行程序
(1)path這個環境變量的作用
專門記錄各種可執行程序所在路徑。
(2)path記錄後
(3)path的意義
一般來說,我們自己的安裝的程序,都沒有設置環境變量
3.1.4 再來看看windows的環境變量
3.2 環境變量表
(1)什麼是環境變量表
用於存“放環境變量”的表,就是環境變量表。
什麼是環境變量呢?
答:其實就是進程在運行時,會用到的一些字符串信息,環境表就好比是工具箱,
裏面放了各種進程運行時需要用到的“工具”,比如各種的路徑。
(2)環境變量文件
(3)每個進程的環境變量表
每一個進程都在自己的內存空間(堆空間)保存了一份自己的環境變量表。
每個進程空間中的環境變量表又是怎麼來的?
顯然從環境變量文件中得來的。
(4)如果某環境變量的數據有很多條,在環境變量表中,多條數據之間怎麼區分
在windows這邊使用;分隔,Linux這邊則使用:分隔。
(5)爲什麼只有重新打開“命令行窗口”後, 新設置的“環境變量”才生效?
3.3 Linux的環境變量
修改Linux的環境變量表
(1)永久修改
1)圖形方式操作
2)直接修改“環境變量文件”
(2)臨時修改
· 什麼是臨時修改?
就是隻修改當前進程自己的“環境變量表”,其它不相關進程“環境變量表”及“環境變量文件”中數據,不會發生任何變化,
· 如何實現臨時修改
1)如何使用命令來修改“命令行窗口進程”的環境變量表
(a)查看所有環境變量
(b)顯示單個的環境變量
(c)添加一個新的環境變量
(d)修改已有環境變量
(d)刪除
2)通過API修改環境變量
對於我們自己所寫的程序來說,我們可以調用API來修改自己所寫程序的“環境變量表”。
(a)獲取環境表中的所有環境變量
1 environ全局變量: char **environ;
- environ與main函數的argv一樣,指向的都是一個字符串指針數組。
argv:與命令行參數有關
environ:與環境變量表有關
2 main函數的第三個參數: char **environ
(b)調用API:實現環境變量的添加、修改等
· putenv、setenv:添加和修改環境變量
#include <stdlib.h> int putenv(char *string); int setenv(const char *name, const char *value, int overwrite);
· unsetenv:刪除環境變量
#include <stdlib.h> int unsetenv(const char *name);
·getenv:獲取環境變量
(c)疑問:我自己所寫程序的環境表是怎麼來的
我命令行窗口執行./a.out,那麼a.out進程就屬於“命令行窗口進程”的子進程,子進程的環境表是從父進程複製得到的
當有OS支持時,基本所有的進程都是由父進程“生”出來的:
疑問1:最原始的進程從哪來的
答:OS啓動完畢後演變得到的。
4. c程序內存空間佈局
1)什麼是c程序的內存空間
c程序運行時,是運行在內存上的,也就是說需要在內存上開闢出一塊空間給c程序,然後將C代碼從硬盤拷貝到內存空間上運行,至於說是不是將代碼全部會被拷貝到內存上,這就不一定的了
2)c程序的內存空間結構
(a)有結構的要求嗎?
有,這段空間必須佈局爲c程序運行所需的空間結構,c程序才能運行
如果空間沒有佈局好,進程將無法運行,因此程序的內存空間佈局是非常重要的進程環境。
(b)c的內存結構是誰來構建的
是由啓動代碼來搭建的,比如啓動代碼會把c內存空間的某一部分空間構建爲“棧”,或者說以“棧”的方式來管理這片空間。
(c)不光是C程序
所有高級語言的程序在運行時,都涉及內存空間的結構佈局,不過它們的結構都是相似的
5. 庫
寫程序時,絕對不可能從零開始寫代碼,都是要依賴別人所寫的代碼的,比如別人寫的庫,所以庫也是程序非常重要的
進程環境,沒有庫的支持,我們的程序根本做不了什麼複雜的事情。