怎么看別人網(wǎng)站怎么做的優(yōu)化經(jīng)典軟文案例100例簡短
1、馮諾依曼體系結構
我們常見的計算機,如筆記本。我們不常見的計算機,如服務器,大部分都遵守馮諾依曼體系;
截至目前,我們所認識的計算機,都是有一個個的硬件組件組成:
- 輸入單元:包括鍵盤,鼠標,掃描儀,寫板等;
- 中央處理器(CPU):含有運算器和控制器等;
- 輸出單元:顯示器,打印機等;
關于馮諾依曼強調幾點:
- 這里的存儲器指的是內(nèi)存;
- 不考慮緩存情況,這里的CPU能且只能對內(nèi)存進行讀寫,不能訪問外設(輸入或輸出設備);
- 外設(輸入或輸出設備)要輸入或者輸出數(shù)據(jù),也只能寫入內(nèi)存或者從內(nèi)存中讀取;
- 一句話,所有設備都只能直接和內(nèi)存打交道;
2、操作系統(tǒng)(Operator System)
2.1、概念
任何計算機系統(tǒng)都包含一個基本的程序集合,稱為操作系統(tǒng)(OS)?;\統(tǒng)的理解,操作系統(tǒng)包括:
- 內(nèi)核(進程管理,內(nèi)存管理,文件管理,驅動管理)
- 其他程序(例如函數(shù)庫,shell程序等等)
2.2、設計OS的目的
- 對下:與硬件交互,管理所有的軟硬件資源
- 對上:為用戶程序(應用程序)提供一個良好的執(zhí)行環(huán)境
2.3、定位
- 在整個計算機軟硬件架構中,操作系統(tǒng)的定位是:一款純正的“搞管理”的軟件
2.4、如何理解 “ 管理 ”
- 描述被管理對象
- 組織被管理對象
2.5、總結
計算機管理硬件:
- 描述起來,用struct結構體
- 組織起來,用鏈表或其他高效的數(shù)據(jù)結構
3、系統(tǒng)調用和庫函數(shù)概念
- 在開發(fā)角度,操作系統(tǒng)對外會表現(xiàn)為一個整體,但是會暴露自己的部分接口,供上層開發(fā)使用,這部分由操作系統(tǒng)提供的接口,叫做系統(tǒng)調用;
- 系統(tǒng)調用在使用上,功能比較基礎,對用戶的要求相對也比較高,所以,有心的開發(fā)者可以對部分系統(tǒng)調用進行適度封裝,從而形成庫,有了庫,就很有利于更上層用戶或者開發(fā)者進行二次開發(fā);
4、進程
4.1、基本概念
- 課本概念:程序的一個執(zhí)行實例,正在執(zhí)行的程序等;
- 內(nèi)核觀點:擔當分配系統(tǒng)資源(CPU時間,內(nèi)存)的實體;
4.2、描述進程-PCB
- 進程信息被放在一個叫做進程控制塊的數(shù)據(jù)結構中,可以理解為進程屬性的集合;
- 課本上稱之為PCB(process control block),Linux操作系統(tǒng)下的PCB是: task_struct;
task_struct-PCB的一種
- 在Linux中描述進程的結構體叫做task_struct;
- task_struct是Linux內(nèi)核的一種數(shù)據(jù)結構,它會被裝載到RAM(內(nèi)存)里并且包含著進程的信息;
task_ struct內(nèi)容分類
- 標示符: 描述本進程的唯一標示符,用來區(qū)別其他進程;
- 狀態(tài): 任務狀態(tài),退出代碼,退出信號等;
- 優(yōu)先級: 相對于其他進程的優(yōu)先級;
- 程序計數(shù)器: 程序中即將被執(zhí)行的下一條指令的地址;
- 內(nèi)存指針: 包括程序代碼和進程相關數(shù)據(jù)的指針,還有和其他進程共享的內(nèi)存塊的指針;
- 上下文數(shù)據(jù): 進程執(zhí)行時處理器的寄存器中的數(shù)據(jù);
- I/O狀態(tài)信息: 包括顯示的I/O請求,分配給進程的I/O設備和被進程使用的文件列表;
- 記賬信息: 可能包括處理器時間總和,使用的時鐘數(shù)總和,時間限制,記賬號等;
- 其他信息
?
4.3、組織進程
可以在內(nèi)核源代碼里找到它。所有運行在系統(tǒng)里的進程都以task_struct鏈表的形式存在內(nèi)核里;
4.4、查看進程
進程的信息可以通過 /proc 系統(tǒng)文件夾 查看
- 如:要獲取PID為1的進程信息,你需要查看 /proc/1 這個文件夾;
- ??大多數(shù)進程信息同樣可以使用 top 和 ps 這些用戶級工具來獲取;
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{while(1){sleep(1);}return 0;
}
?4.5、通過系統(tǒng)調用獲取進程標示符
- 進程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;
}
4.6、通過系統(tǒng)調用創(chuàng)建進程-fork初識
- 運行 man fork 認識fork
- fork有兩個返回值
- 父子進程代碼共享,數(shù)據(jù)各自開辟空間,私有一份(采用寫時拷貝)
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{int ret = fork();printf("hello proc : %d!, ret: %d\n", getpid(), ret);sleep(1);return 0;
}
- 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){ //childprintf("I am child : %d!, ret: %d\n", getpid(), ret);}else{ //fatherprintf("I am father : %d!, ret: %d\n", getpid(), ret);}sleep(1);return 0;
}
5、進程狀態(tài)
5.1、Linux內(nèi)核源代碼的定義
- 為了弄明白正在運行的進程是什么意思,我們需要知道進程的不同狀態(tài)。一個進程可以有幾個狀態(tài)(在 Linux內(nèi)核里,進程有時候也叫做任務);
- 下面的狀態(tài)在kernel源代碼里定義:
/*
* 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運行狀態(tài)(running): 并不意味著進程一定在運行中,它表明進程要么是在運行中要么在運行隊列里;
- S睡眠狀態(tài)(sleeping): 意味著進程在等待事件完成(這里的睡眠有時候也叫做可中斷睡眠 (interruptible sleep));
- D磁盤休眠狀態(tài)(Disk sleep)有時候也叫不可中斷睡眠狀態(tài)(uninterruptible sleep),在這個狀態(tài)的進程通常會等待IO的結束;
- T停止狀態(tài)(stopped): 可以通過發(fā)送 SIGSTOP 信號給進程來停止(T)進程,這個被暫停的進程可以通過發(fā)送 SIGCONT 信號讓進程繼續(xù)運行;
- X死亡狀態(tài)(dead):這個狀態(tài)只是一個返回狀態(tài),你不會在任務列表里看到這個狀態(tài);
?
?
5.2、進程狀態(tài)查看
ps aux / ps axj 命令
5.3、Z(zombie)-僵尸進程
- 僵死狀態(tài)(Zombies)是一個比較特殊的狀態(tài)。當進程退出并且父進程沒有讀取到子進程退出的返回代碼時就會產(chǎn)生僵死(尸)進程;
- 僵死進程會以終止狀態(tài)保持在進程表中,并且會一直在等待父進程讀取退出狀態(tài)代碼;
- 所以,只要子進程退出,父進程還在運行,但父進程沒有讀取子進程狀態(tài),子進程進入Z狀態(tài);
來一個創(chuàng)建維持30秒的僵死進程例子:
#include <stdio.h>
#include <stdlib.h>
int main()
{pid_t id = fork();if(id < 0){perror("fork");return 1;}else if(id > 0){ //parentprintf("parent[%d] is sleeping...\n", getpid());sleep(30);}else{printf("child[%d] is begin Z...\n", getpid());sleep(5);exit(EXIT_SUCCESS);}return 0;
}
ptrace系統(tǒng)調用追蹤進程運行
5.4、僵尸進程危害
- 進程的退出狀態(tài)必須被維持下去,因為他要告訴關心它的進程(父進程),你交給我的任務,我辦的怎么樣了??筛高M程如果一直不讀取,那子進程就一直處于Z狀態(tài);
- 維護退出狀態(tài)本身就是要用數(shù)據(jù)維護,也屬于進程基本信息,所以保存在task_struct(PCB)中,換句話說,Z狀態(tài)一直不退出,PCB一直都要維護;
- 那一個父進程創(chuàng)建了很多子進程,就是不回收,就會造成內(nèi)存資源的浪費!因為數(shù)據(jù)結構對象本身就要占用內(nèi)存,想想C中定義一個結構體變量(對象),是要在內(nèi)存的某個位置進行開辟空間;
- 內(nèi)存泄漏
6、孤兒進程
- 父進程如果提前退出,那么子進程后退出,進入Z之后,那該如何處理呢?
- 父進程先退出,子進程就稱之為“孤兒進程”’
- 孤兒進程被1號init進程領養(yǎng),當然要有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){//childprintf("I am child, pid : %d\n", getpid());sleep(10);}else{//parentprintf("I am parent, pid: %d\n", getpid());sleep(3);exit(0);}return 0;
}
7、進程優(yōu)先級
7.1、基本概念
- cpu資源分配的先后順序,就是指進程的優(yōu)先權(priority);
- 優(yōu)先權高的進程有優(yōu)先執(zhí)行權利。配置進程優(yōu)先權對多任務環(huán)境的linux很有用,可以改善系統(tǒng)性能;
- 還可以把進程運行到指定的CPU上,這樣一來,把不重要的進程安排到某個CPU,可以大大改善系統(tǒng)整體性能;
7.2、查看系統(tǒng)進程
在linux或者unix系統(tǒng)中,用ps –l命令則會類似輸出以下幾個內(nèi)容:
?容易注意到其中的幾個重要信息,有下:
- UID : 代表執(zhí)行者的身份
- PID : 代表這個進程的代號
- PPID :代表這個進程是由哪個進程發(fā)展衍生而來的,亦即父進程的代號
- PRI :代表這個進程可被執(zhí)行的優(yōu)先級,其值越小越早被執(zhí)行
- NI :代表這個進程的nice值
7.3、PRI and NI
- PRI也還是比較好理解的,即進程的優(yōu)先級,或者通俗點說就是程序被CPU執(zhí)行的先后順序,此值越小,進程的優(yōu)先級別越高;
- NI就是所要說的nice值了,其表示進程可被執(zhí)行的優(yōu)先級的修正數(shù)值;
- PRI值越小越快被執(zhí)行,那么加入nice值后,將會使得PRI變?yōu)?#xff1a;PRI(new)=PRI(old)+nice;
- 這樣,當nice值為負值的時候,那么該程序將會優(yōu)先級值將變小,即其優(yōu)先級會變高,則其越快被執(zhí)行;
- 所以,調整進程優(yōu)先級,在Linux下,就是調整進程nice值;
- nice其取值范圍是-20至19,一共40個級別;
7.4、PRI vs NI
- 需要強調一點的是,進程的nice值不是進程的優(yōu)先級,他們不是一個概念,但是進程nice值會影響到進程的優(yōu)先級變化;
- 可以理解nice值是進程優(yōu)先級的修正修正數(shù)據(jù);
7.5、查看進程優(yōu)先級的命令
用top命令更改已存在進程的nice:
- top;
- 進入top后按“r”–>輸入進程PID–>輸入nice值;
8、其他概念
- 競爭性: 系統(tǒng)進程數(shù)目眾多,而CPU資源只有少量,甚至1個,所以進程之間是具有競爭屬性的。為了高效完成任務,更合理競爭相關資源,便具有了優(yōu)先級;
- 獨立性: 多進程運行,需要獨享各種資源,多進程運行期間互不干擾;
- 并行: 多個進程在多個CPU下分別,同時進行運行,這稱之為并行;
- 并發(fā): 多個進程在一個CPU下采用進程切換的方式,在一段時間之內(nèi),讓多個進程都得以推進,稱之為并發(fā)
?
9、環(huán)境變量
9.1、基本概念
- 環(huán)境變量(environment variables)一般是指在操作系統(tǒng)中用來指定操作系統(tǒng)運行環(huán)境的一些參數(shù)
- 如:我們在編寫C/C++代碼的時候,在鏈接的時候,從來不知道我們的所鏈接的動態(tài)靜態(tài)庫在哪里,但是照樣可以鏈接成功,生成可執(zhí)行程序,原因就是有相關環(huán)境變量幫助編譯器進行查找;
- 環(huán)境變量通常具有某些特殊用途,還有在系統(tǒng)當中通常具有全局特性;
9.2、常見環(huán)境變量
- PATH : 指定命令的搜索路徑
- HOME : 指定用戶的主工作目錄(即用戶登陸到Linux系統(tǒng)中時,默認的目錄)
- SHELL : 當前Shell,它的值通常是/bin/bash;
9.3、測試PATH
?1.?創(chuàng)建hello.c文件;
#include <stdio.h>
int main()
{printf("hello world!\n");return 0;
}
?2.?對比./hello執(zhí)行和之間hello執(zhí)行;
?3.?為什么有些指令可以直接執(zhí)行,不需要帶路徑,而我們的二進制程序需要帶路徑才能執(zhí)行;
?4.?將我們的程序所在路徑加入環(huán)境變量PATH當中, export PATH=$PATH:hello程序所在路徑
?5.?對比測試
9.4、測試HOME
?1.?用root和普通用戶,分別執(zhí)行 echo $HOME ,對比差異 . 執(zhí)行 cd ~; pwd ,對應 ~ 和 HOME 的關系
9.5、和環(huán)境變量相關的命令
- echo: 顯示某個環(huán)境變量值
- export: 設置一個新的環(huán)境變量
- env: 顯示所有環(huán)境變量
- unset: 清除環(huán)境變量
- set: 顯示本地定義的shell變量和環(huán)境變量
9.6、環(huán)境變量的組織方式
每個程序都會收到一張環(huán)境表,環(huán)境表是一個字符指針數(shù)組,每個指針指向一個以’\0’結尾的環(huán)境字符串
9.7、通過代碼如何獲取環(huán)境變量
- 命令行第三個參數(shù)
#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;
}
- 通過第三方變量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指向環(huán)境變量表,environ沒有包含在任何頭文件中,所以在使用時要用extern聲明;
9.8、通過系統(tǒng)調用獲取或設置環(huán)境變量
- putenv
- getenv
#include <stdio.h>
#include <stdlib.h>
int main()
{printf("%s\n", getenv("PATH"));return 0;
}
常用getenv和putenv函數(shù)來訪問特定的環(huán)境變量
9.9、環(huán)境變量通常是具有全局屬性的
- 環(huán)境變量通常具有全局屬性,可以被子進程繼承下去
#include <stdio.h>
#include <stdlib.h>
int main()
{char * env = getenv("MYENV");if(env){printf("%s\n", env);}return 0;
}
直接查看,發(fā)現(xiàn)沒有結果,說明該環(huán)境變量根本不存在
- 導出環(huán)境變量?export MYENV="hello world"
- 再次運行程序,發(fā)現(xiàn)結果有了!說明:環(huán)境變量是可以被子進程繼承下去的
實驗
- 如果只進行 MYENV=“helloworld” ,不調用export導出,在用我們的程序查看,會有什么結果;
- 普通變量;
10、程序地址空間
10.1、研究背景
- kernel 2.6.32
- 32位平臺
10.2、程序地址空間回顧
來段代碼感受一下
#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){ //childprintf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);}else{ //parentprintf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);}sleep(1);return 0;
}
輸出
//與環(huán)境相關,觀察現(xiàn)象即可
parent[2996]: 0 : 0x80497a9
child[2997]: 0 : 0x80497a9
我們發(fā)現(xiàn),輸出出來的變量值和地址是一模一樣的,很好理解,因為子進程按照父進程為模版,父子并沒有對變量進行進行任何修改;可是將代碼稍加改動:
#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{ //parentsleep(3);printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);}sleep(1);return 0;
}
輸出結果:
//與環(huán)境相關,觀察現(xiàn)象即可
child[3056]: 100 : 0x80347f4
parent[3055]: 0 : 0x80347f4
我們發(fā)現(xiàn),父子進程,輸出地址是一致的,但是變量內(nèi)容不一樣!能得出如下結論:
- 變量內(nèi)容不一樣,所以父子進程輸出的變量絕對不是同一個變量;
- 但地址值是一樣的,說明,該地址絕對不是物理地址;
- 在Linux地址下,這種地址叫做 虛擬地址
- 在用C/C++語言所看到的地址,全部都是虛擬地址!物理地址,用戶一概看不到,由OS統(tǒng)一管理OS必須負責將 虛擬地址 轉化成 物理地址;
10.3、進程地址空間
所以之前說"程序的地址空間"是不準確的,準確的應該說成 "進程地址空間",那該如何理解?看圖:
說明:
- 上面的圖就足矣說明問題,同一個變量,地址相同,其實是虛擬地址相同,內(nèi)容不同其實是被映射到了不同的物理地址;
?
?
11、Linux2.6內(nèi)核進程調度隊列
上圖是Linux2.6內(nèi)核中進程隊列的數(shù)據(jù)結構
一個CPU擁有一個runqueue
- 如果有多個CPU就要考慮進程個數(shù)的負載均衡問題
優(yōu)先級
- 普通優(yōu)先級:100~139(我們都是普通的優(yōu)先級,想想nice值的取值范圍,可與之對應!)
- 實時優(yōu)先級:0~99(不關心)
活動隊列
- 時間片還沒有結束的所有進程都按照優(yōu)先級放在該隊列
- nr_active: 總共有多少個運行狀態(tài)的進程
- queue[140]: 一個元素就是一個進程隊列,相同優(yōu)先級的進程按照FIFO規(guī)則進行排隊調度,所以,數(shù)組下標就是優(yōu)先級;
- 從該結構中,選擇一個最合適的進程,過程是怎么的?
- 從0下表開始遍歷queue[140];
- 找到第一個非空隊列,該隊列必定為優(yōu)先級最高的隊列;
- 拿到選中隊列的第一個進程,開始運行,調度完成;
- 遍歷queue[140]時間復雜度是常數(shù)!但還是太低效了;
- bitmap[5]:一共140個優(yōu)先級,一共140個進程隊列,為了提高查找非空隊列的效率,就可以用5*32個 比特位表示隊列是否為空,這樣,便可以大大提高查找效率;
過期隊列
- 過期隊列和活動隊列結構一模一樣;
- 過期隊列上放置的進程,都是時間片耗盡的進程;
- 當活動隊列上的進程都被處理完畢之后,對過期隊列的進程進行時間片重新計算;
active指針和expired指針
- active指針永遠指向活動隊列;
- expired指針永遠指向過期隊列;
- 可是活動隊列上的進程會越來越少,過期隊列上的進程會越來越多,因為進程時間片到期時一直都存在的;
- 沒關系,在合適的時候,只要能夠交換active指針和expired指針的內(nèi)容,就相當于有具有了一批新的活動進程;
總結
- 在系統(tǒng)當中查找一個最合適調度的進程的時間復雜度是一個常數(shù),不隨著進程增多而導致時間成本增加,我們稱之為進程調度O(1)算法!
更多文檔