1. 瞭解馮諾依曼體系結構
自行了解 不做贅述。
2. 什麼是操作系統 如何瞭解它
操作系統最主要的作用是對一臺計算機軟硬件資源的管理。
操作系統主要進行四個方面的管理:
文件管理, 進程管理, 內存管理, 驅動管理(由內核進行)
操作系統還包括其他程序 比如庫函數 、shell(命令行解釋器)程序。
操作系統的目的:
1.與硬件交互 管理系統的軟硬件資源
2.爲用戶程序提供一個良好的執行環境。
操作系統是一款純正的”搞管理“的軟件
必須認識到一個普遍真理即——-描述是管理的前提
要有效管理一個被管理的對象必不可少的要描述其性質。
(例如學校的教務管理系統中有每位同學的學生信息)
有了其性質的描述以後再對其進行組織。
如何描述 ???——–通過struct描述複雜的性質。
如何組織???———通過鏈表 樹 等數據結構進行有效組織。
計算機的層次性
理解區分庫函數與系統調用
- 在開發角度,爲了保護操作系統安全。操作系統對外表現爲一個被包裹起來的整體,以向外暴露一個個接口的方式提供功能。這些接口叫系統調用。
- 系統調用功能比較基礎, 對使用它的用戶對操作系統知識的要求較高,鑑於此 開發者們開發了庫函數對系統調用進行封裝 。有了庫,就更有利於更上層的用戶進行開發。
????爲什麼有了系統調用還要有庫函數
1. 庫函數擴展了系統調用的功能。
2. 庫函數提供了不同操作系統平臺下的可移植性保障。
3. 庫函數的使用更方便。
初探進程
- 進程是程序執行的實例、是正在執行的程序。
- 從內核的角度來看 是擔當分配系統資源(CPU 、內存)的實體。
進程的兩個基本元素是進程代碼 和與這些代碼關聯的數據。
進程是一種動態描述 ,不是所有的進程都在運行。(進程在內存中因調度策略和調度要求,會處於各種不同狀態)
進程描述
一個進程的所有屬性描述信息被放在叫進程控制塊(PCB)的結構體中
Linux上叫task_struct。
Linux上task_struct 存放着它所對應的進程的所有屬性信息。
task_struck 和進程一一對應 ,一旦磁盤上的可執行程序被執行,它的部分或全部代碼被加載如內存 並在內存中生成一個用於描述進程屬性的task_struct。
task_struck所包含比較重要的內容:
1. 進程標識符:爲了區別其他進程,描述本進程的唯一標識符。
2. 狀態:任務狀態、退出代碼、退出信號等。
3. 優先級:當前進程相對於其他進程得到系統資源的先後順序級別(主要指CPU資源)
4. 程序計數器 —進程中即將被執行的下一條指令(代碼)的地址(相對於CPU中的PC指針)
5. 內存指針:通過task_struct要找到與其對應的進程的代碼和數據就要有內存指針,內存指針也指向與其他進程共享的內存塊。
6. 上下文數據:進程執行時CPU寄存器中的數據。
7. I/O狀態信息:顯示的I/O請求、分配給進程的I/O設備和被進程使用的文件列表。
8. 記賬信息: 處理器處理時間總和、使用的時鐘數總和、記賬號等等。。。。。
~~~~解釋記賬信息作用:
從記賬信息可以看出某個進程I/O操作多 就讓他高優先級 讓CPU先處理它 好讓其他進程接下來執行少被I/O干擾。
如果某個進程計算操作多 就降低它的優先級 確保它不長期佔用CPU。
圖解PCB(task_struct)
圖解進程上下文信息:
task_struct結構體的全部信息:
http://blog.csdn.net/x__016meliorem/article/details/70060305
查看進程狀態
可以通過 ps -aux | grep (可執行程序)
ps -axj | grep (可執行程序)
這兩條命令 ps -aux 或 axj 是顯示進程狀態 | 是管道 grep是行過濾工具 後加可執行程序名就可以列出關心的進程狀態了。
還有一種方法可以用來查看進程 ———/proc 系統文件夾
該文件夾下的文件夾以進程pid來命名 比如要查1號進程的信息 進入 /proc/1文件夾下查看。
獲取進程ID函數
getpid();//獲取當前進程ID
getppid();//獲取當前進程父進程ID
兩個函數的本質是 在PCB(task_struct)中獲取 pid 和ppid這兩個成員變量。
子進程的創建
fork();
該函數的特點是有兩個返回值 一般要用if else 語句分開
返回值大於0時執行的是父進程代碼,
返回值等於零是執行的是子進程代碼,
父進程中看到的fork()返回的是所創建的自進程的pid(標識這是創建的哪個子進程),
子進程中fork()返回0
如果返回值小於0 說明fork()失敗。
fork 失敗的原因: 1.內存不足 2.進程的最大數量有限制 3.操作系統正在重啓
#include <stdio.h>
#include<unistd.h>
int g_val = 100;
int main()
{
int ret = fork();
if(ret > 0){
sleep(2);
printf("father %d,%p, g_val=%d\n",getpid(),&g_val,g_val);
}else if(ret == 0){
g_val = 200;
sleep(1);
printf("child %d, %p,g_val=%d\n",getpid(),&g_val,g_val);
}else{
perror("fork");
}
return 0;
}
後面還要談fork()
進程狀態
Linux內核源碼爲進程定義瞭如下幾種狀態。
R運行狀態: 進程不是正在運行就是在運行隊列中。
S睡眠狀態: 進程正在等待某件事的完成 這裏的睡眠是可中斷的。
D磁盤休眠狀態: 不可中斷的睡眠狀態 該狀態的進程應該正等待I/O的結束。
T狀態: 停止狀態 我們可以發送SIGSTOP信號終止進程 也可以發送SIGCONT讓進程繼續。
kill -SIGSTOP (PID)
kill -SIGCONT (PID)
Z 狀態: 殭屍狀態 形成殭屍進程的原因是 子進程退出時父進程還在運行且並未回收其退出代碼。 這是子進程以終止狀態保持在內核進程表中,並且會一直等待父進程回收其狀態。
——殭屍進程的危害:
進程的退出狀態只要不被回收就必須被維持下去,它要告訴它的父進程自己的任務完成的如何 。
進程退出狀態也屬於進程的基本信息 在PCB中用數據保存着 PCB就要一直被維護 如果系統中有大量子進程被創建 這些結構體都要在內存中佔空間長時間佔用大量資源。
殭屍進程代碼:
#include<stdio.h>
#include<unistd.h>
int main()
{
int ret = fork();
if(ret > 0){//ret = child pid
//father
printf("I am father : %d\n",getpid());
while(1){sleep(1);}
//子進程已經退出 父進程沒有回收子進程狀態且一直沒有退出。
}else if(ret == 0){//
//child
printf("I am child :%d\n",getpid());
}else if(ret < 0){//fork 失敗的原因 1.內存不足 2.進程的最大數量有限制 3.操作系統正在重啓
perror("fork");//向標準輸出打印失敗信息
}
return 0;
}
孤兒進程
如果 父進程先退出 子進程沒有退出
等到子進程退出時父進程已經無法回收子進程推出狀態 這時子進程退出狀態可以讓 1 號進程(init進程)回收 init進程是操作系統啓動時創建的進程 其它進程皆由它創建。
這時的子進程叫孤兒進程。
#include <stdio.h>
#include<unistd.h>
int main()
{
int ret = fork();
if(ret > 0){
//father
printf("father %d\n",getpid());
// sleep(3);
//父進程已經退出
}else if(ret == 0){
//child
printf("child: %d. %d, \n",getpid(), getppid());
sleep(10);
//子進程不退出
}
}else{
perror("fork");
}
return 0;
}
可以通過 kill -9 (ppid)命令 將父進程殺死的方式使殭屍進程被init進程回收 這是孤兒進程的父進程id會被標識爲1.
但是這種方法很不好 實際上很多時候父進程不可以隨意被殺死。
程序地址空間
地址空間圖:
—–代碼段可不可以修改?
可以 比如調試器加斷點 遊戲外掛。
圖中由下往上是從低地址到高地址
下面一段測試代碼:
#include <stdio.h>
#include<unistd.h>
int g_val = 100;
int main()
{
int ret = fork();
if(ret > 0){
sleep(2);
printf("father %d,%p, g_val=%d\n",getpid(),&g_val,g_val);
}else if(ret == 0){
g_val = 200;
sleep(1);
printf("child %d, %p,g_val=%d\n",getpid(),&g_val,g_val);
}else{
perror("fork");
}
return 0;
}
這段代碼輸出的父子進程g_val的地址是一樣的
但父進程g_val沒變是100 子進程g_val卻是200.
爲什麼 代碼輸出的%p 是地址空間的地址 不是物理內存。
實際上父子進程的g_val 在真正的物理內存中是兩份。
OS負責將虛擬地址轉化成物理地址 用戶是看不到物理地址的
虛擬地址與物理地址
圖解:
爲什麼要有虛擬地址空間?
1.如果直接使用物理地址 一個進程有BUG 內存越界會影響另一個進程。
2.需要在進行時把全部內存地址空間加載到內存中佔用空間大
3. 內存間切換不方便。
現在有頁表 不同的進程使用不同的頁表 進程的地址空間通過頁表映射到物理內存上 各個進程通過頁表可以將需要的一部分的地址空間加載人物理地址 這樣可以加快內存切換效率
各個進程在自己的地址空間內對應的物理內存中運行 不影響其他進程
CPU中的段寄存器 使進程的物理地址隔離起來 容易找到內存越界問題。