Linux環(huán)境變量&&進(jìn)程地址空間詳解
一、初步認(rèn)識(shí)環(huán)境變量
1.1常見的環(huán)境變量
- PATH:Linux系統(tǒng)下的指令命令的默認(rèn)搜索路徑
- HOME:用戶登錄shell的默認(rèn)主工作目錄
- SHELL:當(dāng)前Shell,它的值通常是/bin/bash
為什么我們運(yùn)行自己的可執(zhí)行程序需要加./,而一些指令可以直接執(zhí)行,這是因?yàn)榇嬖诃h(huán)境變量PATH,它是Linux下指令的默認(rèn)搜索路徑,當(dāng)運(yùn)行一個(gè)指令時(shí),操作系統(tǒng)會(huì)到PATH中去查找該指令的所在路徑 ,當(dāng)我們把我們可執(zhí)行程序的路徑添加到PATH中,也可像指令一樣直接執(zhí)行,不需要添加./
和環(huán)境變量相關(guān)的指令
- 1. echo: 顯示某個(gè)環(huán)境變量值
- 2. export: 設(shè)置一個(gè)新的環(huán)境變量
- 3. env: 顯示所有環(huán)境變量
- 4. unset: 清除環(huán)境變量
- 5. set: 顯示本地定義的shell變量和環(huán)境變
1.2環(huán)境變量的基本概念
環(huán)境變量是系統(tǒng)引入的一套name=value形式的變量,不同的環(huán)境變量具有不同的用途,環(huán)境變量具有全局屬性
環(huán)境變量一般是指在操作系統(tǒng)中用來指定操作系統(tǒng)運(yùn)行環(huán)境的一些參數(shù)
有了全局屬性這個(gè)概念,以下要引入命令行參數(shù)來解釋這個(gè)概念
二、命令行參數(shù)
2.1通過命令行參數(shù)獲取環(huán)境變量
main函數(shù)有三個(gè)參數(shù),int agrc ,char*argv[ ] ,char*evn[ ]
當(dāng)我們?cè)谳斎敫鞣N指令,運(yùn)行各種程序時(shí),本質(zhì)上輸入的都是一個(gè)一個(gè)的字符串,bash會(huì)根據(jù)空格將這些字符串一一劃分,argc就是用來記錄劃分字符串的個(gè)數(shù)的,argv是一張叫作命令行參數(shù)的表(本質(zhì)上是一個(gè)指針數(shù)組),里面存儲(chǔ)的是被bash分割形成的一個(gè)個(gè)字符串的地址
為什么需要命令行參數(shù)表呢?因?yàn)檫@樣可以為我們的指令、軟件、軟件等提供命令行選項(xiàng)支持!
命令行參數(shù)表是以NULL為結(jié)束的
實(shí)例:通過argv查看命令行參數(shù)
實(shí)例:通過命令行參數(shù)argc和agrv來進(jìn)行選項(xiàng)的設(shè)置
main函數(shù)中還有一個(gè)參數(shù)evn,它對(duì)應(yīng)是一張環(huán)境變量表,可以通過打印該表來查看系統(tǒng)的所以環(huán)境變量
通過比較可以發(fā)現(xiàn),子進(jìn)程的環(huán)境變量和env展示出來的環(huán)境變量一模一樣!這就可以解釋為什么環(huán)境變量具有全局屬性了!我們所運(yùn)行的進(jìn)程都是子進(jìn)程,bash本身在啟動(dòng)時(shí)會(huì)從操作系統(tǒng)的配置文件中讀取環(huán)境變量, 形成一張環(huán)境變量表,這個(gè)表會(huì)被子進(jìn)程的main函數(shù)參數(shù)接收,也就是說,子進(jìn)程會(huì)繼承父進(jìn)程交給它的環(huán)境變量,所有建立在父進(jìn)程上的子進(jìn)程都會(huì)有相同的一份環(huán)境變量2.2本地變量和內(nèi)建命令
本地變量:只會(huì)在本bash內(nèi)部有效,不會(huì)被子進(jìn)程繼承
set可以查看所以變量(環(huán)境變量&&本地變量)
export可以把本地變量設(shè)置成成環(huán)境變量
子進(jìn)程中也可以查找到一份
通過unset可清除設(shè)置的環(huán)境變量
我們知道任何程序和指令在運(yùn)行時(shí)都是bash的子進(jìn)程,本地變量只對(duì)本bash內(nèi)部有效,那么這里就有一個(gè)問題了:為什么使用echo可以顯示出MY_VALUE的內(nèi)容呢?
這里就要再引入一個(gè)新的概念:內(nèi)建命令
- 常規(guī)命令:通過創(chuàng)建子進(jìn)程完成的
- 內(nèi)建命令:bash不創(chuàng)建子進(jìn)程,由bash自己執(zhí)行,類似bash通過調(diào)用自己內(nèi)部實(shí)現(xiàn)的函數(shù)或者系統(tǒng)提供的函數(shù),比如echo、cd都屬于內(nèi)建命令,比如我們?cè)赾d時(shí)改變的是bash下的工作目錄,并不會(huì)去改變子進(jìn)程所在的目錄
- 模擬實(shí)現(xiàn)一個(gè)具有cd功能的指令--chdir()系統(tǒng)提供的一個(gè)改變當(dāng)前工作目錄的函數(shù)
2.3環(huán)境變量的獲取
- 第一種就是上面演示的通過命令行的第三個(gè)參數(shù)獲取
- 第二種是通過第三方變量environ獲取
第三種是通過調(diào)用系統(tǒng)函數(shù)獲取或者設(shè)置環(huán)境變量--getenv()和putenv()
三、進(jìn)程地址空間
3.1進(jìn)程(虛擬)地址空間的引入
觀察下面代碼的運(yùn)行結(jié)果:可以看到子進(jìn)程對(duì)全局變量g_val1進(jìn)行修改,父進(jìn)程和子進(jìn)程輸出變量的值不一樣,但是地址是一樣的,說明該變量所在的地址一定不是物理地址!父子進(jìn)程輸出的變量在物理地址上看來也不是同一個(gè)變量!我們?cè)谟肅/C++語言看到的也是虛擬地址,真正的物理地址用戶是看不見到,由操作系統(tǒng)統(tǒng)一管理
操作系統(tǒng)必須負(fù)責(zé)物理地址和虛擬地址之間的映射,那操作系統(tǒng)具體是如何做到的呢?
3.2進(jìn)程地址空間的布局和理解
結(jié)合fork() 初步理解地址進(jìn)程空間
前面已經(jīng)談到,fork()創(chuàng)建子進(jìn)程成功,會(huì)有兩個(gè)返回值,給父進(jìn)程返回子進(jìn)程的pid,給子進(jìn)程返回0。fork()之后創(chuàng)建的子進(jìn)程,在內(nèi)存中除了有一個(gè)以父進(jìn)程為模板(拷貝)的pcb數(shù)據(jù)結(jié)構(gòu),還有一個(gè)從父進(jìn)程拷貝下來的mm數(shù)據(jù)結(jié)構(gòu)(進(jìn)程地址空間)和一個(gè)頁表,頁表中存著變量虛擬地址和物理地址的映射關(guān)系、權(quán)限字段、判斷數(shù)據(jù)在內(nèi)存還是磁盤的字段,通過映射關(guān)系,可以找到虛擬地址對(duì)應(yīng)的物理地址
子進(jìn)程剛創(chuàng)建的時(shí)候,在mm中數(shù)據(jù)的虛擬地址和父進(jìn)程是一樣的,我們打印看到的都是這個(gè)虛擬地址,子進(jìn)程剛拷貝父進(jìn)程的數(shù)據(jù)結(jié)構(gòu)內(nèi)容的時(shí)候,代碼和數(shù)據(jù)都是共用的,并且數(shù)據(jù)在頁表中的權(quán)限會(huì)被設(shè)置為只讀
由于代碼是共享的,那么在fork()return前子進(jìn)程被創(chuàng)建好了,return就會(huì)被父子進(jìn)程各執(zhí)行一次,又由于return的實(shí)質(zhì)就是在對(duì)變量進(jìn)行寫入,這時(shí)候就相當(dāng)于要對(duì)數(shù)據(jù)進(jìn)行修改
當(dāng)子進(jìn)程和父進(jìn)程有一方要對(duì)數(shù)據(jù)進(jìn)行修改,就會(huì)觸發(fā)對(duì)數(shù)據(jù)的非法操作,從而發(fā)生缺頁中斷,此時(shí)操作系統(tǒng)就會(huì)重新在內(nèi)存中開辟一塊空間,將要修改的數(shù)據(jù)拷貝一份作修改,再重新建立映射關(guān)系,這個(gè)過程也叫寫時(shí)拷貝,這樣父子進(jìn)程就做到了各自私有一份數(shù)據(jù)
我們上面代碼所展示的結(jié)果,地址一樣變量值不一樣,這是因?yàn)榇蛴〕鰜淼牡刂肥翘摂M地址,子進(jìn)程拷貝了父進(jìn)程的數(shù)據(jù)結(jié)構(gòu)內(nèi)容,所以他們的虛擬地址就是一樣的;變量值不一樣是因?yàn)?,寫時(shí)拷貝后虛擬地址在頁表中映射的物理地址不一樣,找到的數(shù)據(jù)當(dāng)然也就并不一樣了。這兩個(gè)原因結(jié)合就說明了我們所看到的現(xiàn)象
3.3什么是地址空間?
地址總線排列組合形成的地址范圍[0,2^32) 32根地址總線
3.4地址空間如何進(jìn)行區(qū)域劃分?
進(jìn)程地址空間本質(zhì)上是描繪進(jìn)程可視范圍大小,在內(nèi)核上他是一個(gè)數(shù)據(jù)結(jié)構(gòu)對(duì)象(mm_struct),也要被 操作系統(tǒng)管理,地址空間通過各個(gè)區(qū)域的star和end對(duì)區(qū)域進(jìn)行線性(區(qū)域)劃分,在一個(gè)區(qū)域的范圍內(nèi),連續(xù)空間中,每一個(gè)最小單元都有地址,都可以被使用
區(qū)域空間的調(diào)整,本質(zhì)上就是通過調(diào)整每個(gè)區(qū)域的star和end
3.5進(jìn)一步理解進(jìn)程和進(jìn)程地址空間
目前為止,我們所說的進(jìn)程,就是:進(jìn)程=內(nèi)核數(shù)據(jù)結(jié)構(gòu)對(duì)象(pcb,mm,頁表)+程序的代碼和數(shù)據(jù)(可執(zhí)行文件)
為什么需要進(jìn)程地址空間?
讓進(jìn)程以統(tǒng)一的視角來看待內(nèi)存:如果進(jìn)程直接和物理地址進(jìn)行交互,那么進(jìn)程的pcb數(shù)據(jù)結(jié)構(gòu)中就勢(shì)必要存在各個(gè)數(shù)據(jù)的物理地址。一個(gè)進(jìn)程的各個(gè)數(shù)據(jù)部分,在物理內(nèi)存中實(shí)際是亂序的,但是有了地址空間(mm_struct)之后:代碼就在代碼段,數(shù)據(jù)就在數(shù)據(jù)段該在堆區(qū)的在堆區(qū),該在棧區(qū)的在棧區(qū)同時(shí),這些連續(xù)的虛擬地址再經(jīng)過的頁表映射到物理內(nèi)存,這樣,讓進(jìn)程數(shù)據(jù)地址從無序變有序,讓進(jìn)程以統(tǒng)一的視角看待物理內(nèi)存以及各個(gè)運(yùn)行區(qū)域,每個(gè)進(jìn)程都會(huì)以為自己占有了整個(gè)系統(tǒng)的內(nèi)存資源
攔截對(duì)內(nèi)存的非法操作和異常訪問:進(jìn)程地址空間讓我們?cè)谠L問內(nèi)存的時(shí)候有一個(gè)轉(zhuǎn)化的過程,在這個(gè)過程中,如果我們對(duì)內(nèi)存進(jìn)行了非法操作和訪問,那么就會(huì)被攔截,物理內(nèi)存不會(huì)收到影響,進(jìn)而保護(hù)了物理內(nèi)存
有了進(jìn)程地址空間和頁表,就可以做到將進(jìn)程管理模塊和內(nèi)存管理模塊進(jìn)行解耦合:進(jìn)程在運(yùn)行的時(shí)候不會(huì)關(guān)心操作系統(tǒng)是如何申請(qǐng)內(nèi)存的,對(duì)進(jìn)程的管理和對(duì)內(nèi)存的申請(qǐng)都是由操作系統(tǒng)來完成,他們互不干擾!操作系統(tǒng)不做任何浪費(fèi)時(shí)間和空間的事情,當(dāng)一個(gè)進(jìn)程申請(qǐng)了內(nèi)存,但是它又不立即使這塊內(nèi)存的時(shí)候,就相當(dāng)于占用了內(nèi)存資源,這時(shí)候操作系統(tǒng)會(huì)采用惰性加載的方式:給該進(jìn)程一個(gè)虛擬地址,但在頁表中并沒有實(shí)際映射的物理地址,也就是說進(jìn)程看到的是已經(jīng)開辟好的虛擬地址,但在物理內(nèi)存上并沒有真的申請(qǐng)到空間;當(dāng)進(jìn)程需要內(nèi)存的時(shí)候,找不到映射的物理地址觸發(fā)缺頁中斷,此時(shí)操作系統(tǒng)就會(huì)給它開辟空間,建立映射關(guān)系。從而說明了,pcb數(shù)據(jù)結(jié)構(gòu)對(duì)象的創(chuàng)建先于可執(zhí)行程序的加載
3.6頁表的理解
頁表不僅僅有虛擬地址和物理地址的映射,還有對(duì)應(yīng)的權(quán)限,當(dāng)一個(gè)進(jìn)程要對(duì)一個(gè)數(shù)據(jù)修改時(shí),本質(zhì)是通過虛擬地址找到對(duì)應(yīng)物理內(nèi)存的數(shù)據(jù)再修改,當(dāng)要修改某一個(gè)數(shù)據(jù),但是該數(shù)據(jù)在頁表的所記錄的權(quán)限只有rx,僅僅允許只讀,那么就會(huì)修改動(dòng)作就會(huì)被攔截,直接報(bào)錯(cuò),程序崩潰,修改這個(gè)動(dòng)作就不會(huì)被允許
這也就是為什么,一個(gè)程序崩潰時(shí),并不會(huì)影響其他進(jìn)程,因?yàn)楸罎⒌某绦蛟谔摂M內(nèi)存頁表層就已經(jīng)被攔截,操作系統(tǒng)會(huì)直接殺掉進(jìn)程,進(jìn)而也就不會(huì)影響其他進(jìn)程的運(yùn)行
總結(jié):頁表存在CPU的cr3寄存器(物理地址),進(jìn)程在被CPU調(diào)度和離開CPU的時(shí)候,都要帶走寄存器里的數(shù)據(jù) ,CPU在運(yùn)行程序時(shí),為了獲取數(shù)據(jù),就會(huì)通過cr3寄存器里的頁表地址找到該進(jìn)程的頁表,頁表中的虛擬地址通過映射得到物理地址,進(jìn)而可以訪問到物理內(nèi)存,同時(shí)會(huì)根據(jù)虛擬地址和物理地址的映射權(quán)限(rwx)來決定是否能對(duì)該物理內(nèi)存進(jìn)行操作,如果非法操作,該請(qǐng)求會(huì)被攔截,操作系統(tǒng)會(huì)將此進(jìn)程殺掉
三個(gè)實(shí)例體現(xiàn)頁表的作用
進(jìn)程的掛起是如何實(shí)現(xiàn)的?前面說到,頁表中還存在一個(gè)用來判斷數(shù)據(jù)是在內(nèi)存還是在磁盤中的字段,進(jìn)程的掛起就是將該進(jìn)程所對(duì)應(yīng)的數(shù)據(jù)和代碼換出到外設(shè)分區(qū)中,那么這時(shí)候操作系統(tǒng)只需要通過修該字段就可以知道進(jìn)程是否處于掛起狀態(tài)
進(jìn)程的獨(dú)立性:進(jìn)程的獨(dú)立性表現(xiàn)在,每個(gè)進(jìn)程都有自己私有的一份數(shù)據(jù),以及每個(gè)進(jìn)程都有自己的一份mm_struct進(jìn)程地址空間,這就保證了每個(gè)進(jìn)程只能訪問自己的進(jìn)程地址空間,相互之間不得訪問!也就是說,如果進(jìn)程直接和物理內(nèi)存打交道,那么就可能訪問到其他進(jìn)程的數(shù)據(jù),但是由于進(jìn)程地址空間的存在,非法訪問在頁表層面就會(huì)被攔截,確保了更個(gè)進(jìn)程之間不會(huì)非法訪問和篡改對(duì)方的數(shù)據(jù)
代碼和字符常量區(qū)的數(shù)據(jù)為什么是只讀的?如果這寫數(shù)據(jù)本身是只讀的,那么它就不可能從磁盤加載到內(nèi)存中!這邊的只讀是在頁表層面上的只讀!它在頁表中的權(quán)限被設(shè)置成只讀,當(dāng)一個(gè)進(jìn)程試圖修改該部分的數(shù)據(jù),一樣會(huì)被攔截
四、Linux內(nèi)核進(jìn)程調(diào)度隊(duì)列
4.1優(yōu)先級(jí)
- 普通優(yōu)先級(jí):100~139(我們都是普通的優(yōu)先級(jí),想想nice值的取值范圍,可與之對(duì)應(yīng)!)
- 實(shí)時(shí)優(yōu)先級(jí):0~99(不關(guān)心)
4.2活動(dòng)隊(duì)列
- 所有時(shí)間片還沒結(jié)束的進(jìn)程都被放在活動(dòng)隊(duì)列
- 本質(zhì)上是一個(gè)指針數(shù)組,數(shù)組的下標(biāo)就表示優(yōu)先級(jí),從100開始
- 調(diào)度過程
- 從[0,140)開始遍歷,找到第一個(gè)優(yōu)先級(jí)最高且非空的隊(duì)列
- 從該隊(duì)列的第一個(gè)進(jìn)程開始調(diào)度運(yùn)行
- 但由于逐一遍歷數(shù)組的效率太低下了,為了提高查找非空隊(duì)列的效率,可以采用位圖的思想,用5*32個(gè)比特位來隊(duì)列是否為空
4.3過期隊(duì)列
- 過期隊(duì)列的結(jié)構(gòu)和活動(dòng)隊(duì)列一樣
- 過期隊(duì)列上放的都是時(shí)間片過期的隊(duì)列
- 當(dāng)活動(dòng)隊(duì)列的進(jìn)程都調(diào)度結(jié)束了,那么swap交換兩個(gè)隊(duì)列的指針,就可以對(duì)過期隊(duì)列的進(jìn)程進(jìn)行時(shí)間片的重新計(jì)算,等待調(diào)度運(yùn)行
4.4active指針和expired指針
- active指針指向活動(dòng)隊(duì)列
- expired指針指向過期隊(duì)列
- 當(dāng)活動(dòng)隊(duì)列的進(jìn)程全都調(diào)度完畢,swap交換兩個(gè)指針,就相當(dāng)于有了新的活動(dòng)隊(duì)列
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
使用‘fsck’修復(fù)Linux中文件系統(tǒng)錯(cuò)誤的方法
這篇文章主要給大家介紹了關(guān)于如何使用'fsck'修復(fù)Linux中文件系統(tǒng)錯(cuò)誤的相關(guān)資料,文中通過示例代碼以及圖文介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03Ubuntu Server 16.04安裝MySQL設(shè)置遠(yuǎn)程訪問出現(xiàn)問題的完美解決方案(error:10061)
Ubuntu 16.04安裝MySQL及需要其他主機(jī)遠(yuǎn)程連接MySQL時(shí)的設(shè)置問題。在安裝過程中會(huì)出現(xiàn)各種奇葩問題,下面小編給大家?guī)砹薝buntu Server 16.04安裝MySQL設(shè)置遠(yuǎn)程訪問出現(xiàn)問題的完美解決方案(error:10061),一起看看吧2016-10-10Apache多虛擬主機(jī)多站點(diǎn)配置的兩種實(shí)現(xiàn)方案
本文介紹了在Apache服務(wù)器上配置多虛擬主機(jī)的兩種方案:基于IP地址的虛擬主機(jī)配置和基于域名的虛擬主機(jī)配置,詳細(xì)說明了每個(gè)方案的配置步驟,以實(shí)現(xiàn)在同一臺(tái)服務(wù)器上托管多個(gè)網(wǎng)站的目的,感興趣的可以了解一下2024-09-09VMware虛擬機(jī)安裝Centos操作系統(tǒng)的教程
這篇文章主要為大家詳細(xì)介紹了VMware虛擬機(jī)安裝Centos操作系統(tǒng)的教程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07