1. 程式人生 > >Linux(高階程式設計)1————程序概念

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號程序。