linux-進程概念

1.馮諾依曼體系

體系的構成 :運算器,存儲器(RAM 和 ROM),控制器,輸入設備,輸出設備
思想 :
 1.數據和程序是以二進制代碼的形式放在存儲器中,存放的位置由地址指定,地址碼也是二進制的。
 2.控制器是根據存放在存儲器中的指令序列即程序來工作的,並由一個程序計數器(指令地址計數器)控制指令執行。控制器具有判斷能力,能根據計算結果選擇不同的動作流程。
在這裏插入圖片描述
注意:
 ;a 這裏的存儲器指的是內存
 b 不考慮緩存情況,這裏的CPU能且只能對內存進行讀寫,不能訪問外設(輸入或輸出設備)
 c 外設(輸入或輸出設備)要輸入或者輸出數據,也只能寫入內存或者從內存中讀取。
 d 所有設備都只能直接和內存打交道。

2.操作系統

 操作系統是一個軟件,是管理計算機硬件與軟件資源的計算機程序,同時也是計算機系統 的內核與基石。操作系統需要處理如管理與配置內存、決定系統資源供需的優先次序、控制輸入設備與輸出設備、操作網絡與管理文件系統等基本事務。操作系統也提供一個讓用戶與系統交互的操作界面。
操作系統的構成
 操作系統內核(進程管理,內存管理,文件管理,驅動管理),其他應用(函數庫 ,shell程序等等)
在這裏插入圖片描述
系統調用 : 操作系統提供的函數,稱之爲系統調用函數
庫函數 : 系統在功能調用的使用上,功能比較基礎,對用戶的要求相對比較高,然後一些有心的開發者(大佬們),封裝了系統調用函數,提供了一些封裝後的函數供程序員去使用,被封裝的函數稱之爲庫函數(stdio.h、stdlib.h、string.h等等)。
操作系統管理軟硬件資源的方式:
  管理 = 描述 + 組織
 描述 :struct task_struct 用結構體來進行抽象的描述
 組織 :操作系統是用雙向鏈表來組織進程的PCB
在這裏插入圖片描述

3. 進程模塊

進程和程序的區別
  程序是一個文本文件,是靜態的 ;而進程是程序執行起來的一個實體(要創建一個task_struct結構體)
進程概念
  1.進程就是運行起來的程序,程序運行起來需要被加載到內存中。(這是站在用戶的角度看待進程的)
  2.進程就是操作系統的描述,這個描述叫PCB(process control block)(進程控制塊),Linux下 PCB 有自己的名字叫task_struct.而操作系統就是使用task_struct結構體描述進程,使用雙向鏈表來將這些結構體組織起來進行管理。
在這裏插入圖片描述
描述進程 :
  標識符: 與進程相關的唯一標識符,用來區別正在執行的進程和其他進程。
  狀態: 描述進程的狀態,因爲進程有就緒,阻塞,運行等好幾個狀態,所以都有個標識符來記錄進程的執行狀態。
  優先級: 如果有好幾個進程正在執行,就涉及到進程被執行的先後順序的問題,這和進程優先級這個標識符有關。
  程序計數器: 保存程序執行的下一條指令。
  內存指針: 該指針指向程序地址空間 ( mm_struct )
  上下文信息: 進程執行時處理器的寄存器中的數據。
  I/O狀態信息: 包括顯示的I/O請求,分配給進程的I/O設備和被進程使用的文件列表等。
  記賬信息: 可能包括處理器時間總和,使用的時鐘數總和,時間限制,記賬號等。
 cpu密集程序 :程序大量時間都在使用CPU進行計算(邏輯計算和算術計算)
 IO密集程序 :程序大量時間都在和磁盤打交道,進行IO
