Linux 進程概論

1. 瞭解馮諾依曼體系結構

自行了解 不做贅述。

2. 什麼是操作系統 如何瞭解它

操作系統最主要的作用是對一臺計算機軟硬件資源的管理。

操作系統主要進行四個方面的管理:
文件管理, 進程管理, 內存管理, 驅動管理(由內核進行)
操作系統還包括其他程序 比如庫函數 、shell(命令行解釋器)程序。

操作系統的目的:
1.與硬件交互 管理系統的軟硬件資源
2.爲用戶程序提供一個良好的執行環境。

操作系統是一款純正的”搞管理“的軟件

必須認識到一個普遍真理即——-描述是管理的前提
要有效管理一個被管理的對象必不可少的要描述其性質。
(例如學校的教務管理系統中有每位同學的學生信息)
有了其性質的描述以後再對其進行組織。

如何描述 ???——–通過struct描述複雜的性質。
如何組織???———通過鏈表 樹 等數據結構進行有效組織。

計算機的層次性

這裏寫圖片描述

理解區分庫函數與系統調用

  1. 在開發角度,爲了保護操作系統安全。操作系統對外表現爲一個被包裹起來的整體,以向外暴露一個個接口的方式提供功能。這些接口叫系統調用。
  2. 系統調用功能比較基礎, 對使用它的用戶對操作系統知識的要求較高,鑑於此 開發者們開發了庫函數對系統調用進行封裝 。有了庫,就更有利於更上層的用戶進行開發。

????爲什麼有了系統調用還要有庫函數
1. 庫函數擴展了系統調用的功能。
2. 庫函數提供了不同操作系統平臺下的可移植性保障。
3. 庫函數的使用更方便。

初探進程

  1. 進程是程序執行的實例、是正在執行的程序。
  2. 從內核的角度來看 是擔當分配系統資源(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中的段寄存器 使進程的物理地址隔離起來 容易找到內存越界問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章