http://blog.chinaunix.net/uid-25324849-id-3110075.htmlphp
部分轉自:http://blog.chinaunix.net/uid-20620288-id-3025213.htmlhtml
1、首先要明確進程和線程的含義:java
進程(Process)是具備必定獨立功能的程序關於某個數據集合上的一次運行活動,是系統進行資源分配和調度的一個獨立單位。與程序相比,程序只是一組指令的有序集合,它自己沒有任何運行的含義,只是一個靜態實體。進程是程序在某個數據集上的執行,是一個動態實體。它因建立而產生,因調度而運行,因等待資源或事件而被處於等待狀態,因完成任務而被撤消,反映了一個程序在必定的數據集上運行的所有動態過程。linux
每一個正在系統上運行的程序都是一個進程。每一個進程包含一到多個線程。進程也多是整個程序或者是部分程序的動態執行。線程是一組指令的集合,或者是程序的特殊段,它能夠在程序裏獨立執行。也能夠把它理解爲代碼運行的上下文。因此線程基本上是輕量級的進程,它負責在單個程序裏執行多任務。一般由操做系統負責多個線程的調度和執行。算法
多線程是爲了同步完成多項任務,不是爲了提升運行效率,而是爲了提升資源使用效率來提升系統的效率。線程是在同一時間須要完成多項任務的時候實現的。編程
使用線程的好處有如下幾點:數組
a)使用線程能夠把佔據長時間的程序中的任務放到後臺去處理服務器
b)用戶界面能夠更加吸引人,這樣好比用戶點擊了一個按鈕去觸發某些事件的處理,能夠彈出一個進度條來顯示處理的進度網絡
c)程序的運行速度可能加快數據結構
d)在一些等待的任務實現上如用戶輸入、文件讀寫和網絡收發數據等,線程就比較有用了。在這種狀況下咱們能夠釋放一些珍貴的資源如內存佔用等等。
2、其次來看下線程和進程的關係
線程是屬於進程的,線程運行在進程空間內,同一進程所產生的線程共享同一內存空間,當進程退出時該進程所產生的線程都會被強制退出並清除。線程可與屬於同一進程的其它線程共享進程所擁有的所有資源,可是其自己基本上不擁有系統資源,只擁有一點在運行中必不可少的信息(如程序計數器、一組寄存器和棧)。
3、而後咱們來看下線程和進程間的比較
子進程繼承父進程的屬性: |
子線程繼承主線程的屬性: |
實際用戶ID,實際組ID,有效用戶ID,有效組ID; 附加組ID; 進程組ID; 會話ID; 控制終端; 設置用戶ID標誌和設置組ID標誌; 當前工做目錄; 根目錄; 文件模式建立屏蔽字(umask); 信號屏蔽和安排; 針對任一打開文件描述符的在執行時關閉(close-on-exec)標誌; 環境; 鏈接的共享存儲段; 存儲映射; 資源限制; |
進程中的全部信息對該進程的全部線程都是共享的; 可執行的程序文本; 程序的全局內存; 堆內存; 棧; 文件描述符; 信號的處理是進程中全部線程共享的(注意:若是信號的默認處理是終止該進程那麼便是把信號傳給某個線程也同樣會將進程殺掉);
|
父子進程之間的區別: |
子線程特有的: |
fork的返回值(=0子進程); 進程ID不一樣; 兩個進程具備不一樣的父進程ID; 子進程的tms_utime,tms_stime,tms_cutime以及tms_ustime均被設置爲0; 不繼承父進程設置的文件鎖; 子進程的未處理鬧鐘被清除; 子進程的未處理信號集設置爲空集; |
線程ID; 一組寄存器值; 棧; 調度優先級和策略; 信號屏蔽字; errno變量; 線程私有數據; |
3、設計時考慮的使用技巧
1.儘可能避免長駐內存的進程,例如那些不多用到的功能,或週期性很長(10分鐘以上),把它們的功能提取出來,作成一個小的應用程序。須要的時候再把它們拉起來(如經過crontab配置,或直接system)。
2.把目標設計成子功能系統的組合可用提升重用的易用性和維護性。
把目標根據功能劃分不一樣的子系統,子系統間遵循特定的協議(文本或XML),由通信聯繫起來,協做完成目標。
也就是說,咱們在作設計的時候能夠以下考慮:
一、線程的建立以及線程間的通訊和同步都比進程要快。在多核CPU上的任務分割是對線程而言的,不是進程。
二、若是不須要頻繁的建立和銷燬 執行的效率是並很少的,須要頻繁建立的話,線程快。
三、其它的就根據你的實際狀況選擇了, 要是沒有數據通訊什麼的,線程間的通訊比進程間方便。最關鍵的一點,多線程可讓同一個程序的不一樣部分併發執行。
因此在作安防系統的時候,報警系統和監控系統之間能夠用多進程來作,對於報警系統中能夠用多線程來實現若是發生意外,能夠向用戶發送消息,同時鳴笛,以及若是是火警的話,能夠打開閥門等。
進程間通訊
unix系統主要進程間通訊機制(IPC)
管道
FIFO(命名管道)
消息隊列
共享內存
信號量
套接字
3. 管道
詳細請見:
http://blog.chinaunix.net/space.php?uid=25324849&do=blog&id=207407
管道是最多見的IPC機制,是單工的,若是要兩個進程實現雙向傳輸則須要兩個管道,管道建立的時候既有兩端,一個讀端和一個寫端。兩個進程要協調好,一個進程從讀的方向讀,一個進程從寫的方向寫,而且只能在關係進程間進行,好比父子進程,經過系統調用pipe()函數實現。
#include
int pipe(int fd[2]);
fd[0]:文件描述符,用於讀操做
fd[1]:文件描述符,用於寫操做
返回值:成功返回0,若是建立失敗將返回-1並記錄錯誤碼
4. FIFO
詳細請見:
http://blog.chinaunix.net/space.php?uid=25324849&do=blog&id=207413
FIFO又稱命名管道,經過FIFO的通訊能夠發生在任何兩個進程之間,且只須要對FIFO有適當的訪問權限,對FIFO的讀寫操做與普通文件相似,命名管道的建立是經過mkfifo()函數建立的。
#include
int mkfifo(const char *filename, mode_t mode)
filename:命名管道的文件名
mode:訪問權限
返回值:若成功則返回0,不然返回-1,錯誤緣由存於errno中。
4.1 FIFO服務器實例
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SERVER_FIFO_NAME "./serv_fifo"
#define CLIENT_FIFO_NAME "./cli_%d_fifo"
#define BUFFER_SIZE 20
struct data_to_pass_st {
pid_t client_pid;
char some_data[BUFFER_SIZE - 1];
};
int main()
{
int server_fifo_fd, client_fifo_fd;
struct data_to_pass_st my_data;
int read_res;
char client_fifo[256];
char *tmp_char_ptr;
mkfifo(SERVER_FIFO_NAME, 0777);
server_fifo_fd = open(SERVER_FIFO_NAME, O_RDONLY);
if (server_fifo_fd == -1) {
fprintf(stderr, "Server fifo failure\n");
exit(EXIT_FAILURE);
}
sleep(10); /* lets clients queue for demo purposes */
do {
read_res = read(server_fifo_fd, &my_data, sizeof(my_data));
if (read_res > 0) {
tmp_char_ptr = my_data.some_data;
while (*tmp_char_ptr) {
*tmp_char_ptr = toupper(*tmp_char_ptr);
tmp_char_ptr++;
}
sprintf(client_fifo, CLIENT_FIFO_NAME, my_data.client_pid);
client_fifo_fd = open(client_fifo, O_WRONLY);
if (client_fifo_fd != -1) {
write(client_fifo_fd, &my_data, sizeof(my_data));
close(client_fifo_fd);
}
}
} while (read_res > 0);
close(server_fifo_fd);
unlink(SERVER_FIFO_NAME);
exit(EXIT_SUCCESS);
}
4.2 FIFO客戶實例
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SERVER_FIFO_NAME "./serv_fifo"
#define CLIENT_FIFO_NAME "./cli_%d_fifo"
#define BUFFER_SIZE 20
struct data_to_pass_st {
pid_t client_pid;
char some_data[BUFFER_SIZE - 1];
};
int main()
{
int server_fifo_fd, client_fifo_fd;
struct data_to_pass_st my_data;
int times_to_send;
char client_fifo[256];
server_fifo_fd = open(SERVER_FIFO_NAME, O_WRONLY);
if (server_fifo_fd == -1) {
fprintf(stderr, "Sorry, no server\n");
exit(EXIT_FAILURE);
}
my_data.client_pid = getpid();
sprintf(client_fifo, CLIENT_FIFO_NAME, my_data.client_pid);
if (mkfifo(client_fifo, 0777) == -1) {
fprintf(stderr, "Sorry, can't make %s\n", client_fifo);
exit(EXIT_FAILURE);
}
for (times_to_send = 0; times_to_send < 5; times_to_send++) {
sprintf(my_data.some_data, "Hello from %d", my_data.client_pid);
printf("%d sent %s, ", my_data.client_pid, my_data.some_data);
write(server_fifo_fd, &my_data, sizeof(my_data));
client_fifo_fd = open(client_fifo, O_RDONLY);
if (client_fifo_fd != -1) {
if (read(client_fifo_fd, &my_data, sizeof(my_data)) > 0) {
printf("received: %s\n", my_data.some_data);
}
close(client_fifo_fd);
}
}
close(server_fifo_fd);
unlink(client_fifo);
exit(EXIT_SUCCESS);
}
5. 消息隊列
詳細請見:
http://blog.chinaunix.net/space.php?uid=25324849&do=blog&id=207459
消息隊列有以下特色:
(1) 經過消息隊列key值來定義和生成消息隊列
(2) 任何進程只要有訪問權限而且知道key就能夠訪問消息隊列
(3) 消息隊列爲內存塊方式數據段
(4) 消息隊列的消息長度可爲系統參數限制內的任何長度
(5) 消息隊列有消息類型,訪問能夠按類型訪問
(6) 在一次讀寫操做前都必須取得消息標識符,即訪問權,訪問後脫離關係
(7) 消息隊列中的某條消息被讀後當即自動的從消息隊列中刪除
(8) 消息隊列具備加鎖處理機制
(9) 在權限容許時,消息隊列的信息能夠雙向傳遞
6. 共享內存
詳細請見:
http://blog.chinaunix.net/space.php?uid=25324849&do=blog&id=207467
共享內存是效率最高的IPC機制,他容許任何兩個進程訪問相同的邏輯內存區,它具備一下特色:
(1) 經過共享內存key值定義和生成共享內存
(2) 任何進程只要有訪問權限而且知道key就能夠訪問共享內存
(3) 共享內存爲內存塊方式數據段
(4) 共享內存的消息長度可爲系統參數限制內的任何長度
(5) 共享內存的訪問方式與數組的訪問方式相同
(6) 在取得共享內存標識符將共享內存與進程數據段鏈接後便可以開始對其進行讀寫操做,在全部操做完成以後再作共享內存與進程數據段的脫離操做,才完成內存訪問的過程
(7) 共享內存中的數據不會由於數據被進程讀取後消失
(8) 共享內存不具有鎖機制,全部共享內存最好與信號量一塊兒使用來保證數據的一致性
(9) 在權限容許時,共享內存的信息傳遞時雙向的
7. 信號量
詳細請見:
http://blog.chinaunix.net/space.php?uid=25324849&do=blog&id=207464
信號量是一種同步機制,主要用途是保護臨界資源(在一個時刻只能被一個進程所擁有),一般與共享內存一塊兒使用。
6.1 semget()函數
#include
int semget(key_t key, int num_sems, int sem_flags)
key:信號量集合的鍵
num_sems:信號量集合裏面元素個數
sem_flags:任選參數
返回值:返回信號量集合標識符,出錯返回-1
6.2 semop()函數
#include
int semop(int sem_id, struct sembuf *sem_ops , size_t num_sem_ops)
sem_id: 信號量集合標識符
sem_ops:信號量操做結構的指針
num_sem_ops:信號量操做結構的個數
6.3 semctl)函數
#include
int semctl (int sem_id, int sem_num, int command, …)
sem_id: 信號量集合標識符
sem_num:信號量元素編號
command:控制命令
…:命令參數列表
返回值:根據命令返回相應的值,出錯返回-1
http://timyang.net/linux/linux-process/
上週碰到部署在真實服務器上某個應用CPU佔用太高的問題,雖然通過tuning, 問題貌似已經解決,但我對tuning的方式只是基於大膽的假設並最終生效了。我更但願更多的求證一下程序背後CPU及OS kernel當時的運做機制。因此我讀了一些Linux內核設計與實現及其餘一些相關資料,對Linux process的機制與切換有了更多一些體會。本文儘量條理一點,但因爲牽涉點較多,同時本身可能以爲某些點有記錄的價值,所以文字可能會零散。
Linux進程的狀態比較容易理解,值得注意的是 UNINTERRUPTIBLE 及 ZOMBIE
TASK_RUNNING
TASK_INTERRUPTIBLE
TASK_UNINTERRUPTIBLE 此時進程不接收信號,這就是爲何有時候kill一個繁忙的進程沒有響應。
TASK_ZOMBIE 咱們常常 kill -9 pid 以後運行ps會發現被kill的進程仍然存在,狀態爲 zombie。zombie的進程實際上已經結束,佔用的資源也已經釋放,僅因爲kernel的相關進程描述符還未釋放。
TASK_STOPPED
Kernel space是供內核,設備驅動運行的內存區域。user space是供普通應用程序運行的區域。每個進程都運行在本身的虛擬內存區域,不能訪問其餘進程的內存空間。普通進程不能訪問kernel space, 只能經過系統調用來間接進行。當系統內存比較緊張時,非當前運行進程user space可能會被swap到磁盤。
使用命令 pmap -x 能夠查看進程的內存佔用信息; lsof -a -p 能夠查看一個進程打開的文件信息。ps -Lf 能夠查看進程的線程數。
另外procfs也是一個分析進程結構的好地方。procfs是一個虛擬的文件系統,它把系統中正在運行的進程都顯如今/proc/目錄下。
進程建立一般調用fork實現。建立後子進程和父進程指向同一內存區域,僅當子進程有write發生時候,纔會把改動的區域copy到子進程新的地址空間,這就是copy-on-write技術,它極大的提升了建立進程的速度。
Linux線程是經過進程來實現。Linux kernel爲進程建立提供一個clone()系統調用,clone的參數包括如 CLONE_VM, CLONE_FILES, CLONE_SIGHAND 等。經過clone()的參數,新建立的進程,也稱爲LWP(Lightweight process)與父進程共享內存空間,文件句柄,信號處理等,從而達到建立線程相同的目的。
Linux 2.6的線程庫叫NPTL(Native POSIX Thread Library)。POSIX thread(pthread)是一個編程規範,經過此規範開發的多線程程序具備良好的跨平臺特性。儘管是基於進程的實現,但新版的NPTL建立線程的效率很是高。一些測試顯示,基於NPTL的內核建立10萬個線程只須要2秒,而沒有NPTL支持的內核則須要長達15分鐘。
在Linux 2.6以前,Linux kernel並無真正的thread支持,一些thread library都是在clone()基礎上的一些基於user space的封裝,所以一般在信號處理、進程調度(每一個進程須要一個額外的調度線程)及多線程之間同步共享資源等方面存在必定問題。爲了解決這些問題,當年IBM曾經開發一套NGPT(Next Generation POSIX Threads), 效率比 LinuxThreads有明顯改進,但因爲NPTL的推出,NGPT也完成了相關的歷史使命並中止了開發。
NPTL的實現是在kernel增長了futex(fast userspace mutex)支持用於處理線程之間的sleep與wake。futex是一種高效的對共享資源互斥訪問的算法。kernel在裏面起仲裁做用,但一般都由進程自行完成。
NPTL是一個1×1的線程模型,即一個線程對於一個操做系統的調度進程,優勢是很是簡單。而其餘一些操做系統好比Solaris則是MxN的,M對應建立的線程數,N對應操做系統能夠運行的實體。(N<m),優勢是線程切換快,但實現稍複雜。< p="">
進程接收信號有兩種:同步和異步。同步信號好比SEGILL(非法訪問), SIGSEGV(segmentation fault)等。發生此類信號以後,系統會當即轉到內核陷阱處理程序,所以同步信號也稱爲陷阱。異步信號如kill, lwp_kill, sigsend等調用產生的都是,異步信號也稱爲中斷。
kill 調用的是 SIGTERM, 此信號能夠被捕獲和忽略。
kill -9 調用的是 SIGKILL, 殺掉進程,不能被捕獲和忽略。
SIGHUP是在終端被斷開時候調用,若是信號沒有被處理,進程會終止。這就是爲何忽然斷網剛經過遠程終端啓動的進程都終止的緣由。防止的方法是在啓動的命令前加上 nohup 命令來忽略 SIGHUP信號。如 nohup ./startup.sh &
不少應用程序一般捕獲SIGHUP用來實現一些自定義特性,好比經過控制檯傳遞信號讓正在運行的程序從新加載配置文件,避免重啓帶來的中止服務的反作用。惋惜的是,在JAVA中無法直接使用這一功能,SUN JVM沒有官方的signal支持,儘管它已經能夠實現,詳情可參看Singals and Java.
另外有個有趣的現象是 zombie 狀態的進程 kill/kill -9 都沒有任何做用,這是因爲進程自己已經不存在,因此沒有相應的進程來處理signal, zombie狀態的進程只是kernel中的進程描述符及相關數據結構沒有釋放,但進程實體已經不存在了。
關於殭屍進程,也可參看下酷殼上的這篇Linux 的殭屍(zombie)進程,從程序的角度解釋了相關原理。