進程的執行方式 : 搶佔式執行
並行 : 在同一時間,多個進程,每一個進程都擁有一個CPU進行運算
  併發 : 多個進程,只擁有少量CPU,每個進程只能獨佔CPU一小會,就會讓出CPU供其他進程運算。
 也就是說,當一個CPU暫時空閒的時候,沒有擁有CPU且需要運算的所有進程會同時向該CPU靠攏,進行搶佔式執行,以便擁有CPU進行運算 ,但是他只能用CPU一小會,就要讓出CPU供其他進程完成自己的運算。
組織進程 :
  操作系統是用雙向鏈表來組織進程的PCB,所有運行在系統裏的進程都以 task_struct 鏈表的形式存在內核裏。
通過系統功能調用創建進程
  利用 fork 創建子進程(兩個返回值,代碼共享,數據獨有)
在這裏插入圖片描述

pid_t fork(void);   //pid_t  ==>int 類型
子進程創建失敗 -->  返回值爲 -1
子進程創建成功(返回兩次) -->  返回0  返回給子進程
            返回值大於0 (返回子進程的 pid ),返回給父進程

在這裏插入圖片描述
fork 返回值大於0的情況返回給父進程 的原因 :
  因爲如果一個父進程在創建一個子進程的情況下又創建了一個子進程,並且兩個子進程在返回值都是0的情況下返回給父進程,那麼父進程就無法判斷這是哪一個子進程結束的返回值,所以子進程將自己的 pid 返回給父進程,父進程就可以分辨出返回的子進程是哪一個。
  而對於子進程來說,fork返回給0給子進程,但是子進程的pid 不會是0,之所以fork返回0給子進程,是因爲子進程可以隨時使用getpid() 來獲取父進程的pid,也可以用getppid() 來獲取自己進程的pid ,所以返回值可以是0。
  fork 之後父子進程除非採用同步的手段( vfork() ),負責不能確定父子進程是哪一個進程最先執行,因爲進程執行的方式是搶佔式執行。

#include<stdio.h>
#include<unistd.h>

int main()
{
    //創建子進程
    //系統調用函數 fork()
    pid_t pid=fork();
    if(pid < 0)
   {
      //沒有內存時可能會導致此結果;
      //創建PCB 是需要消耗內存的
      perror("fork\n");
      return 0;
     }
    else if(pid == 0)
    {
    //child
    printf("i am child :[%d] - [%d]\n",getpid(),getppid());
    }
    else //pid > 0
    {
    //father
    printf("i am father :[%d] - [%d]\n",getpid(),getppid());
    }
    //獲取當前進程的pid,使用getpid
    //獲取當前進程的父進程的pid,使用getppid
     return 0;
}

  在fork之後,子進程和父進程是在fork()指令的下一行開始執行的,首先,如果子進程還是從fork() 指令處開始執行,就會陷入無限fork() 中,無法停止;其次,程序在執行時,所依靠的是程序計數器來取指令的,而程序計數器的用處就是來保存下一條指令,所以子進程直接從程序計數器所指的指令處開始執行。
進程狀態
R : 運行狀態(Running) 並不意味着程序一定在運行中,也可能是在指令隊列緩衝器中。

R+ 前臺進程	&&	R 後臺進程
./[可執行程序名稱] &  -->可以將該進程變成後臺進程
fg -->可以把一個進程放到前臺來運行,
        利用棧後進先出的特性,
        彈出最後創建的一個後臺進程來變成前臺進程。

