1 操作系統
概念
任何計算機系統都包含一個基本的程序集合,稱爲操作系統(OS)。籠統的理解,操作系統包括:
- 內核(進程管理,內存管理,文件管理,驅動管理)
- 其他程序(例如函數庫,shell程序等等)
設計OS的目的
- 與硬件交互,管理所有的軟硬件資源
- 爲用戶程序(應用程序)提供一個良好的執行環境
定位
在整個計算機軟硬件架構中,操作系統的定位是:一款純正的“搞管理”的軟件
總結
計算機管理硬件:
- 描述起來,用struct結構體
- 組織起來,用鏈表或其他高效的數據結構
系統調用和庫函數概念
在開發角度,操作系統對外會表現爲一個整體,但是會暴露自己的部分接口,供上層開發使用,這部分由操作系統提供的接口,叫做系統調用。
系統調用在使用上,功能比較基礎,對用戶的要求相對也比較高,開發者可以對部分系統調用進行適度封裝,從而形成庫,有了庫,就很有利於更上層用戶或者開發者進行二次開發。
操作系統是怎麼管理進行進程管理的:先把進程描述起來,再把進程組織起來!
2 進程
基本概念
課本概念:程序的一個執行實例,正在執行的程序等
內核觀點:擔當分配系統資源(CPU時間,內存)的實體。
描述進程-PCB
進程信息被放在一個叫做進程控制塊的數據結構中,可以理解爲進程屬性的集合。課本上稱之爲PCB(process control block),Linux操作系統下的PCB是: task_struct。
task_struct-PCB的一種
在Linux中描述進程的結構體叫做task_struct。task_struct是Linux內核的一種數據結構,它會被裝載到RAM(內存)裏並且包含着進程的信息。
task_ struct內容分類
- 標示符: 描述本進程的唯一標示符,用來區別其他進程。
- 狀態: 任務狀態,退出代碼,退出信號等。
- 優先級: 相對於其他進程的優先級。
- 程序計數器: 程序中即將被執行的下一條指令的地址。
- 內存指針: 包括程序代碼和進程相關數據的指針,還有和其他進程共享的內存塊的指針
- 上下文數據: 進程執行時處理器的寄存器中的數據。
- I/O狀態信息: 包括顯示的I/O請求,分配給進程的I/O設備和被進程使用的文件列表。
- 記賬信息: 可能包括處理器時間總和,使用的時鐘數總和,時間限制,記賬號等。
- 其他信息
組織進程
所有運行在系統裏的進程都以task_struct鏈表的形式存在內核裏。
查看進程
- 進程的信息可以通過 /proc 系統文件夾查看(若要獲取PID爲1的進程信息,你需要查看 /proc/1 這個文件夾)
- 大多數進程信息同樣可以使用top和ps這些用戶級工具來獲取
用例:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
while(1){
sleep(1);
}
return 0;
}
通過系統調用獲取進程標示符
- 進程id(PID)
- 父進程id(PPID)
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
printf("pid: %d\n", getpid());
printf("ppid: %d\n", getppid());
return 0;
}
通過系統調用創建進程-fork初識
運行 man fork 認識fork
- fork有兩個返回值
- 父子進程代碼共享,數據各自開闢空間,私有一份(採用寫時拷貝)
- fork 之後通常要用 if 進行分流
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int ret = fork();
if(ret < 0){
perror("fork");
return 1;
}
else if(ret == 0){ //child
printf("I am child : %d!, ret: %d\n", getpid(), ret);
}else{ //father
printf("I am father : %d!, ret: %d\n", getpid(), ret);
}
sleep(1);
return 0;
}
進程狀態
看看Linux內核源代碼怎麼說 :
/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};
- R運行狀態(running): 並不意味着進程一定在運行中,它表明進程要麼是在運行中要麼在運行隊列裏
- S睡眠狀態(sleeping): 意味着進程在等待事件完成(這裏的睡眠有時候也叫做可中斷睡眠)
- D磁盤休眠狀態(Disk sleep)有時候也叫不可中斷睡眠狀態(uninterruptible sleep),在這個狀態的進程通常會等待IO的結束。
- T停止狀態(stopped): 可以通過發送 SIGSTOP 信號給進程來停止(T)進程。這個被暫停的進程可以通過發送 SIGCONT 信號讓進程繼續運行。
- X死亡狀態(dead):這個狀態只是一個返回狀態,你不會在任務列表裏看到這個狀態。
進程狀態查看
ps aux / ps axj 命令
Z(zombie)-殭屍進程
僵死狀態(Zombies)是一個比較特殊的狀態。當進程退出並且父進程(使用wait()系統調用)沒有讀取到子進程退出的返回代碼時就會產生殭屍進程僵死進程會以終止狀態保持在進程表中,並且會一直在等待父進程讀取退出狀態代碼。
所以,只要子進程退出,父進程還在運行,但父進程沒有讀取子進程狀態,子進程進入Z狀態。
#include <stdio.h>
#include <stdlib.h>
int main()
{
pid_t id = fork();
if(id < 0){
perror("fork");
return 1;
}
else if(id > 0){ //parent
printf("parent[%d] is sleeping...\n", getpid());
sleep(30);
}
else{
printf("child[%d] is begin Z...\n", getpid());
sleep(5);
exit(EXIT_SUCCESS);
}
return 0;
}
殭屍進程危害
- 進程的退出狀態必須被維持下去,因爲他要告訴父進程,你如果父進程如果一直不讀取,那子進程就一直處於Z狀態。
- 維護退出狀態本身就是要用數據維護,也屬於進程基本信息,所以保存在task_struct(PCB)中,換句話說,Z狀態一直不退出,PCB一直都要維護。
- 一個父進程創建了很多子進程,就是不回收,就會造成內存資源的浪費。因爲數據結構對象本身就要佔用內存,是要在內存的某個位置進行開闢空
間! - 內存泄漏。
孤兒進程
- 父進程先退出,子進程就稱之爲“孤兒進程”
- 孤兒進程被1號init進程領養,當然要有init進程回收。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t id = fork();
if(id < 0){
perror("fork");
return 1;
}
else if(id == 0){//child
printf("I am child, pid : %d\n", getpid());
sleep(10);
}else{//parent
printf("I am parent, pid: %d\n", getpid());
sleep(3);
exit(0);
}
return 0;
}
環境變量
基本概念
- 環境變量一般是指在操作系統中用來指定操作系統運行環境的一些參數
- 如:我們在編寫C/C++代碼的時候,在鏈接的時候,從來不知道我們的所鏈接的動態靜態庫在哪裏,但是照樣可以鏈接成功,生成可執行程序,原因就是有相關環境變量幫助編譯器進行查找。
- 環境變量通常具有某些特殊用途,還有在系統當中通常具有全局特性
常見環境變量
- PATH : 指定命令的搜索路徑 [重點]
- HOME : 指定用戶的主工作目錄(即用戶登陸到Linux系統中時,默認的目錄)[重點]
- SHELL : 當前Shell,它的值通常是/bin/bash。
查看環境變量方法
echo $NAME //NAME:你的環境變量名稱
測試PATH
- 創建hello.c文件
#include <stdio.h>
int main()
{
printf("hello world!\n");
return 0;
}
- 對比./hello執行和之間hello執行
- 指令可以直接執行,不需要帶路徑,而二進制程序需要帶路徑才能執行。
- 將我們的程序所在路徑加入環境變量PATH當中, export PATH=$PATH:hello程序所在路徑
- 對比測試
測試HOME
- 用root和普通用戶,分別執行 echo $HOME ,對比差異
- 執行 cd ~; pwd ,對應 ~ 和 HOME 的關係
和環境變量相關的命令
- echo: 顯示某個環境變量值
- export: 設置一個新的環境變量
- env: 顯示所有環境變量
- unset: 清除環境變量
- set: 顯示本地定義的shell變量和環境變量
環境變量的組織方式
每個程序都會收到一張環境表,環境表是一個字符指針數組,每個指針指向一個以’\0’結尾的環境字符串 。
通過代碼如何獲取環境變量
- 1.命令行第三個參數
#include <stdio.h>
int main(int argc, char *argv[], char *env[])
{
int i = 0;
for(; env[i]; i++){
printf("%s\n", env[i]);
}
return 0;
}
- 2.通過第三方變量environ獲取
#include <stdio.h>
int main(int argc, char *argv[])
{
extern char **environ;
int i = 0;
for(; environ[i]; i++){
printf("%s\n", environ[i]);
}
return 0;
}
libc中定義的全局變量environ指向環境變量表,environ沒有包含在任何頭文件中,所以在使用時 要用extern聲明。
通過系統調用獲取或設置環境變量
- putenv
- getenv
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("%s\n", getenv("PATH"));
return 0;
}
常用getenv和putenv函數來訪問特定的環境變量。
環境變量通常是具有全局屬性的
環境變量通常具有全局屬性,可以被子進程繼承下去
#include <stdio.h>
#include <stdlib.h>
int main()
{
char * env = getenv("MYENV");
if(env){
printf("%s\n", env);
}
return 0;
}
直接查看,發現沒有結果,說明該環境變量根本不存在
- 導出環境變量 export MYENV=“hello world”
- 再次運行程序,發現結果有了,說明:環境變量是可以被子進程繼承下去的。
程序地址空間
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int g_val = 0;
int main()
{
pid_t id = fork();
if(id < 0){
perror("fork");
return 0;
}
else if(id == 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;
}
輸出結果:
//與環境相關,觀察現象即可
child[3046]: 100 : 0x80497e8
parent[3045]: 0 : 0x80497e8
父子進程,輸出地址是一致的,但是變量內容不一樣!能得出如下結論:
- 變量內容不一樣,所以父子進程輸出的變量絕對不是同一個變量
但地址值是一樣的,說明,該地址絕對不是物理地址! - 在Linux地址下,這種地址叫做 虛擬地址
- 我們在用C/C++語言所看到的地址,全部都是虛擬地址!物理地址,用戶一概看不到,由OS統一管理
OS必須負責將 虛擬地址 轉化成 物理地址 。
進程地址空間
分頁&虛擬地址空間:
同一個變量,地址相同,其實是虛擬地址相同,內容不同其實是被映射到了不同的物理地址!
進程優先級
基本概念
- cpu資源分配的先後順序,就是指進程的優先權。
- 優先權高的進程有優先執行權利。配置進程優先權對多任務環境的linux很有用,可以改善系統性能。
- 還可以把進程運行到指定的CPU上,這樣一來,把不重要的進程安排到某個CPU,可以大大改善系統整體性能。
查看系統進程
在linux或者unix系統中,用ps –l
命令則會類似輸出以下幾個內容:
- UID : 代表執行者的身份
- PID : 代表這個進程的代號
- PPID :代表這個進程是由哪個進程發展衍生而來的,亦即父進程的代號
- PRI :代表這個進程可被執行的優先級,其值越小越早被執行
- NI :代表這個進程的nice值
PRI and NI
-PRI也是進程的優先級,或者通俗點說就是程序被CPU執行的先後順序,此值越小,進程的優先級別越高
- NI就是我們所要說的nice值了,其表示進程可被執行的優先級的修正數值
PRI值越小越快被執行,那麼加入nice值後,將會使得PRI變爲:PRI(new)=PRI(old)+nice - 當nice值爲負值的時候,那麼該程序將會優先級值將變小,即其優先級會變高,則其越快被執行所以,調整進程優先級,在Linux下,就是調整進程nice值,nice其取值範圍是-20至19,一共40個級別。
PRI vs NI
- 進程的nice值不是進程的優先級,他們不是一個概念,但是進程nice值會影響到進程的優先級變化。
- 可以理解爲nice值是進程優先級的修正修正數據。
查看進程優先級的命令
用top命令更改已存在進程的nice:
- top
- 進入top後按“r”–>輸入進程PID–>輸入nice值
其他概念
- 競爭性: 系統進程數目衆多,而CPU資源只有少量,甚至1個,所以進程之間是具有競爭屬性的。爲了高效完成任務,更合理競爭相關資源,便具有了優先級
- 獨立性: 多進程運行,需要獨享各種資源,多進程運行期間互不干擾
- 並行: 多個進程在多個CPU下分別,同時進行運行,這稱之爲並行
- 併發: 多個進程在一個CPU下采用進程切換的方式,在一段時間之內,讓多個進程都得以推進,稱之爲併發