1.馮諾依曼體系結構
結論:
--- CPU不和外設直接打交道,和內存直接打交道。
--- 所有的外設,有數據需要收入,只能載入到內存中;內存寫出,也一定是寫道外設中。
--- 為什么程序要運行必須加載到內存?
? ? ?寫好的程序存儲到磁盤上,cpu要執行代碼訪問數據,只能從內存中讀取(體系結構規定)。
--- 進度條代碼:為什么已經調用了printf函數,但是數據沒有被打印出來?
? ? ?顯示器是外設,你的代碼加載到內存cpu跑,跑完之后不是直接寫入到外設的,而是寫給內存,交給它一段時間后刷新,才被寫入到外設輸出出來。
--- 計算機怎么知道輸入設備中有數據呢?
? ? ?各種外設和cpu在控制之間存在交互。
--- cpu中的控制器是用來響應外設的一些請求,它去告訴cpu;還有一些芯片專門用來把外設數據搬到內存,數據就緒狀態的捕捉;cpu中的運算器進行算術運算和邏輯運算。
2.操作系統
操作系統是一個進行軟硬件資源管理的軟件,為用戶提供穩定的、高效的、安全的執行環境。操作系統內包括進程管理、文件系統、內存管理、驅動管理。 管理者對重大事情做決策,但不需要和被管理者直接交互。決策需要有依據,依據的來源是數據!?
計算機如何管理硬件?
用struct結構體描述硬件各屬性,然后用鏈表或其它數據結構將各硬件組織起來。
3.什么是進程?
一個運行起來(加載到內存)的程序---進程。?同一時期有很多磁盤上的程序要加載到內存上。操作系統要對這些進程進行管理,引入PCB的概念進行對進程的描述---進程控制塊struct task_struct{進程屬性},進程ID、屬性、狀態、優先級、數據......。當程序加載到內存里,系統給每一個進程匹配一個進程控制塊,操作系統遍歷鏈表(存儲單元為進程控制塊),將死亡狀態的進程清除,將優先級高的進程加載到CPU---對進程的管理轉化成了對鏈表的增刪查 。管理的理念---先描述再組織。
---進程演示,進程在調度運行的時候,具有動態屬性!
---在Linux下,當程序加載在內存中,然后將myproc二進制文件刪除掉,進程不會關閉!一個進程在運行時一定加載的是磁盤上的某個程序,如何知道進程加載的是哪個磁盤上的程序,系統上標識---getpid()獲取子進程、getppid()獲取父進程,重啟進程每次子進程id每次發生變化,但是父進程id每次都不變。我們在登陸時,操作系統給指派了一個shell,命令行上啟動的進程一般它的父進程沒有特殊情況的話都是bash!./myproc這個任務交給子進程去跑,哪個子進程出問題了不會影響到父進程,但是如果父進程出問題了命令行解釋器就會崩掉。?
---fork()創建子進程;fork是一個函數,在它執行前:父進程,執行后:父進程+子進程,fork()之后的代碼,fork()函數的返回值;pid_t,同一個變量id,會給父進程返回子進程的id,給子進程返回0。被父進程和子進程共享!可以根據返回id不同讓父子進程執行后續不同部分的代碼。
4.進程狀態:
1.運行狀態:一般一個cpu會維護一個進程的運行隊列,進程入隊列---讓進程的PCB結構體去排隊。隊列的存儲數據類型是task_struct,在運行隊列的所有進程都叫做運行狀態(包括正在運行/在運行隊列)。cpu運行處理某一個進程時,根據pcb去內存中查找儲存著對應的code/data塊去調用運行。進程的狀態就是它的一個屬性,在task_struct中存儲。
2.阻塞狀態:進程不只是只占用CPU資源,當cpu正在執行某個進程時,發現它需要調用某個外設資源時,由于外設速度很慢,就會先將它放到某外設隊列去排隊,這個狀態就叫阻塞狀態。當在外設處理完畢后,操作系統將它的狀態改為R,重新放到運行狀態中。所謂的進程不同的狀態,本質上是進程在不同的隊列中等待某種資源。
3.掛起狀態:由于內存的空間有一定的限制,阻塞狀態的任務又需要等待很長時間,d操作系統就會先將該任務的code/data換出保存在磁盤的某個區域,保留它的task_struct。阻塞狀態不一定會掛起(內存不夠才會掛起),只要不是運行狀態都有可能被掛起。當阻塞狀態結束,操作系統將該進程資源重新加載到內存,運行狀態改為R,放入cpu的運行隊列。
4.內存數據的換入換出:將進程的相關數據,從磁盤加載到內存、從內存保存到磁盤某個區域。
5.Linux下的進程
在實際的操作系統中,阻塞狀態、掛起狀態這些不會暴露給用戶,只會提供用戶有用的信息。
---前臺進程:狀態后面帶+,一直在會在顯示器打印,不能再鍵入shell命令,用ctrlC可以終止。
---后臺進程:不帶+,一直在顯示器打印但是可以鍵入別的命令也會夾雜打印,用ctrlC不能終止,只能用kill -9 pid殺死該進程。
1.查看進程運行狀態R:運行狀態時間很短,經常會進入S狀態排隊。
2.查看進程休眠狀態S:當程序調用需要訪問外設時,就會進入休眠狀態,阻塞狀態的一種。
3.查看進程深度睡眠狀態D:
4.查看進程暫停狀態T:一個正在運行的程序,kill -19?pid暫停狀態變為T ,kill -18?pid繼續運行。
5.查看進程追蹤暫停狀態t:當代碼文件進入gdb調試時,調試的過程等待輸入指令時就是追蹤暫停狀態。
6.查看進程將亡狀態Z:fork用父進程創建子進程,退出子進程,繼續運行父進程,此時子進程并沒有被回收,就是僵尸狀態。進程結束會釋放data/code,但是還沒有釋放PCB,等待回收后才能回收PCB,不回收會造成內存泄漏。
7.孤兒進程:父進程如果提前退出,子進程就會被操作系統“領養”,它的PPID變為1(操作系統進程)。例:fork用父進程創建子進程,然后殺死父進程,子進程的PPID就會變成1。
6.進程優先級(簡單了解)
1.什么叫做優先級?
先/后獲得某種資源的權利。
2.為什么會存在優先級?
資源有限,要訪問資源的進程很多。
3.Linux優先級特點---很快---PRI:老的優先級,不會被改也不能被改。
---NI:取值范圍【-20,19】。通過top修改,sudo top ,PID,新Nice值,uit退出。
在Linux當中,一個進程的 最終優先級=老的優先級 + nice;Linux支持進程運行中,通過修改nice值修改優先級,大部分情況下nice默認為0。我們可以把一個進程運行起來后調整他的優先級。
7.進程的相關特性
進程之間具有競爭性、獨立性--運行期間互不干擾。
任何時刻,只有一個進程正在運行;多個進程在多個CPU下并行運行
Windows下:一個cpu好似多個進程都在跑,時間片輪轉:不管進程執行完花多長時間,每次給定一段時間讓它占領cpu,時間到了繼續返回運行隊列排隊,采用輪轉的方式使得多個進程可以同時推進,這種方式叫做并發。通過進程切換的方式可以在一個時間段內讓多個進程“同時運行”。
進程切換:
cpu有一套寄存器:取指令、分析指令、執行指令。指令--代碼中
例:pc/eip寄存器:永遠保存當前正在執行指令的下一條指令的位置。
當我們的進程在運行時,一定會產生很多的臨時數據,比如加法運算的中間結果。這份數據屬于當前進程。cpu內部雖然有一套寄存器硬件。但是,寄存器里面保存的數據,是屬于當前進程的。
進程在運行時占有cpu,但不是占用到進程結束。進程在運行時都有自己的時間片。
上下文保護和上下文恢復:暫且認為將A進程運行中產生的臨時數據暫時保存在PCB中,剝離當前的進程,輪轉下一個進程。等到A進程繼續輪轉時,首先恢復它數據,加載到寄存器中。
8.環境變量
環境變量是操作系統為了滿足不同的應用場景而預先在系統中設置的一大堆全局變量,這些變量在整個系統中一直都會被其它進程訪問到。
command not found
要執行一個指令或程序,先找到這個程序。 ./myprocess -》 ./表示當前路徑
將我們的程序拷貝到系統安裝指令下,就可以不加./直接運行了。sudo cp myprocess? /usr/bin/
沒經過測試的程序最好不要安裝在bin下,sudo rm?myprocess? /usr/bin/
添加到環境變量中:echo $PATH輸出系統的環境變量,pwd查詢當前路徑,將當前路徑添加到環境變量中,export PATH=$PATH:/ / /。$PATH將老的環境變量先加載進來再添加新的,直接添加自己的環境變量的話會導致整個PATH文件被覆蓋。
當自己的可執行程序的路徑也被添加到環境變量中后,也可以用which查詢
cd ~,vim /etc/bashprofile -> vim /etc/bashrc配置文件中環境變量的設置,在系統啟動后加載到內存
---env?是一個外部命令,用于列出當前系統的所有環境變量及其賦值。
---PATH?是一個環境變量,用于指定操作系統在命令行中搜索可執行文件的目錄列表。
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define USER "USER"
#define MY_ENV "myval"
int mai()
{char *who = getenv(USER);if(strcmp(who,"root")==0)
{printf("user:%s\n",who);printf("user:%s\n",who);printf("user:%s\n",who);
}else printf("權限不足!\n");return 0;
}
int main()
{char *myenv = getenv(MY_ENV);if(NULL==myenv){printf("%s,not found\n",MY_ENV);return 1;}printf("%s-%s\n",MY_ENV,myenv);return 0;
USER環境變量最大的意義,可以標識當前使用Linux的用戶
bash就是一個系統進程,mycmd也會變為一個進程(會由fork創建),是bash的子進程,由于環境變量的全局屬性,子進程也能訪問環境變量;但對于自己創建的myval,只在當前bash下創建,由當前bash產生的子進程都不能訪問它。本地變量也可以用set添加:
?ls顯示的是該目錄下的文件,shell在當前目錄變化時會改變配置文件PWD
9.命令行參數
main()函數可以帶參數嗎?可以帶幾個參數?可以帶什么參數??
int main(int argc, char *argv[ ] ) ;
int min(int argc,char* argv[])
{for(int i=0;i<argc;i++){printf("argv[%d]->%s\n",i,argv[i]);}return 0;
}
在我的進程的上下文中,獲取環境變量的三種方式:
1.getenv,更加推薦。拿到的是等號后面的內容,其它兩種是整行輸出。
int main()
{char* myusr = getenv(USER);printf("%s\n",myusr);return 0;
}
?2.char *env[ ]
int main(int argc,char* argv[],char* env[])
{for(int i=0;env[i];i++){printf("%s\n",env[i]);}return 0;
}
3.extern char **environ,指向env的那張表,存儲的系統中的環境變量
int main()
{extern char **environ;for(int i=0;environ[i];i++)//這里為什么降維了?{printf("%s\n",environ[i]);}return 0;
}
補充:putenv(),改變或者添加一個環境變量
stat 獲取文件的所有屬性
10.程序地址空間
多進程在讀取同一個地址時,讀出來的值不同!-> 這里的指針指向的地址,不是物理地址,是虛擬地址。
#include<stdio.h>
#include<unistd.h>
int global = 100;
int main()
{pid_t id = fork();if(id<0){printf("error!\n");return 1;}else if(id==0){int cnt = 0;while(1){printf("我是子進程! pid:%d,ppid:%d,global:%d,&global:%p\n",getpid(),getppid(),global,&global);sleep(1);cnt++;if(cnt==10){global = 300;printf("子進程已經更改了global!\n");}}}1,17 else{while(1){printf("我是父進程! pid:%d,ppid:%d,global:%d,&global:%p\n",getpid(),getppid(),global,&global);sleep(2);}}return 0;
}
在global被改變后,子進程打印300,父進程打印100?---發生了寫時拷貝
地址空間的本質:是內核的一種數據結構 mm_struct? //以32位為例
---地址空間描述的基本空間大小是字節
---32位下能夠表示 2^32次方個地址->只要保證唯一性即可
---2^32*1字節 = 4GB空間范圍
---每一個字節都要有唯一的地址,給每一個字節用32bit位的地址表示
為什么 存在地址空間
---1.如果讓進程之間直接訪問物理內存,萬一進程越界非法操作呢?
? ? ??非常不安全。頁表攔截非法操作!
---2.地址空間的存在可以更方便的進行進程和進程數據代碼的解耦,維持進程獨立性這樣的特征。
寫時拷貝:任何一個進程嘗試寫入時,操作系統先進行數據拷貝,更改頁表映射,然后再讓進程進行修改。操作系統,為了保證進程的獨立性,做了很多工作! ---通過地址空間和頁表,讓不同的進程,映射到不同的物理內存處。
---3.再一次理解地址空間
在磁盤中的可執行程序里面,有沒有地址呢?(還沒有加載到內存的時候),這個地址是什么地址?
---調C庫函數的時候需要鏈接,鏈接的過程就是將庫中需要用到的函數的地址加載到程序中形成可執行程序,這個地址叫做邏輯地址。
---虛擬地址空間,不是只有操作系統會遵守對應的規則,編譯器也要遵守!編譯器在編譯代碼時,會按照虛擬地址空間規則劃分代碼區/數據區(堆和棧運行時才會產生),編譯器按照虛擬地址空間的方式同時對我們的代碼和數據進行編址。當程序被加載到物理內存中的時候,該程序對應的指令和數據,都天然的具有了物理地址!