S : 睡眠狀態(Sleeping) 意味着進程在等待事件完成(這裏的睡眠有時候也叫做可中斷睡(interruptible sleep))。
D : 磁盤睡眠狀態(Disk sleep) 有時候也叫不可中斷睡眠狀態(uninterruptible sleep),在這個狀態的進程通常會等待IO的結束。
T : 暫停狀態(stopped) 可以通過發送 SIGSTOP 信號給進程來停止(T)進程。這個被暫停的進程可以通過發送 SIGCONT 信號讓進程繼續運行。
t : 跟蹤狀態(tracing stop) gdb調試時,所調試的程序就會產生一個跟蹤狀態
X : 死亡狀態(dead) 進程終止的一瞬間,PCB裏的進程狀態就會被改成X狀態,然後被釋放掉
Z : 殭屍狀態(Zombie) 進程已經退出,但是PCB沒有釋放的狀態,當子進程退出時,父進程沒有回收子進程的退出狀態,這個時候子進程退出的消息父進程沒有接收,子進程就成爲一個殭屍進程(“子進程此時是死的”),此時使用kill強制刪除命令也無法刪除該進程。
殭屍進程的危害 --> 內存泄漏
  由於子進程變成殭屍狀態,相當於,子進程的PCB在內核中還存在,並且在雙向鏈表中掛着(使用 ps aux 命令還可以看到該進程),也就是說,PCB 的資源還沒有釋放,也就是說內存被泄漏掉了。如果有大量殭屍進程產生,則內存泄漏的越多。
  結束進程

kill -9 [進程pid]  --> 強制刪除

孤兒進程
 孤兒進程不是一種進程狀態,而是一種進程種類的名稱。
產生原因 : 是由於父進程先於子進程退出,導致子進程需要將自己的退出狀態信息返回給一個進程。這是操作系統的1號進程(Init進程),Init進程本身就會創建許多進程,而本身Init進程創建的進程我們不能稱之爲孤兒進程。
解決或防止殭屍進程的方法:
草率的做法 : 如果產生了殭屍狀態的進程,則將殭屍狀態的父進程殺掉,那麼殭屍狀態的進程變成了孤兒進程,這時孤兒進程就會被1號進程將進程的返回狀態信息回收掉,同時在內核當中釋放該孤兒進程的PCB

4.環境變量

  環境變量 一般是指在操作系統中用來指定操作系統運行環境的一些參數,爲了讓操作系統在執行時更加愉快的變量
常見的環境變量

PATH : 查看可執行程序的環境變量
HOME : 保存當前用戶的家目錄的環境變量
SHELL : 保存當前所使用的shell的環境變量
LD_LIBRARY_PATH : 程序運行時,庫文件的搜索路徑的環境變量
LIBRARY_PATH : 程序編譯的時候,庫文件的搜索路徑的環境變量

常見的命令

env : 查看當前操作系統中的環境變量以及環境變量的值
echo $[環境變量的名稱] :查看特定的環境變量的值
set : 查看操作系統中所有的環境變量
    系統下的環境變量
        /etc/bashrc
    當前用戶的環境變量
        ~/.bashrc
        ~/.bash_profile
export : 設置一個環境變量
export [環境變量名稱] = $[環境變量名稱]:[新增的環境變量名稱]
    在文件中修改
        vim ~/.bash_profile
        export [環境變量的名稱] = 環境變量的值
        source [修改過的環境變量文件] :告訴操作系統我們修改過這個文件,
    操作系統重新加載一下就可以在命令中修改。
    在命令行中修改
        臨時修改,只在當前shell中是有效的,在其他的shell當中是沒有效的
     就算重新開一個終端也是沒有效的。 

環境變量的組織方式
 操作系統環境變量的變量的類型

char* env[] --數組中每一個元素都是char*,字符指針數組以NULL來結尾

在這裏插入圖片描述
 使用代碼獲取環境變量(環境變量具有全局屬性,可以被子進程繼承)

//從main函數的參數裏獲取
#include <stdio.h>
int main(int argc, char *argv[], char *env[])
{
    int i = 0;
    for(i=0; i < argc; i++)
    {
        printf("argv[%d] = [%s]\n",i,argv[i]);
    }
    for(i=0; env[i]; i++)
    {
        printf("env[%d] = [%s]\n",i, env[i]);
    }
    return 0;
}

//利用庫函數獲取
#include <stdio.h>
int main(int argc, char *argv[])
{
    extern char **environ;
    int i = 0;
    for(; environ[i]; i++)
    {
        printf("environ[%d] = [%s]\n",i, environ[i]);
    }
    return 0;
}

