Linux(高階程式設計)1————程序概念
何為程序?
程序的典型定義:
1.程序是程式的一次執行。
2.程序是一個程式及其資料在處理機上順序執行時所發生的活動。
3.程序是具有獨立功能的程式在資料集合上執行的過程,他是系統進行資源分配和排程的一個獨立單位。
程序是程序實體的執行過程,是系統進行資源分配和排程的一個獨立單位。
程序 = 程式段+資料段+PCB(程式控制塊)
那麼程序有什麼特徵呢?
1.程序具有動態性。在這一點我們可以從程序狀態可以看出,程序從產生–>執行->消亡的過程其實就是一個動態的過程。
2.程序具有併發性。是指多個程序同時存在記憶體中,且在一段時間內同時進行(是巨集觀上的,因為一個處理機同一時刻只能處理一個程序)。
3.程序具有獨立性。在傳統的OS中,獨立性是指程序實體是一個獨立執行、獨立獲得資源和獨立接受排程的基本單位。
4.程序具有非同步性。指程序是按非同步方式執行。
上面對程序定義及特點說了一大堆,但我們似乎並沒有意識到程序到底是個什麼東西?那麼我們來看一些東西實實在在感受一下程序面貌。
1.程序描述
在作業系統,程序是用一個結構體描述的(PCB),而在Linux作業系統中我們用的是task_struct,PCB的一種。
接下來我們來看一下task_struct中都有一些什麼東西呢(核心成員)?
-
1.程序描述符(身份標識):pid(程序ID)
a.內部表示符:在所有的作業系統中,都為每一個程序賦予了一個惟一的數字識別符號,它通常是一個程序的序號。設定內部識別符號主要是為了方便系統使用。
b.外部識別符號:它由建立者提供,通常是由字母,數字組成,往往是由使用者(程序)在訪問該程序時使用。為了描述程序的家族關係,還應設定父程序標識和子程序標識。此外,還可設定使用者標識,以指示擁有該程序的使用者。 -
2.一組記憶體指標:程式碼和程式碼依賴的資料(告訴程序對應的程式碼和程式碼依賴的資料)。
-
3.輔助作業系統進行排程的屬性
a.優先順序
b.上下文資訊(CPU中的各種暫存器:通用暫存器、指令暫存器、程式狀態字PSW、使用者棧指標):儲存該程序上次在CPU上執行的現場。
c.記賬資訊(程序切換時)指令執行的數量。
d.程序狀態 -
4.IO相關資訊(檔案描述符表)。
-
5.訊號相關的資訊(後面詳細解釋)
2.程序的組織
程序通過PCB描述後,通過雙向連結串列組織起來。
3.如何建立一個程序?
- fork()函式,用來建立子程序
特點:
1.子程序複製父程序以父程序為模板(寫時拷貝)。
2.把父程序的PCB複製過來並稍加修改(pid/ppid)。
3.記憶體指標基本相同,上下文也相同(EIP)。
4.父子程序執行同一份程式碼。
5.EIP相同,所以fork之後,父子程序都要從fork之後開始執行。
fork返回的資訊:
1.父程序返回子程序ID。
2.子程序返回0。
3.fork執行失敗,返回-1。 - 失敗原因:
1.記憶體不夠。
2.子程序數目達到上限。
fork之後,父子程序執行的先後順序取決於作業系統的排程。
- vfork()函式
vfork是linux前期的進城建立函式,其缺點比較多。
特點:
1.父子程序共享程序地址空間。
2.父子程序執行順序固定,子程序先執行父程序後執行。
4.程序狀態:
1.建立狀態:程序在建立時需要申請一個空白PCB,向其中填寫控制和管理程序的資訊,完成資源分配。如果建立工作無法完成,比如資源無法滿足,就無法被排程執行,把此時程序所處狀態稱為建立狀態
2.就緒狀態:程序已經準備好,已分配到所需資源,只要分配到CPU就能夠立即執行
3.執行狀態:程序處於就緒狀態被排程後,程序進入執行狀態
4.阻塞狀態:正在執行的程序由於某些事件(I/O請求,申請快取區失敗)而暫時無法執行,程序受到阻塞。在滿足請求時進入就緒狀態等待系統呼叫
5.終止狀態:程序結束,或出現錯誤,或被系統終止,進入終止狀態。無法再執行
- 程序狀態轉換:
- Linux核心關於程序狀態的描述:
殭屍程序:
1.成因1:子程序執行完了,父程序沒有做一些特殊處理。
2.危害:PCB沒有釋放(記憶體洩露)。
3.處理:kill掉某個程序,傳送一個特定訊號就能完成程序銷燬。kill -9 pid殺死程序組。
直接殺死殭屍程序是殺不掉的,但可以殺掉(kill 父程序)父程序和殭屍程序都銷燬。
4.成因2:殭屍程序是子程序向父程序彙報工作結果的一種機制。在父程序檢視結果前,這樣的結果不應該被釋放掉,結果仍儲存子程序的PCB中,父程序獲取子程序結果的方式“程序等待” 。
孤兒程序
父程序結束,子程序沒有結束成為孤兒程序。孤兒程序的父程序為1號程序init,1號程序會釋放孤兒程序。
—————————————————————————————————————————
fork使用樣例:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
int main(void)
{
pid_t pid = 0;
int i = 1;
if((pid = fork())<0)
{
printf("fork error! i = %d\n",i);
exit(0);
}
if(pid == 0)
{
printf("child! i = %d\n",i);
}
else
{
i = 2;
printf("father! i = %d\n",i);
}
return 0;
}
執行結果:
通過執行結果我們可以驗證:父子程序有各自的地址空間。接下來再對比一下vfork();
vfork函式樣例:
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
#include<stdio.h>
int main(void)
{
pid_t pid = 0;
int i = 1;
if((pid = vfork())<0)
{
printf("vfork error! i = %d\n",i);
exit(0);
}
if(pid > 0)
{
printf("father! i = %d\n",i);
}
else
{
i = 2;
printf("child! i = %d\n",i);
exit(1);
}
return 0;
}
執行結果:
我們可以清楚的看到:1.子程序先執行,父程序後執行。2.子程序對i進行更改,父程序的值也發生了變化,說明父子程序共享程序地址空間。
建立一個殭屍程序例項:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(void)
{
pid_t pid;
if((pid = fork())<0)
{
printf("fork error!\n");
exit(0);
}
if(pid > 0)
{
while(1);//父程序死迴圈
}
else
{
printf("child ID = %d\n",getpid());//父程序沒有wait子程序,子程序退出。
exit(0);
}
}
執行結果:
可以看到子程序退出,父程序沒有為子程序收屍,導致子程序變為殭屍程序。
kill掉父程序再檢視還有沒有殭屍程序:
我們可以看到父程序殺死後,殭屍程序也隨之釋放。
孤兒程序例項:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(void)
{
pid_t pid;
if((pid = fork())<0)
{
printf("fork error!\n");
exit(0);
}
if(pid > 0)
{
exit(1);//父程序退出
}
else
{
printf("child ID = %d,father ID = %d\n",getpid(),getppid());
while(1);//子程序死迴圈
}
}
執行結果:
子程序在父程序退出後仍在執行,子程序成為孤兒程序,父程序變為1號程序。