天天看點

[ Linux ] 程序控制(上)----程序建立與程序終止

文章目錄

  • ​​一、程序建立​​
  • ​​fork函數初識​​
  • ​​fork之後,作業系統做了什麼?​​
  • ​​寫時拷貝​​
  • ​​fork的正常用法​​
  • ​​fork調用失敗的原因​​
  • ​​二、程序終止​​
  • ​​關于終止的正确認識​​
  • ​​關于終止的常見做法​​
  • ​​_exit函數​​
  • ​​關于終止,核心做了什麼?​​

​正文開始!​

一、程序建立

fork函數初識

在Linux中fork函數是非常重要的函數,他從已存在程序中建立一個新程序。新程序稱為子程序,而原程序為父程序。

[ Linux ] 程式控制(上)----程式建立與程式終止
[ Linux ] 程式控制(上)----程式建立與程式終止

傳回值:子程序中傳回0,父程序中傳回子程序的id,出錯傳回-1。

程序調用fork,當控制轉移到核心中的fork代碼後,核心做:

  • 配置設定新的記憶體塊和核心資料結構給子程序
  • 将父程序部分資料結構内容拷貝至子程序
  • 添加子程序到系統程序清單當中
  • fork傳回,開始排程器排程
[ Linux ] 程式控制(上)----程式建立與程式終止

當一個程序調用fork之後,就有兩個二進制代碼相同的程序。而且他們都運作到相同的地方。但每個程序都将可以開始他們自己的執行。

看下面代碼了解:

#include<stdio.h>
  2 #include<unistd.h>
  3 
  4 int main()         
  5 {
  6     printf("我是一個程序,pid:%d\n",getpid());
  7     fork();
  8     printf("我依舊是一個程序:pid:%d\n",getpid());
  9     sleep(1);   
      return 0;
    }      
[ Linux ] 程式控制(上)----程式建立與程式終止

這裡我們看到三行輸出,一行before,兩行after。程序5034先列印before消息,然後他又列印after。另一個after消息由5035列印的。注意到程序5035沒有列印before,為什麼呢?如下圖所示

[ Linux ] 程式控制(上)----程式建立與程式終止

是以,fork之前父程序獨立執行,fork之後,父子兩個執行流分别執行。注意fork之後,誰先執行完全由排程器決定。

那麼fork之後,是否隻有fork之後代碼是被父子程序共享的??

一般情況下,fork之後父子程序共享所有的代碼!!

子程序執行的後續代碼!=共享的所有代碼,隻不過子程序隻能從這裡開始執行!!

那麼這是為什麼呢?

[ Linux ] 程式控制(上)----程式建立與程式終止

因為CPU中有一個eip,會儲存正在執行指令的下一條指令!eip程式計數器會拷貝給子程序,子程序便從該eip所指向的代碼處開始執行!!!

fork之後,作業系統做了什麼?

程序=核心的程序的資料結構+程序的代碼和資料

建立子程序的核心資料結構(struct task_struct+struct mm_struct+頁表)+代碼繼承父程序,資料以寫時拷貝的方式,來進行共享或者獨立!

寫時拷貝

通常情況下,父子代碼共享,父子在不寫入時,資料也是共享的,當任意一方試圖寫入,便以寫時拷貝的方式各自有一份副本。具體如下圖:

[ Linux ] 程式控制(上)----程式建立與程式終止

寫時拷貝本身就是由OS的記憶體管理子產品完成的!

那麼為什麼要寫時拷貝呢?

建立子程序的時候,就把資料分開不行嗎???

1.父程序的資料,子程序不一定全用,即便使用,也不一定全部寫入。—會有浪費空間的嫌疑

2.最理想的情況,隻有會被父子程序修改的資料,進行分離拷貝。不需要修改的共享即可—但是從技術角度實作複雜

3.如果fork的時候,就無腦拷貝資料給子程序,會增加fork的成本(記憶體和時間)