//通過系統功能調用獲取或者設置環境變量
#include <stdio.h>
#include <stdlib.h>
int main()
{
    char* path = getenv("PATH");
    printf("%s\n", path);
    return 0;
}

5.序地址空間

 在C語言中我們稱之爲程序地址空間,而Linux下稱之爲進程虛擬地址空間。
在這裏插入圖片描述

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
    pid_t pid = fork();
    int g_val = 10;
    if(pid < 0)
    {
        perror("fork");
        return 0;
    }
    else if(pid == 0)
    { 
        //child,子進程肯定先跑完,也就是子進程先修改
        //完成之後,父進程再讀取
        g_val=100;
        printf("child [%d]: [%d] : [%p]\n", getpid(), g_val, &g_val);
    }
    else
    { 
        //parent
        sleep(3);
        printf("parent [%d]: [%d] : [%p]\n", getpid(), g_val, &g_val);
    }
    sleep(1);
    return 0;
}

 運行之後我們發現變量內容不一樣,所以父子進程輸出的變量絕對不是同一個變量,但他們的地址值是一樣的,說明,該地址絕對不是物理地址!在Linux地址下,這種地址叫做虛擬地址,而我們在用C/C++語言所看到的地址,全部都是虛擬地址!物理地址,用戶一概看不到,由OS統一管理,這塊就涉及一個寫時拷貝的概念。

寫時拷貝 : 當數據發生修改的時候,才重新分配一個物理內存,並將頁表當中的映射關係改掉
物理內存: 存儲數據時需要依靠的介質
邏輯地址: 進程虛擬地址空間,也就是人爲規定的,不能存儲數據的.
在這裏插入圖片描述

頁表

 頁表的作用是將虛擬地址映射成物理地址
 頁表的存儲在彙編中就是 段基址 * 16 + 偏移量,段基址是頁內偏移,塊號是偏移量。
分頁式
 前提:會將虛擬地址分成一頁一頁的格式,會將物理內存分成一塊一塊的格式。
 分頁的原因:假設我們有一個16M大小的存儲空間,因爲計算機存儲的時候,地址都是隨機分配的,如果很不湊巧,這段空間的正中間的位置分配一個8M大小的數據,那麼之後要是再存儲一個5M大小的數據就會出現存不下的問題,這就造成了內存的浪費。
在這裏插入圖片描述
 分頁式的存儲就和書差不多,把知識點都分成一頁一頁的,然後每一塊知識點都有一個目錄,可以通過目錄很快的找到這一塊的位置,然後在這一頁中找知識點,唯一有區別的就是,在計算機中,每一塊物理內存的大小都是一樣的。
塊號: 根據頁號在頁表中的映射去查找的塊的標號
 假設虛擬地址爲5000,塊的大小是4096,如何計算物理地址?
頁號 = 虛擬地址 / 塊大小
頁內偏移 = 虛擬地址 % 塊的大小
塊的起始地址 = 塊號 * 大小
物理地址 = 塊的起始地址 + 頁內偏移

頁號 : 5000 / 4096 = 1
頁內偏移 :5000 % 4096 = 904
塊的起始地址 :5 * 4096
物理地址 : 5 * 4096 +904`

在這裏插入圖片描述
分段式
 將虛擬地址映射成物理地址的結構不是頁表了,而是段表。它的存儲是將一個數據段直接放在物理內存中。
先根據段號從段表中找到對應的段的起始地址,再通過 段內偏移 + 段的起始地址 =物理地址。
在這裏插入圖片描述
分段式和分頁式對比
 分頁式數據存儲效率高,分段式效率低。
分段式對程序很友好,可以通過段表的結構,找到虛擬地址空間當中的一段。
段頁式
 存儲時採取了段表結構和頁表結構。
先根據段號來找到段表中頁的起始地址,然後找到對應是那一頁,再根據頁號找到該頁表中的塊號,最後通過塊號算出塊的起始地址,塊的起始地址 + 頁內偏移 = 物理地址。
在這裏插入圖片描述

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