是以最終我們采用寫時拷貝!!!(隻會拷貝父子修改的,變相的就是拷貝資料的最小成本)

寫時拷貝本質上是一個延遲拷貝政策。隻有真正使用的時候,才給你!

就意味着你想要,但是不立馬使用的空間,先不給你,那麼也就意味着可以先給别人!

綜上,就是變相的提高了記憶體的使用率!

fork的正常用法

  • 一個父程序希望複制自己,使得父子程序同時執行不同的代碼段。例如,父程序等待用戶端請求,生成子程序來處理請求。
  • 一個程序要執行一個不同的程式。例如子程序從fork傳回後,調用exec函數。

fork調用失敗的原因

1.系統中有太多的程序了

2.實際使用者的程序數超過了限制

二、程序終止

關于終止的正确認識

我們使用C/C++的時候,main使我們的入口函數,函數的結束我們經常會寫return 0;語句!

a.return 0,給誰return

根據下面的論述我們可以得出return是給父程序的!

b.為何是0?其他值可以嗎?

答案是:程序代碼跑完,結果是否正确

0:表示success

非零:表示失敗

但是當我們失敗的時候,我們最想知道的是失敗的原因是什麼?

是以我們用非零辨別不同的原因!

首先我們先來了解常見的程序退出:

1.代碼跑完,結果正确

2.代碼跑完,結果不正确

3.代碼沒跑完,程式異常了

我們将return X;X稱為程序退出碼!表征程序退出的資訊!得出的資訊是要讓父程序讀取的!

先來看代碼!

1 #include<stdio.h>
  2 #include<unistd.h>
  3 
  4 int main()
  5 {
  6 
  7     return 123;                                                                                            
  8 
  9 }
 10      

我們可以看到程式隻有一行代碼,直接運作完成就退出了!

根據我們之前的學習,我們可以直到指令行的父程序就是bash程序

我們可以用目前指令檢視退出碼

echo $?
[ Linux ] 程式控制(上)----程式建立與程式終止
[ Linux ] 程式控制(上)----程式建立與程式終止

$?代表在bash中,最近依次執行完畢時,對應程序的退出碼!

一般而言,失敗的非零值我該如何設定呢??以及預設表達的含義?

1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<string.h>
  4 
  5 int main()
  6 {
  7     for(int i=0;i<100;i++)
  8     {
  9         printf("%d:%s\n",i,strerror(i));
 10     }
 11   return 0;
 12}      
[ Linux ] 程式控制(上)----程式建立與程式終止
[ Linux ] 程式控制(上)----程式建立與程式終止
[ Linux ] 程式控制(上)----程式建立與程式終止
[ Linux ] 程式控制(上)----程式建立與程式終止
[ Linux ] 程式控制(上)----程式建立與程式終止

錯誤碼退出碼可以對應不同的錯誤原因,友善定位問題!

關于終止的常見做法

1.在main函數中return。為什麼其它函數不行呢?

2.在自己的代碼任意地點中,調用exit()函數!

man 3 exit
[ Linux ] 程式控制(上)----程式建立與程式終止
1 #include<stdio.h>
    2 #include<unistd.h>
    3 #include<string.h>
    4 
    5 void func()
    6 {
    7 
    8     printf("hello func\n");
E>  9     exit(111);
   10 
   11 
   12 }
   13 int main()
   14 {
   15 
   16     func();                                                                                              
   17     return 10;
   18 
   19}      
[ Linux ] 程式控制(上)----程式建立與程式終止

_exit函數

[ Linux ] 程式控制(上)----程式建立與程式終止

exit終止程序會重新整理緩沖區

_exit直接終止程序,不會有任何重新整理操作!

其實exit底層也是在調_exit函數。

[ Linux ] 程式控制(上)----程式建立與程式終止

關于終止,核心做了什麼?

程序= 核心資料結構 + 程序代碼和資料