多進程與多線程

一. 多進程程序的特點 

進程是一個具有獨立功能的程序關於某個數據集合的一次可以併發執行的運行活動,是處於活動狀態的計算機程序。進程作爲構成系統的基本細胞,不僅是系統內部獨立運行的實體,而且是獨立競爭資源的基本實體。

進程是資源管理的最小單位,線程是程序執行的最小單位。進程管理着資源(比如cpu、內存、文件等等),而將線程分配到某個cpu上執行。在操作系統設計上,從進程演化出線程,最主要的目的就是更好的支持多處理器系統和減小上下文切換開銷。

進程的狀態 系統爲了充分的利用資源,對進程區分了不同的狀態.將進程分爲新建,運行,阻塞,就緒和完成五個狀態. 

新建 表示進程正在被創建,

運行 是進程正在運行,

阻塞 是進程正在等待某一個事件發生,

就緒 是表示系統正在等待CPU來執行命令,

完成 表示進程已經結束了系統正在回收資源. 

    由於UNIX系統是分時多用戶系統, CPU按時間片分配給各個用戶使用,而在實質上應該說CPU按時間片分配給各個進程使用每個進程都有自己的運行環境以使得在CPU做進程切換時不會"忘記"該進程已計算了一半的"半成品”. DOS的概念來說進程的切換都 是一次"DOS中斷"處理過程, 包括三個層次: 

1) 用戶數據的保存包括正文段(TEXT), 數據段(DATA,BSS), 棧段(STACK), 共享內存段(SHARED MEMORY)的保存

2) 寄存器數據的保存包括PC(program counter,指向下一條要執行的指令的地址), PSW(processor status word,處理機狀態字), SP(stack pointer,棧指針), PCBP(pointer of process control block,進程控制塊指針), FP(frame pointer,指向棧中一個函數的local變量的首地址)AP(augument pointer,指向棧中函數調用的實參位置)ISP(interrupt stack pointer,中斷棧指針), 以及其他的通用寄存器等

3) 系統層次的保存

包括proc,u,虛擬存儲空間管理表格,中斷處理棧.以便於該進程再一次得到CPU時間片時能正常運行。 既然系統已經處理好所有這些中斷處理的過程我們做程序還有什麼要擔心 的呢我們儘可以使用系統提供的多進程的特點讓幾個程序精誠合作簡單而又高效地把結果給它搞出來。

另外,UNIX系統本身也是用C語言寫的多進程程序,多進程編程是UNIX的特點,當我們熟悉了多進程?將會對UNIX系統機制有一個較深的認識.首先我介紹一下多進程程序的一些突出的特點: 

1.1並行化 

一件複雜的事件是可以分解成若干個簡單事件來解決的這在程序員的大腦中早就形成了這種概念首先將問題分解成一個個小問題將小問題再細分最後在一個合適的規模上做成一個函數在軟件工程中也是這麼說的如果我們以圖的方式來思考一些小問題的計算是可以互不干擾的可以同時處理而在關鍵點則需要統一在一個地方來處理這樣程序的運行就是並行的至少從人的時間觀念上來說是這樣的而每個小問題的計算又是較簡單的

1.2簡單有序 

這樣的程序對程序員來說不亞於管理一班人程序員爲每個進程設計好相應的功能並通過一定的通訊機制將它們有機地結合在一起對每個進程的設計是簡單的只在總控部分小心應付(其實也是蠻簡單的), 就可完成整個程序的施工

1.3.互不干擾 

這個特點是操作系統的特點各個進程是獨立的不會串位

1.4.事務化 

比如在一個數據電話查詢系統中將程序設計成一個進程只處理一次查詢即可即完成一個事務當電話查詢開始時產生這樣一個進程對付這次查詢另一個電話進來時主控程序又產生一個這樣的進程對付每個進程完成查詢任務後消失這樣的編程多簡單只要做一次查詢的程序就可以了

.常用的多進程編程的系統調用 

2.1.fork()     創建一個新的進程.

功能:創建一個新的進程

語法:

#include <unistd.h> 

#include <sys/types.h> 

pid_t fork(); 

說明:本系統調用產生一個新的進程叫子進程是調用進程的一個複製品調用進程叫父進程子進程繼承了父進程的幾乎所有的屬性。

進程:代碼段(程序代碼)

堆棧段(局部變量、函數返回地址、函數參數)

數據段(全局變量、常數等)

在Linux系統中,系統調用fork後,內核爲完成系統調用fork要進行幾步操作:

第一步,爲新進程在進程表中分配一個表項。系統對一個普通用戶可以同時運行的進程數是有限制的,對超級用戶沒有該限制,但不能超過進程表的最大表項的數目。

第二步,給子進程一個唯一的進程標識號(PID)。該進程標識號其實就是該表項在進程表中的索引號。

第三步,複製一個父進程的進程表項的副本給子進程。內核初始化子進程的進程表項時,是從父進程處拷貝的。所以子進程擁有與父進程一樣的uid、當前目錄、當前根、用戶文件描述符表等。

第四步,把與父進程相連的文件表和索引節點表的引用數加1。這些文件自動地與該子進程相連。

第五步,內核爲子進程創建用戶級上下文。內核爲子進程的代碼段分配內存,並複製父進程的區內容,生成的是進程的靜態部分。

第六步,生成進程的動態部分,然後對父進程返回子進程的pid,對子進程返回0。

從父進程拷貝的內容主要有:

●用戶標識符,包括實際用戶號(real)和有效用戶號(effective);

●環境變量

●打開的文件描述符、套接字描述符

●信號處理設置

●堆棧

●目錄

●進程組標誌(process ID)

●會晤組標誌(session ID)

●正文

子進程特有內容:

●進程號

●父進程號

●進程執行時間

●未處理的信號被處理爲空

●不繼承異步的輸入輸出操作

簡述:fork() 調用成功時,分別返回兩個整數,對父進程返回 〉0的整數,對子進程返回 0,

函數執行過程:

① 內核在系統進程表中,創建一個新條目;

② 複製父進程內容(已打開的文件描述符、堆棧、正文等);

③ 修改兩者的堆棧,給父進程返回子進程號,給子進程返回0(父進程知道每個子進程的標誌號,而子進程可根據需要調用getppid() 來獲得父進程的標誌號)。

例子:

pid_t fork(void) 

#include <unistd.h>

pid_t pid;

if((pid=fork())==0)

{

  //子進程代碼

exit(0);

}

else if(pid>0)

{

//父進程代碼

exit(0);

}

else

{

  printf("Error");

  exit(1);

}

 

2.2.system()   子進程執行指定的命令

功能:產生一個新的進程子進程執行指定的命令

語法:

#include <stdio.h> 

#include <stdlib.h> 

int system(string) 

char *string; 

說明:

本調用將參數string傳遞給一個命令解釋器(一般爲sh)執行string被解釋爲一條命令sh執行該命令.若參數string爲一個空指針則爲檢查命令解釋器是否存在.

 該命令可以同命令行命令相同形式但由於命令做爲一個參數放在系統調用中應注意編譯時對特殊意義字符的處理命令的查找是按PATH環境變量的定義的命令所生成的後果一般不會對父進程造成影響

返回值:當參數爲空指針時只有當命令解釋器有效時返回值爲非零若參數不爲空指針返回值爲該命令的返回狀態(waitpid())的返回值命令無效或語法錯誤則返回非零值,所執行的命令被終止其他情況則返回-1. 

例子:char command[81]; 

int i; 

for (i=1;i<8;i++) { 

sprintf(command,"ps t tty%02i",i); 

system(command); 

2.3.exec()     執行一個文件 

功能:執行一個文件

語法

#include <unistd.h>

int execve(const char* path, char* const* argv,char* const* envp);

int execl(const char* path, char* arg,...); 

int execp(const char* file, char* arg,...); 

int execle(const char* path, const char* argv,...,char* const* envp);

int execv(const char* path, char* const* arg); 

int execvp(const char* file, char* const* arg);

說明:

 exec函數族的作用是根據指定的文件名找到可執行文件,並用它來取代調用進程的內容,換句話說,就是在調用進程內部執行一個可執行文件

其中只有execve是真正意義上的系統調用,其它都是在此基礎上經過包裝的庫函數

與一般情況不同,exec函數族的函數執行成功後不會返回,因爲調用進程的實體,包括代碼段,數據段和堆棧等都已經被新的內容取代,只留下進程ID等一些表面上的信息仍保持原樣,頗有些神似"三十六計"中的"金蟬脫殼"。看上去還是舊的軀殼,卻已經注入了新的靈魂。只有調用失敗了,它們纔會返回一個-1,從原程序的調用點接着往下執行。

fork()和exec()這兩個函數,前者用於並行執行,父、子進程執行相同正文中的不同部分;後者用於調用其他進程,父、子進程執行不同的正文,調用前,一般應爲子進程創造一個乾淨的環境。

fork()以後,父、子進程共享代碼段,並只重新創建數據有改變的頁(段頁式管理)

exec()以後,建立新的代碼段,用被調用程序的內容填充。

前者的子進程執行後續的公共代碼,後者的子進程不執行後續的公共代碼。

父、子進程以及各個子進程執行的順序不定。

例子:printf("now this process will be ps command\n"); 

execl("/bin/ps","ps","-ef",NULL); 

2.4.popen()    初始化從/到一個進程的管道

功能:初始化從/到一個進程的管道

語法:

#include <stdio.h> 

FILE *popen(command,type) 

char *command,type; 

說明:本系統調用在調用進程和被執行命令間創建一個管道

參數command做爲被執行的命令行.type做爲I/O模式,"r"爲從被 

執行命令讀,"w"爲向被執行命令寫.返回一個標準流指針,做爲管 

道描述符,向被執行命令讀或寫數據(做爲被執行命令的STDIN或 

STDOUT)該系統調用可以用來在程序中調用系統命令,並取得命令 

的輸出信息或者向命令輸入信息

返回值:不成功則返回NULL,成功則返回管道的文件指針

2.5.pclose()    關閉到一個進程的管道

功能:關閉到一個進程的管道

語法:

#include <stdio.h> 

int pclose(strm) 

FILE *strm; 

說明:本系統調用用於關閉由popen()打開的管道,並會等待由popen() 

激活的命令執行結束後,關閉管道後讀取命令返回碼

返回值:若關閉的文件描述符不是由popen()打開的,則返回-1. 

例子:printf("now this process will call popen system call\n"); 

FILE * fd; 

if ((fd=popen("ps -ef","r"))==NULL) { 

printf("call popen failed\n"); 

return; 

else { 

char str[80]; 

while (fgets(str,80,fd)!=NULL) 

printf("%s\n",str); 

pclose(fd); 

2.6.wait()      等待一個子進程返回並修改狀態

功能:等待一個子進程返回並修改狀態 

語法:

#include <sys/types.h> 

#include <sys/wait.h> 

pid_t wait(stat_loc) 

int *stat_loc; 

說明:允許調用進程取得子進程的狀態信息.調用進程將會掛起直到其 

一個子進程終止

返回值:等待到一個子進程返回時,返回值爲該子進程號,否則返回值爲 

-1.同時stat_loc返回子進程的返回值

例子:/*父進程*/ 

if (fork()>0) { 

wait((int *)0); 

/*父進程等待子進程的返回*/ 

else { 

/*子進程處理過程*/ 

exit(0); 

2.7.waitpid()   等待指定進程號的子進程的返回並修改狀態

功能:等待指定進程號的子進程的返回並修改狀態 

語法:

#include <sys/types.h> 

#include <sys/wait.h> 

pid_t waitpid(pid,stat_loc,options) 

pid_t pid; 

int *stat_loc,options; 

說明:pid等於-1,options等於0,該系統調用等同於wait().否則該 

系統調用的行爲由參數pidoptions決定

pid指定了一組父進程要求知道其狀態的子進程

-1:要求知道任何一個子進程的返回狀態

>0:要求知道進程號爲pid值的子進程的狀態

<-1:要求知道進程組號爲pid的絕對值的子進程的狀態

options參數爲以比特方式表示的標誌以或運算組成的位圖,每個 

標誌以字節中某個比特置1表示

WUNTRACED:報告任何未知而又已停止運行的指定進程號的子進 

程的狀態.該子進程的狀態自停止運行時起就沒有被報告 過

WCONTINUED:報告任何繼續運行的指定進程號的子進程的狀態

該子進程的狀態自繼續運行起就沒有被報告過

WHOHANG:若調用本系統調用時,指定進程號的子進程的狀態目 

前並不是立即有效的(即可被立即讀取的),調用進程並被 暫停執行

WNOWAIT:保持將其狀態設置在stat_loc的進程在可等待狀態

該進程將等待直到下次被要求其返回狀態值

返回值:等待到一個子進程返回時,返回值爲該子進程號,否則返回值爲 1. 

同時stat_loc返回子進程的返回值

例子:pid_t pid; 

int stat_loc; /*父進程*/ 

if ((pid="fork())">0) { 

waitpid(pid,&stat_loc,0); 

/*父進程等待進程號爲pid的子進程的返回*/ 

else { 

/*子進程的處理過程*/ 

exit(1); 

/*父進程*/ 

printf("stat_loc is [%d]\n",stat_loc); 

/*字符串"stat_loc is [1]"將被打印出來*/ 

2.8.setpgrp()   設置進程組號和會話號

功能:設置進程組號和會話號

語法:

#include <sys/types.h> 

pid_t setpgrp() 

說明:若調用進程不是會話首進程.將進程組號和會話號都設置爲與它 

的進程號相等.並釋放調用進程的控制終端

返回值:調用成功後,返回新的進程組號

例子:/*父進程處理*/ 

if (fork()>0) { 

/*父進程處理*/ 

else { 

setpgrp(); 

/*子進程的進程組號已修改成與它的進程號相同*/ 

exit(0); 

2.9.exit()      終止進程

功能:終止進程

語法:

#include <stdlib.h> 

void exit(status) 

int status; 

說明:調用進程被該系統調用終止.引起附加的處理在進程被終止前全 

部結束

返回值:無 

2.10.signal()   信號管理功能

功能:信號管理功能 

語法:

#include <signal.h> 

void (*signal(sig,disp))(int) 

int sig; 

void (*disp)(int); 

void (*sigset(sig,disp))(int) 

int sig; 

void (*disp)(int); 

int sighold(sig) 

int sig; 

int sigrelse(sig) 

int sig; 

int sigignore(sig) 

int sig; 

int sigpause(sig) 

int sig; 

說明:這些系統調用提供了應用程序對指定信號的簡單的信號處理

signal()sigset()用於修改信號定位.參數sig指定信號(除了 

SIGKILLSIGSTOP,這兩種信號由系統處理,用戶程序不能捕捉到). 

disp指定新的信號定位,即新的信號處理函數指針.可以爲 

SIG_IGN,SIG_DFL或信號句柄地址

若使用signal(),disp是信號句柄地址,sig不能爲SIGILL,SIGTRAP 

SIGPWR,收到該信號時,系統首先將重置sig的信號句柄爲SIG_DFL, 

然後執行信號句柄

若使用sigset(),disp是信號句柄地址,該信號時,系統首先將該 

信號加入調用進程的信號掩碼中,然後執行信號句柄.當信號句柄 

運行結束 

,系統將恢復調用進程的信號掩碼爲信號收到前的狀態.另外

使用sigset(),dispSIG_HOLD,則該信號將會加入調用進程的 

信號掩碼中而信號的定位不變

sighold()將信號加入調用進程的信號掩碼中

sigrelse()將信號從調用進程的信號掩碼中刪除

sigignore()將信號的定位設置爲SIG_IGN. 

sigpause()將信號從調用進程的信號掩碼中刪除,同時掛起調用 

進程直到收到信號

若信號SIGCHLD的信號定位爲SIG_IGN,則調用進程的子進程在終 

止時不會變成僵死進程.調用進程也不用等待子進程返回並做相 

應處理

返回值:調用成功則signal()返回最近調用signal()設置的disp的值

否則返回SIG_ERR. 

例子一:設置用戶自己的信號中斷處理函數,SIGINT信號爲例

int flag=0; 

void myself() 

flag=1; 

printf("get signal SIGINT\n"); 

/*若要重新設置SIGINT信號中斷處理函數爲本函數則執行以 

*下步驟*/ 

void (*a)(); 

a=myself; 

signal(SIGINT,a); 

flag=2; 

main() 

while (1) { 

sleep(2000); /*等待中斷信號*/ 

if (flag==1) { 

printf("skip system call sleep\n"); 

exit(0); 

if (flag==2) { 

printf("skip system call sleep\n"); 

printf("waiting for next signal\n"); 

2.11.kill()     向一個或一組進程發送一個信號

功能:向一個或一組進程發送一個信號

語法:

#include <sys/types.h> 

#include <signal.h> 

int kill(pid,sig); 

pid_t pid; 

int sig; 

說明:本系統調用向一個或一組進程發送一個信號,該信號由參數sig指 

,爲系統給出的信號表中的一個.若爲0(空信號)則檢查錯誤但 

實際上並沒有發送信號,用於檢查pid的有效性

pid指定將要被髮送信號的進程或進程組.pid若大於0,則信號將 

被髮送到進程號等於pid的進程;pid等於0則信號將被髮送到所 

有的與發送信號進程同在一個進程組的進程(系統的特殊進程除 

);pid小於-1,則信號將被髮送到所有進程組號與pid絕對值 

相同的進程;pid等於-1,則信號將被髮送到所有的進程(特殊系 

統進程除外). 

信號要發送到指定的進程,首先調用進程必須有對該進程發送信 

號的權限.若調用進程有合適的優先級則具備有權限.若調用進程 

的實際或有效的UID等於接收信號的進程的實際UID或用setuid() 

系統調用設置的UID,sig等於SIGCONT同時收發雙方進程的會話 

號相同,則調用進程也有發送信號的權限

若進程有發送信號到pid指定的任何一個進程的權限則調用成功

否則調用失敗,沒有信號發出

返回值:調用成功則返回0,否則返回-1. 

例子:假設前一個例子進程號爲324,現向它發一個SIGINT信號,讓它做 

信號處理

kill((pid_t)324,SIGINT); 

2.12.alarm()   設置一個進程的超時時鐘

功能:設置一個進程的超時時鐘

語法:

#include <unistd.h< 

unsigned int alarm(sec) 

unsigned int sec; 

說明:指示調用進程的超時時鐘在指定的時間後向調用進程發送一個 

SIGALRM信號.設置超時時鐘時時間值不會被放入堆棧中,後一次 

設置會把前一次(還未到超時時間)沖掉

sec0,則取消任何以前設置的超時時鐘

fork()會將新進程的超時時鐘初始化爲0.而當一個進程用exec() 

族系統調用新的執行文件時,調用前設置的超時時鐘在調用後仍 

有效

返回值:返回上次設置超時時鐘後到調用時還剩餘的時間秒數

例子:int flag=0; 

void myself() 

flag=1; 

printf("get signal SIGALRM\n"); 

/*若要重新設置SIGALRM信號中斷處理函數爲本函數則執行 

*以下步驟*/ 

void (*a)(); 

a=myself; 

signal(SIGALRM,a); 

flag=2; 

main() 

alarm(100); /*100秒後發超時中斷信號*/ 

while (1) { 

sleep(2000); /*等待中斷信號*/ 

if (flag==1) { 

printf("skip system call sleep\n"); 

exit(0); 

if (flag==2) { 

printf("skip system call sleep\n"); 

printf("waiting for next signal\n"); 

2.13.msgsnd()  發送消息到指定的消息隊列中

功能:發送消息到指定的消息隊列中

語法:

#include <sys/types.h> 

#include <sys/ipc.h> 

#include <sys/msg.h> 

int msgsnd(msqid,msgp,msgsz,msgflg) 

int msqid; 

void *msgp; 

size_t msgsz; 

int msgflg; 

說明:發送一個消息到由msqid指定消息隊列標識號的消息隊列

參數msgp指向一個用戶定義的緩衝區,並且緩衝區的第一個域應 

爲長整型,指定消息類型,其他數據放在緩衝區的消息中其他正文 

區內.下面是消息元素定義

long mtype; 

char mtext[]; 

mtype是一個整數,用於接收進程選擇消息類型

mtext是一個長度爲msgsz字節的任何正文,參數msgsz可從0到系 

統允許的最大值間變化

msgflg指定操作行爲

(msgflg&IPC_NOWAIT)是真的,消息並不是被立即發送而調用 

進程會立即返回

(msgflg&IPC_NOWAIT)不是真的,則調用進程會被掛起直到下 

面情況之一發生

消息被髮送出去

消息隊列標誌被系統刪除.系統調用返回-1. 

調用進程接收到一個未被忽略的中斷信號,調用進程繼續 

執行或被終止

調用成功後,對應指定的消息隊列的相關結構做如下動作

消息數(msg_qnum)1. 

消息隊列最近發送進程號(msg_lspid)改爲調用進程號

消息隊列發送時間(msg_stime)改爲當前系統時間

以上信息可用命令ipcs -a看到

返回值:成功則返回0,否則返回-1. 

2.14.msgrcv()  從消息隊列中取得指定類型的消息

功能:從消息隊列中取得指定類型的消息

語法:

#include <sys/types.h> 

#include <sys/ipc.h> 

#include <sys/msg.h> 

int msgrcv(msqid,msgp,msgsz,msgtyp,msgflg) 

int msqid; 

void *msgp; 

int msgsz; 

long msgtyp; 

int msgflg; 

說明:本系統調用從由msqid指定的消息隊列中讀取一個由msgtyp指定 

類型的消息到由msgp指向的緩衝區中,同樣的,該緩衝區的結構如 

前所述,包括消息類型和消息正文.msgsz爲可接收的消息正文的 

字節數.若接收到的消息正文的長度大於msgsz,則會被截短到 

msgsz字節爲止(當消息標誌msgflg&MSG_NOERROR爲真時),截掉的 

部份將被丟失,而且不通知消息發送進程

msgtyp指定消息類型

0則接收消息隊列中第一個消息

大於0則接收消息隊列中第一個類型爲msgtyp的消息

小於0則接收消息隊列中第一個類型值不小於msgtyp絕對值且 

類型值又最小的消息

msgflg指定操作行爲

(msgflg&IPC_NOWAIT)是真的,調用進程會立即返回,若沒有 

接收到消息則返回值爲-1,errno設置爲ENOMSG. 

(msgflg&IPC_NOWAIT)不是真的,則調用進程會被掛起直到下 

面情況之一發生

隊列中的消息的類型是有效的

消息隊列標誌被系統刪除.系統調用返回-1. 

調用進程接收到一個未被忽略的中斷信號,調用進程繼續 

執行或被終止

調用成功後,對應指定的消息隊列的相關結構做如下動作

消息數(msg_qnum)1. 

消息隊列最近接收進程號(msg_lrpid)改爲調用進程號

消息隊列接收時間(msg_rtime)改爲當前系統時間

以上信息可用命令ipcs -a看到

返回值:調用成功則返回值等於接收到實際消息正文的字節數

不成功則返回-1. 

2.15.msgctl()   消息控制操作

功能:消息控制操作 

語法:

#include <sys/types.h> 

#include <sys/ipc.h> 

#include <sys/msg.h> 

int msgctl(msqid,cmd,buf) 

int msqid,cmd; 

struct msqid_ds *buf; 

說明:本系統調用提供一系列消息控制操作,操作動作由cmd定義,以下 

cmd定義值表明了各操作動作的定義

. IPC_STAT:msqid相關的數據結構中各個元素的當前值放入由 

buf指向的結構中

. IPC_SET:msqid相關的數據結構中的下列元素設置爲由buf指 

向的結構中的對應值

msg_perm.uid 

msg_perm.gid 

msg_perm.mode 

msg_qbytes 

本命令只能由有效UID等於msg_perm.cuidmsg_perm.uid的 

進程或有效UID有合適權限的進程操作.只有具有合適權限的 

用戶才能增加msg_qbytes的值

. IPC_RMID:刪除由msqid指示的消息隊列.將它從系統中刪除並 

破壞相關的數據結構

本命令只能由有效UID等於msg_perm.cuidmsg_perm.uid的 

進程或有效UID有合適權限的進程操作

返回值:調用成功則返回值爲0,否則爲-1. 

2.16.msgget()   取得一個消息隊列

功能:取得一個消息隊列

語法:

#include <sys/types.h> 

#include <sys/ipc.h> 

#include <sys/msg.h> 

int msgget(key,msgflg) 

key_t key; 

int msgflg; 

說明:本系統調用返回與參數key相關的消息隊列的標識符

若以下事實成立,則與消息隊列相關的標識符和數據結構將被創 

建出來

若參數key等於IPC_PRIVATE. 

若參數key沒有一個已存在的消息隊列標識符與之相關,同時值 

(msgflg&IPC_CREAT)爲真

創建消息隊列時,與新的消息隊列標識符相關的數據結構將被初 

始化爲如下

. msg_perm.cuidmsg_perm.uid設置爲調用進程的有效UID. 

. msg_perm.cgidmsg_perm.gid設置爲調用進程的有效GID. 

. msg_perm.mode訪問權限比特位設置爲msgflg訪問權限比特位

. msg_qnum,msg_lspid,msg_lrpid,msg_stime,msg_rtime設置爲0. 

. msg_ctime設置爲當前系統時間

. msg_qbytes設置爲系統允許的最大值

返回值:調用成功則返回一非0,稱爲消息隊列標識符;否則返回值爲-1. 

例子:本例將包括上述所有消息隊列操作的系統調用

#define RKEY 0x9001L /*讀消息隊列的KEY*/ 

#define WKEY 0x9002L /*寫消息隊列的KEY*/ 

#define MSGFLG 0666 /*消息隊列訪問權限*/ 

#define IPC_WAIT 0 /*等待方式在include文件中未定義*/ 

int rmsqid; /*讀消息隊列標識符*/ 

int wmsqid; /*寫消息隊列標識符*/ 

struct msgbuf { 

long mtype; 

char mtext[200]; 

} buf; 

/*若讀消息隊列已存在就取得標識符,否則則創建並取得標識符*/ 

if ((rmsqid=msgget(RKEY,MSGFLG|IPC_CREAT))<0) { 

printf("get read message queue failed\n"); 

exit(1); 

} /*若寫消息隊列已存在則失敗,若不存在則創建並取得標識符*/ 

if ((wmsqid="msgget(WKEY," MSGFLG|IPC_CREAT|IPC_TRUNC))<0) { 

printf("get write message queue failed\n"); 

exit(2); 

} /*接收所有類型的消息*/ 

if (msgrcv(rmsqid,&buf,sizeof(struct msgbuf)-sizeof(long), 0L,IPC_WAIT)>0) 

printf("get %ld type message from queue:%s\n", 

buf.mtype,buf.mtext); 

else { 

printf("get message failed\n"); 

exit(3); 

buf.mtype=3L; 

if (msgsnd(wmsqid,&buf,sizeof(struct msgbuf)-sizeof(long), 

IPC_NOWAIT)>0) 

printf("send message OK\n"); 

else { 

printf("send message failed\n"); 

exit(4); 

msgctl(wmsqid,IPC_RMID,(struct msqid *)NULL); 

2.17.shmat()    聯接共享內存的操作

功能:聯接共享內存的操作

語法:

#include <sys/types.h> 

#include <sys/ipc.h> 

#include <sys/shm.h> 

void *shmat(shmid,shmaddr,shmflg) 

int shmid; 

void *shmaddr; 

int shmid; 

說明:將由shmid指示的共享內存聯接到調用進程的數據段中.被聯接的 

段放在地址,該地址由以下準則指定

shmaddr等於(void *)0,則段聯接到由系統選擇的第一個可 

用的地址上

shmaddr不等於(void *)0同時(shmflg&SHM_RND)值爲真,則 

段聯接到由(shmaddr-(shmaddr%SHMLBA))給出的地址上

shmaddr不等於(void *)0同時(shmflg&SHM_RND)值爲假,則 

段聯接到由shmaddr指定的地址上

(shmflg&sSHM_RDONLY)爲真並且調用進程有讀允許,則被聯接 

的段爲只讀;否則,若值不爲真且調用進程有讀寫權限,則被聯接 

的段爲可讀寫的

返回值:若調用成功則返回被聯接的共享內存段在數據段上的啓始地址

否則返回值爲-1. 

2.18.shmdt()    斷開共享內存聯接的操作

功能:斷開共享內存聯接的操作

語法:

#include <sys/types.h> 

#include <sys/ipc.h> 

#include <sys/shm.h> 

void *shmdt(shmaddr) 

void *shmaddr; 

說明:本系統調用將由shmaddr指定的共享內存段從調用進程的數據段 

脫離出去

返回值:若調用成功則返回值爲0,否則返回值爲-1. 

2.19.shmget()   取得共享內存段

功能:取得共享內存段 

語法:

#include <sys/types.h> 

#include <sys/ipc.h> 

#include <sys/shm.h> 

int shmget(key,size,shmflg) 

key_t key; 

int size,shmflg; 

說明:本系統調用返回key相關的共享內存標識符

共享內存標識符和相關數據結構及至少size字節的共享內存段能 

正常創建,要求以下事實成立

參數key等於IPC_PRIVATE. 

參數key沒有相關的共享內存標識符,同時(shmflg&IPC_CREAT) 

值爲真

共享內存創建時,新生成的共享內存標識相關的數據結構被初始 

化如下

. shm_perm.cuidshm_perm.uid設置爲調用進程的有效UID. 

. shm_perm.cgidshm_perm.gid設置爲調用進程的有效GID. 

. shm_perm.mode訪問權限比特位設置爲shmflg訪問權限比特位

. shm_lpid,shm_nattch,shm_atime,shm_dtime設置爲0. 

. shm_ctime設置爲當前系統時間

. shm_segsz設置爲0. 

返回值:若調用成功則返回一個非0,稱爲共享內存標識符,否則返回 

值爲-1. 

2.20.shmctl()   共享內存控制操作

功能:共享內存控制操作

語法:

#include <sys/types.h> 

#include <sys/ipc.h> 

#include <sys/shm.h> 

int shmctl(shmid,cmd,buf) 

int shmid,cmd; 

struct shmid_ds *buf; 

說明:本系統調用提供一系列共享內存控制操作.操作行爲由cmd指定

以下爲cmd的有效值

. IPC_STAT:shmid相關的數據結構中各個元素的當前值放入由 

buf指向的結構中

. IPC_SET:shmid相關的數據結構中的下列元素設置爲由buf指 

向的結構中的對應值

shm_perm.uid 

shm_perm.gid 

shm_perm.mode 

本命令只能由有效UID等於shm_perm.cuidshm_perm.uid的 

進程或有效UID有合適權限的進程操作

. IPC_RMID:刪除由shmid指示的共享內存.將它從系統中刪除並 

破壞相關的數據結構

本命令只能由有效UID等於shm_perm.cuidshm_perm.uid的 

進程或有效UID有合適權限的進程操作

返回值:若調用成功則返回0,否則返回-1. 

例子:本例包括上述所有共享內存操作系統調用

#include <sys/types.h> 

#include <sys/ipc.h> 

#include <sys/shm.h> 

#define SHMKEY 74 

#define K 1024 

int shmid; 

cleanup() 

shmctl(shmid,IPC_RMID,0); 

exit(0); 

main() 

int *pint; 

char *addr1,*addr2; 

extern char *shmat(); 

extern cleanup(); 

for (i=0;i<20;i++) signal(i,cleanup); 

shmid=shmget(SHMKEY,128*K,0777|IPC_CREAT); 

addr1=shmat(shmid,0,0); 

addr2=shmat(shmid,0,0); 

printf("addr1 0x%x addr2 0x%x\n",addr1,addr2); 

pint=(int*)addr1; 

for (i=0;i<256;i++) *pint++=i; 

pint=(int*)addr1; 

*pint=256; 

pint=(int*)addr2; 

for (i=0;i<256;i++) 

printf("index %d\tvalue%d\n",i,*pint++); 

shmdt(addr1); 

shmdt(addr2); 

pause(); 

2.21.semctl()   信號量控制操作

功能:信號量控制操作

語法:

#include <sys/types.h> 

#include <sys/ipc.h> 

#include <sys/sem.h> 

int semctl(semid,memnum,cmd,arg) 

int semid,semnum,cmd; 

union semun { 

int val; 

struct semid_ds *buf; 

ushort *array; 

}arg; 

說明:本系統調用提供了一個信號量控制操作,操作行爲由cmd定義,這 

些命令是對由semidsemnum指定的信號量做操作的.每個命令都 

要求有相應的權限級別

. GETVAL:返回semval的值,要求有讀權限

. SETVAL:設置semval的值到arg.val.此命令成功執行後

semadj的值對應的所有進程的信號量全部被清除,要求有修 

改權限

. GETPID:返回sempid的值,要求有讀權限

. GETNCNT:返回semncnt的值,要求有讀權限

. GETZCNT:返回semzcnt的值,要求有讀權限

以下命令在一組信號量中的各個semval上操作

. GETALL:返回每個semval的值,同時將各個值放入由arg.array 

指向的數組中.當此命令成功執行後,semadj的值對應的所有 

進程的信號量全部被清除,要求有修改權限

. SETALL:根據由arg.array指向的數組設置各個semval.當此 

命令成功執行後,semadj的值對應的所有進程的信號量全部 

被清除,要求有修改權限

以下命令在任何情況下都是有效的

. IPC_STAT:將與semid相關的數據結構的各個成員的值放入由 

arg.buf指向的結構中.要求有讀權限

. IPC_SET:設置semid相關數據結構的如下成員,設置數據從 

arg.buf指向的結構中讀取

sem_perm.uid 

sem_perm.gid 

sem_perm.mode 

本命令只能由有效UID等於sem_perm.cuidsem_perm.uid的 

進程或有效UID有合適權限的進程操作

. IPC_RMID:刪除由semid指定的信號量標識符和相關的一組信號 

量及數據結構.本命令只能由有效UID等於sem_perm.cuid或 

sem_perm.uid的進程或有效UID有合適權限的進程操作

返回值:若調用成功,則根據cmd返回以下值

GETVAL:semval的值

GETPID:sempid的值

GETNCNT:semncnt的值

GETZCNT:semzcnt的值

其他:0. 

若調用失敗則返回-1. 

2.22.semget()   取得一組信號量

功能:取得一組信號量

語法:

#include <sys/types.h> 

#include <sys/ipc.h> 

#include <sys/sem.h> 

int semget(key,nsems,semflg) 

key_t key; 

int nsems,semflg; 

說明:返回和key相關的信號量標識符

若以下事實成立,則與信號量標識符,與之相關的semid_ds數據結 

構及一組nsems信號量將被創建

. key等於IPC_PRIVATE. 

系統內還沒有與key相關的信號量,同時(semflg&IPC_CREAT) 

爲真

創建時新的信號量相關的semid_ds數據結構被初始化如下

在操作權限結構,sem_perm.cuidsem_perm.uid設置等於調用 

進程的有效UID. 

在操作權限結構,sem_perm.cgidsem_perm.gid設置等於調用 

進程的有效GID. 

訪問權限比特位sem_perm.mode設置等於semflg的訪問權限比 

特位

. sem_otime設置等於0,sem_ctime設置等於當前系統時間

返回值:若調用成功,則返回一非0,稱爲信號量標識符;否則返回-1. 

2.23.semop()   信號量操作

功能:信號量操作

語法:

#include <sys/types.h> 

#include <sys/ipc.h> 

#include <sys/sem.h> 

int semop(semid,sops,nsops) 

int semid; 

struct sembuf *sops; 

unsigned nsops; 

說明:本系統調用用於執行用戶定義的在一組信號量上操作的行爲集合

該組信號量與semid相關

參數sops爲一個用戶定義的信號量操作結構數組指針

參數nsops爲該數組的元素個數

數組的每個元素結構包括如下成員

sem_num; /* 信號量數 */ 

sem_op; /* 信號量操作 */ 

sem_flg; /* 操作標誌 */ 

由本系統調用定義的每個信號量操作是針對由semidsem_num指 

定的信號量的.變量sem_op指定三種信號量操作的一種

sem_op爲一負數並且調用進程具有修改權限,則下列情況之 

一將會發生

semval不小於sem_op的絕對值,sem_op的絕對值被減去 

semval的值.(semflg&SEM_UNDO)爲真則sem_op的絕對值加 

上調用進程指定的信號量的semadj

semval小於sem_op的絕對值同時(semflg&IPC_NOWAIT)爲 

,則本調用立即返回

semval小於sem_op的絕對值同時(semflg&IPC_NOWAIT)爲 

,則本系統調用將增加指定信號量相關的semncnt(加一), 

將調用進程掛起直到下列條件之一被滿足

(1).semval值變成不小於sem_op的絕對值.當這種情況發 

生時,指定的信號量相關的semncnt減一,若 

(semflg&SEM_UNDO)爲真則sem_op的絕對值加上調用 

進程指定信號量的semadj

(2).調用進程等待的semid已被系統刪除

(3).調用進程捕俘到信號,此時,指定信號量的semncnt值 

減一,調用進程執行中斷服務程序

sem_op爲一正值,同時調用進程具有修改權限,sem_op的值加 

semval的值,(semflg&SEM_UNDO)爲真,sem_op減去調用 

進程指定信號量的semadj

sem_op0,同時調用進程具有讀權限,下列情況之一將會發 

semval0,本系統調用立即返回

semval不等於0(semflg&IPC_NOWAIT)爲真,本系統調用 

立即返回

semval不等於0(semflg&IPC_NOWAIT)爲假,本系統調用 

將把指定信號量的 

semzcnt值加一,將調用進程掛起直到下列情況之一發生

(1).semval值變爲0,指定信號量的semzcnt值減一

(2).調用進程等待的semid已被系統刪除

(3).調用進程捕俘到信號,此時,指定信號量的semncnt值 

減一,調用進程執行中斷服務程序

返回值:調用成功則返回0,否則返回-1. 

例子:本例將包括上述信號量操作的所有系統調用

#include <sys/types.h> 

#include <sys/ipc.h> 

#include <sys/sem.h> 

#define SEMKEY 75 

int semid; 

unsigned int count; 

/*在文件sys/sem.h中定義的sembuf結構 

* struct sembuf { 

* unsigned short sem_num; 

* short sem_op; 

* short sem_flg; 

* }*/ 

struct sembuf psembuf,vsembuf; /*PV操作*/ 

cleanup() 

semctl(semid,2,IPC_RMID,0); 

exit(0); 

main(argc,argv) 

int argc; 

char *argv[]; 

int i,first,second; 

short initarray[2],outarray[2]; 

extern cleanup(); 

if (argc==1) { 

for (i=0;i<20;i++) 

signal(i,clearup); 

semid=semget(SEMKEY,2,0777|IPC_CREAT); 

initarray[0]=initarray[1]=1; 

semctl(semid,2,SETALL,initarray); 

semctl(semid,2,GETALL,outarray); 

printf("sem init vals %d%d \n", outarray[0],outarray[1]); 

pause(); /*睡眠到被一軟件中斷信號喚醒*/ 

} else if (argv[1][0]="='a') 

first=0; 

second=1; 

} else { 

first=1; 

second=0; 

semid=semget(SEMKEY,2,0777); 

psembuf.sem_op=-1; 

psembuf.sem_flg=SEM_UNDO; 

vsembuf.sem_op=1; 

vsembuf.sem_flg=SEM_UNDO; 

for (count=0;;xcount++) 

psembuf.sem_num=first; 

semop(semid,&psembuf,1); 

psembuf.sem_num=second; 

semop(semid,&psembuf,1); 

printf("proc %d count %d\n",getpid(),count); 

vsembuf.sem_num=second; 

semop(semid,&vsembuf,1); 

vsembuf.sem_num=first; 

semop(semid,&vsembuf,1); 

2.24.sdenter()  共享數據段同步訪問,加鎖

功能:共享數據段同步訪問,加鎖

語法:

#include <sys/sd.h< 

int sdenter(addr,flags) 

char *addr; 

int flags; 

說明:用於指示調用進程即將可以訪問共享數據段中的內容

參數addr爲將一個sdget()調用的有效返回碼

所執行的動作取決於flags的值

. SD_NOWAIT:若另一個進程已對指定的段調用本系統調用且還沒 

有調用sdleave(),並且該段並非用SD_UNLOCK標誌創建,則調 

用進程不是等待該段空閒而是立即返回錯誤碼

. SD_WRITE:指示調用進程希望向共享數據段寫數據.此時,另一 

個進程用SD_RDONLY標誌聯接該共享數據段則不被允許

返回值:調用成功則返回0,否則返回-1. 

2.25.sdleave()  共享數據段同步訪問,解鎖

功能:共享數據段同步訪問,解鎖

語法:

#include <sys/sd.h&dt; 

int sdleave(addr,flags) 

char *addr; 

說明:用於指示調用進程已完成修改共享數據段中的內容

返回值:調用成功則返回0,否則返回-1. 

2.26.sdget()    聯接共享數據段到調用進程的數據空間中

功能:聯接共享數據段到調用進程的數據空間中

語法:

#include <sys/sd.h> 

char *sdget(path,flags,size.mode) 

char *path; 

int flags; 

long size; 

int mode; 

說明:本系統調用將共享數據段聯接到調用進程的數據段中,具體動作 

flags的值定義

. SD_RDONLY:聯接的段爲只讀的

. SD_WRITE:聯接的段爲可讀寫的

. SD_CREAT:若由path命名的段存在且不在使用中,本標誌的作用 

同早先創建一個段相同,否則,該段根據sizemode的值進程 

創建.對段的讀寫訪問權限的授予基於mode給的權限,功能與 

一般文件的相同.段被初始化爲全0. 

. SD_UNLOCK:若用此標誌創建該段,則允許有多個進程同時訪問 

(在讀寫中)該段

返回值:若調用成功則返回聯接的段地址.否則返回-1. 

2.27.sdfree()   將共享數據段從調用進程的數據空間中斷開聯接

功能:將共享數據段從調用進程的數據空間中斷開聯接

語法:

#include <sys/sd.h> 

int sdfree(addr) 

char *addr; 

說明:本系統調用將共享數據段從調用進程的數據段的指定地址中分離

若調用進程已完成sdenter()的調用,還未調用sdleave()就調用 

本系統調用,sdleave()被自動調用,然後才做本調用的工作

返回值:若調用成功則返回聯接的段地址.否則返回-1. 

2.28.sdgetv()   同步共享數據訪問

功能:同步共享數據訪問

語法:

#include <sys/sd.h> 

int sdgetv(addr) 

char *addr; 

說明:用於同步協調正在使用共享數據段的進程.返回值爲共享數據段 

的版本號.當有進程對該段做sdleave()操作時,版本號會被修改

返回值:若調用成功,則返回指定共享數據段的版本號,否則返回-1. 

2.29.sdwaitv()  同步共享數據訪問

功能:同步共享數據訪問

語法:

#include <sys/sd.h> 

int sdwaitv(addr,vnum) 

char *addr; 

int vnum; 

說明:用於同步協調正在使用共享數據段的進程.返回值爲共享數據段 

的版本號.調用進程會睡眠直到指定段的版本號不再等於vnum; 

返回值:若調用成功,則返回指定共享數據段的版本號,否則返回-1. 

2.30.sbrk()     修改數據段空間分配

功能:修改數據段空間分配

語法:

char *sbrk(incr) 

int incr; 

說明:用於動態修改調用進程數據段的空間分配.進程將重置進程的分 

段值並分配一個合適大小的空間.分段值爲數據段外第一次分配 

的地址.要分配的空間的增加量等於分段值的增加量.新分配的空 

間設置爲0.若相同的內存空間重新分配給同一個進程,則空間的 

內容不確定

返回值:若成功調用則返回值爲0,否則返回-1. 

例子:本例將包括上述共享數據空間操作的所有系統調用

char * area1; 

char buf[21]; 

int v; 

/*取得或創建一個共享數據空間(系統特殊文件),名字爲 

/tmp/area1,長度爲640,用戶訪問權限爲0777*/ 

area1=sdget("/tmp/area1",SD_WRITE|SD_CREAT,640,0777); 

if ((int)area1==-1) { 

printf("get share data segment area1 failed\n"); 

exit(1); 

/*取得共享數據段area1的版本號*/ 

v=sdgetv(area1); 

/*申請訪問共享數據段area1,若已有進程在訪問該段則本進程掛 

*,否則進入訪問並將該數據段加寫鎖*/ 

sdenter(area1,SD_WRITE); 

/*對共享數據段訪問,10a*/ 

strcpy(area1,"aaaaaaaaaa"); 

/*申請解除訪問權限,若已有進程申請訪問則激活該進程*/ 

sdleave(area1); 

/*進程處理過程*/ 

/*等待取共享數據段area1的版本號*/ 

sdwaitv(area1,v); 

/*重新申請訪問共享數據段area1*/ 

sdenter(area1,SD_WRITE); 

/*讀取共享數據段中的數據*/ 

memcpy(buf,area1,20); 

/*申請解除訪問權限,若已有進程申請訪問則激活該進程*/ 

sdleave(area1); 

printf("the data now in area1 is [%s]\n",buf); 

2.31.getenv()   取得指定環境變量值

功能:取得指定環境變量值

語法:

#include <unistd.h>

#include <stdlib.h>

char *getenv(name) 

char *name; 

說明:本系統調用檢查環境字符串(格式如name="value),並在找到有指定名字的環境值後,返回指向value字符串的指針.否則返回空指 針

返回值:如前述

例子

char * value; 

value=getenv("HOME"); 

printf("HOME="[%s]\n",value); /*將打印出HOME環境變量的值*/ 

2.32.putenv()   修改或增加環境值

功能:修改或增加環境值

語法:

#include <stdlib.h> 

int putenv(string) 

char *string; 

說明:參數string指向一個字符串,格式如下

name=value 

本系統調用將環境變量name等於值value,修改或增加一個環境變 

,字符串string成爲環境的一部分

返回值:putenv()不能取得合適的內存空間則返回非0,否則返回0. 

例子:/*父進程處理*/ 

putenv("HOME=/home/abcdef"); 

putenv("PATH=/bin"); 

if (fork()>0) 

exit(0); /*父進程退出運行*/ 

/*子進程處理*/ 

setpgrp(); 

/*父進程設置的環境變量已傳到子進程*/ 

char * value1; 

value1=getenv("HOME"); 

value2=getenv("PATH"); 

printf("HOME=[%s],PATH=[%s]\n",value1,value2); 

/*將打印出"HOME=/home/abcdef""PATH=/bin"*/ 

.多進程編程技巧 

3.1.主要程序結構 

3.1.1事件主控方式 

若是應用程序屬於事務處理方式,則在主函數中設計爲監控事件發生當事件發生時,可以生成一個新的進程來處理該事務,事務處理完成後就 可以讓子進程退出系統.這種處理方式一般不要消息傳遞

3.1.2信息協調方式 

若是應用程序需要由多個進程協調處理完成,則可以生成這些進程通過消息在進程間的傳遞,使各個進程能相互協調,共同完成事務.這種處理方式一般是用fork()生成幾個進程後,exec()調用其它程序文件,使得不同的程序同時在系統內運行.然後通過IPC機制傳送消息,使各個程序能協調運行

3.2.選擇主體分叉點 

3.2.1事件初始產生 

對應於事件主控方式的程序結構.關鍵點在於以何種方式選擇事件的初始發生點,如網絡程序給出的建鏈信息.主控程序在收到該消息後就認爲是一個事件開始,則可以產生一個子進程處理後面的事務:接收交易信息,事務處理,發送返回交易信息,關閉鏈接等,完成後將子進程退出系統

3.2.2主程序自主產生 

對應於信息協調方式的程序結構.主控程序只負責生成幾個子進程,各個子進程分別調用exec()將不同的執行文件調入內存運行,主控程序在生成所有的子進程後即可退出系統,將子進程留在內存中運行

3.3.進程間關係處理 

3.3.1父子進程關係 

進程組處理 

進程組的概念是這樣的,當系統啓動時,第一個進程是init,其進程 

組號等於進程號,由它產生的所有子進程的進程組號也相同,子進程 

的子進程也繼承該進程組號,這樣,init所生成的所有子進程都屬 

於同一個進程組.但是,同一個進程組的父子進程可能在信號上有相 

互通訊,若父進程先於子進程退出系統,則子進程會成爲一個孤兒進 

,可能變成僵死進程.從而使該子進程在其不"願意"的情況下退出 

運行.爲解決這個問題,子進程可以自己組成一個新的進程組,即調 

setpgrp()與原進程組脫離關係,產生一個新的進程組,進程組號 

與它的進程號相同.這樣,父進程退出運行後就不會影響子進程的當 

前運行

子進程信號處理 

但是,單做上述處理還不能解決另一個困難,即子進程在退出運行 

,找不到其父進程(父進程已退出,子進程的父進程號改爲1).發送 

子進程退出信號後沒有父進程做出響應處理,該子進程就不可能完 

全退出運行,可能進入僵死狀態.所以父進程在產生子進程前最好屏 

蔽子進程返回信號的處理,生成子進程,在父進程退出運行後,子進 

程返回則其進程返回信號的處理會由系統給出缺省處理,子進程就 

可以正常退出

3.3.2兄弟進程關係 

交換進程號 

對於信息協調方式的程序來說,各兄弟進程間十分需要相互瞭解進 

程號,以便於信號處理機制.比較合理的方法是父進程生成一個共享 

內存的空間,每個子進程都在啓動時在共享內存中設置自己的進程 

.這樣,當一個子進程要向另一個子進程發送信號或是因爲其他原 

因需要知道另一個子進程號時,就可以在共享內存中訪問得到所需 

要的進程號

3.3.3殭屍進程及如何處理子進程死亡 

3.3.3.1殭屍進程

殭屍(Zombie)進程是非常特殊的一種,它已經放棄了幾乎所有內存空間,沒有任何可執行代碼,也不能被調度,僅僅在進程列表中保留一個位置,記載該進程的退出狀態等信息供其他進程收集,除此之外,殭屍進程不再佔有任何內存空間。

當一個進程已退出,但其父進程還沒有調用系統調用wait(稍後介紹)對其進行收集之前的這段時間裏,它會一直保持殭屍狀態。

殭屍進程的概念是從UNIX上繼承來的,而UNIX的先驅們設計這個東西並非是因爲閒來無聊想煩煩其他的程序員。殭屍進程中保存着很多對程序員和系統管理員非常重要的信息,首先,這個進程是怎麼死亡的?是正常退出呢,還是出現了錯誤,還是被其它進程強迫退出的?其次,這個進程佔用的總系統CPU時間和總用戶CPU時間分別是多少?發生頁錯誤的數目和收到信號的數目。這些信息都被存儲在殭屍進程中,試想如果沒有殭屍進程,進程一退出,所有與之相關的信息都立刻歸於無形,而此時程序員或系統管理員需要用到,就只好乾瞪眼了。

收集這些信息,並終結這些殭屍進程靠waitpid調用和wait調用等方法完成。

殭屍進程雖然對其他進程幾乎沒有什麼影響,不佔用CPU時間,消耗的內存也幾乎可以忽略不計,但有它在那裏呆着,還是讓人覺得心裏很不舒服,同時Linux系統中進程數目是有限制的,在一些特殊的情況下,如果存在太多的殭屍進程,也會影響到新進程的產生。

for(int i=0;i<10;i++)

  if( fork==0)

        exit(0);

   

3.3.3.2處理子進程死亡的四種方法

3.3.3.2.1  忽略SIGCHLD信號.(只在Linux使用);

struct sigaction act,oldact;

act.sa_handler=SIG_IGN;

sigemptyset(&act.sa_mask);

act.sa_flags=0;

if(sigaction(SIGCHLD,&act,&oldact)<0)

exit(1);

內核負責清除進程表項。(Linux only)

3.3.3.2.2  調用wait()或waitpid();

  pid_t wait(int* statloc);

pid_t waitpid(pid_t pid,int* statloc,int option);

前者等待任意一個子進程結束,後者等待特定子進程結束;

函數返回子進程號,statloc返回exit的參數。

Wait:  進程一旦調用了wait,就立即阻塞自己,由wait自動分析是否當前進程的某個子進程已經退出,如果讓它找到了這樣一個已經變成殭屍的子進程,wait就會收集這個子進程的信息,並把它徹底銷燬後返回;如果沒有找到這樣一個子進程,wait就會一直阻塞在這裏,直到有一個出現爲止。

參數statloc用來保存被收集進程退出時的一些狀態,它是一個指向int類型的指針。如果我們對這個子進程是如何死掉的毫不在意,只想把這個殭屍進程消滅掉,(絕大多數情況下如此),我們就可以設定這個參數爲NULL,即:

pidx = wait(NULL)

如果成功,wait會返回被收集的子進程的進程ID,如果調用進程沒有子進程,調用就會失敗,此時wait返回-1,同時errno被置爲ECHILD。

Waitpid: 從本質上講,系統調用waitpid和wait的作用是完全相同的,但waitpid多出了兩個可由用戶控制的參數pid和options,從而爲我們編程提供了另一種更靈活的方式。

Pid:是一個進程ID。當pid取不同的值時,有不同的意義:

pid>0時,只等待進程ID等於pid的子進程,不管其它已經有多少子進程運行結束退出了,只要指定的子進程還沒有結束,waitpid就會一直等下去。 

pid=-1時,等待任何一個子進程退出,沒有任何限制,此時waitpid和wait的作用一模一樣。 

pid=0時,等待同一個進程組中的任何子進程,如果子進程已經加入了別的進程組,waitpid不會對它做任何理睬。 

pid<-1時,等待一個指定進程組中的任何子進程,這個進程組的ID等於pid的絕對值。 

Options:目前在Linux中只支持WNOHANG和WUNTRACED兩個選項,可以用"|"運算符把它們連接起來使用,如:

ret=waitpid(-1,NULL,WNOHANG | WUNTRACED);

WNOHANG,表示即使沒有子進程退出,它也會立即返回,不會像wait那樣永遠等下去。

WUNTRACED,與跟蹤調試有關,極少用到。

我們也可以把options設爲0,如:

ret=waitpid(-1,NULL,0);

waitpid的返回值比wait稍微複雜一些,一共有3種情況:

1) 正常返回的時候,waitpid返回收集到的子進程的進程ID; 

2) 如果設置了選項WNOHANG,而調用中waitpid發現沒有已退出的子進程可收集,則返回0;

3) 調用中出錯,則返回-1,這時errno會被設置成相應的值以指示錯誤所在。例如:當pid所指示的子進程不存在,或此進程存在,但不是調用進程的子進程,waitpid就會出錯返回,這時errno被設置爲ECHILD;

3.3.3.2.3  捕獲SIGCHLD信號,在處理函數中調用wait()或waitpid().

void sigchld_handler(int signo)

{

  pid_t pid;

  int stat;

  while( (pid=waitpid(-1,&stat,WNOHANG))>0 )

{}

return;

}

void main()

{

struct sigaction act,oldact;

act.sa_handler=sigchld_handler;

sigemptyset(&act.sa_mask);

act.sa_flags=0;

if(sigaction(SIGCHLD,&act,&oldact)<0)

{

  ……

}

}

3.3.3.2.4  兩次調用fork().

第一次調用fork()產生的子進程的主進程,調用exit(0), 第二次調用fork()產生的子進程成爲孤兒(orphaned process)進程,交給init管理,孤兒進程退出時.系統會把它清理乾淨。

int main()

{

      int i;

      pid_t pid;

      pid=fork();

      if(pid==0)

      {

     for(i=0;i<5;i++)

     {

        if(fork(0)==0)

        {

       sleep(1);

       exit(0);

        }

     }

     exit(0);

      }

      for(;;){}

}

總結:進程的一生

隨着一句fork,一個新進程呱呱落地,但它這時只是老進程的一個克隆。

然後隨着exec,新進程脫胎換骨,離家獨立,開始了爲人民服務的職業生涯。

人有生老病死,進程也一樣,它可以是自然死亡,即運行到main函數的最後一個"}",從容地離我們而去;也可以是自殺,自殺有2種方式,一種是調用exit函數,一種是在main函數內使用return,無論哪一種方式,它都可以留下遺書,放在返回值裏保留下來;它還甚至能可被謀殺,被其它進程通過另外一些方式結束他的生命。

進程死掉以後,會留下一具殭屍,wait和waitpid充當了殮屍工,把殭屍推去火化,使其最終歸於無形。

3.3.4守護進程 

Linux有三種進程:核心進程、守護進程、用戶進程。

守護進程在後臺運行,如:打印管理程序、http服務器

Linux的大多數服務器就是用守護進程實現的。

守護進程的編程要點 
    不同Unix環境下守護進程的編程規則並不一致,但守護進程的編程原則其實都一樣,區別在於具體的實現細節不同。這個原則就是要滿足守護進程的特性。同時,Linux是基於Syetem V的SVR4並遵循Posix標準,實現起來與BSD4相比更方便。編程要點如下; 
1. 在後臺運行 

   在進程中調用fork後,使父進程終止,讓Daemon在子進程中後臺執行。 

   

if(pid=fork()) 

    exit(0);//是父進程,結束父進程,子進程繼續 

2. 脫離控制終端,登錄會話和進程組 
    進程與控制終端,登錄會話和進程組之間的關係:

進程屬於一個進程組,進程組號(GID)就是進程組長的進程號(PID)。

登錄會話可以包含多個進程組。這些進程組共享一個控制終端。這個控制終端通常是創建進程的登錄終端。會話過程對控制終端具有獨佔性

由於控制終端,登錄會話和進程組通常是從父進程繼承下來的。我們的目的就是要擺脫它們,使之不受它們的影響。方法是在第1點的基礎上,調用setsid()使進程成爲會話組長:     setsid();

說明:當進程是會話組長時setsid()調用失敗。

第一點已經保證進程不是會話組長。

setsid()調用成功後,進程成爲新的會話組長和新的進程組長,並與原來的登錄會話和進程組脫離,同時與控制終端脫離。 

1. 忽略SIGHUP,再次調用fork(),然後父進程退出。

目的:禁止進程重新打開控制終端 

現在,進程已經成爲無終端的會話組長。但它可以重新申請打開一個控制終端。可以通過使進程不再成爲會話組長來禁止進程重新打開控制終端: 

if(pid=fork()) 

exit(0);//結束第一子進程,第二子進程繼續(第二子進程不再是會話組長) 

2. 關閉打開的文件描述符 

進程從創建它的父進程那裏繼承了打開的文件描述符。如不關閉,將會浪費系統資源,造成進程所在的文件系統無法卸下,以及引起無法預料的錯誤。按如下方法關閉它們: 

for(i=0;i 關閉打開的文件描述符close(i);)

3. 改變當前工作目錄 

進程活動時,其工作目錄所在的文件系統不能卸下。一般需要將工作目錄改變到根目錄。對於需要轉儲核心,寫運行日誌的進程將工作目錄改變到特定目錄如/tmpchdir("/") 

4. 重設文件創建掩模 

進程從創建它的父進程那裏繼承了文件創建掩模。它可能修改守護進程所創建的文件的存取位。爲防止這一點,將文件創建掩模清除:umask(0),使得進程具有完全的寫權限; 

5. 處理SIGCHLD信號 

處理SIGCHLD信號並不是必須的。但對於某些進程,特別是服務器進程往往在請求到來時生成子進程處理請求。如果父進程不等待子進程結束,子進程將成爲殭屍進程(zombie)從而佔用系統資源。如果父進程等待子進程結束,將增加父進程的負擔,影響服務器進程的併發性能。在Linux下可以簡單地將SIGCHLD信號的操作設爲SIG_IGN。 

signal(SIGCHLD,SIG_IGN); 

這樣,內核在子進程結束時不會產生殭屍進程。

守護進程實例 

這個守護進程實例包括兩部分:主程序test.c和初始化程序init.c。主程序每隔一分鐘向/tmp目錄中的日誌test.log報告運行狀態。

初始化程序中的init_daemon函數負責生成守護進程。可以利用init_daemon函數生成自己的守護進程。 

1. init.c清單 

#include < unistd.h > 
#include < signal.h > 
#include < sys/param.h > 
#include < sys/types.h > 
#include < sys/stat.h > 
void init_daemon(void) 

  int pid; 
  int i; 
  if(pid=fork()) 
      exit(0);//是父進程,結束父進程 
  else if(pid< 0) 
      exit(1);//fork失敗,退出 
  

//第一子進程,在後臺繼續執行 
setsid();  //第一子進程成爲新的會話組長和進程組長 
           //並與控制終端分離 
if(pid=fork()) 
    exit(0);    //是第一子進程,結束第一子進程 
else if(pid< 0) 
    exit(1);    //fork失敗,退出 

//第二子進程,繼續 
//第二子進程不再是會話組長 

for(i=0;i< NOFILE;++i)//關閉打開的文件描述符 
close(i); 

chdir("/tmp");//改變工作目錄到/tmp 

umask(0);//重設文件創建掩模 

return; 

}

2. test.c清單 
#include < stdio.h > 
#include < time.h > 

void init_daemon(void);//守護進程初始化函數 

main() 

  FILE *fp; 
  time_t t; 
  init_daemon();//初始化爲Daemon 

while(1)//每隔一分鐘向test.log報告運行狀態 

  sleep(60);//睡眠一分鐘 
  if((fp=fopen("test.log","a")) >=0) 
  { 
    t=time(0); 
    fprintf(fp,"Im here at %sn",asctime(localtime(&t)) ); 
    fclose(fp); 
  } 


編譯:gcc -g -o test init.c test.c 
執行:./test 

查看進程:ps -ef 
從輸出可以發現test守護進程的各種特性滿足上面的要求。 

3.4.進程間通訊處理 

3.4.1共享內存需要鎖機制 

由於共享內存在設計時沒有處理鎖機制,故當有多個進程在訪問共享 

內存時就會產生問題.:一個進程修改一個共享內存單元,另一個進程在 

讀該共享內存單元時可能有第三個進程立即修改該單元,從而會影響程序 

的正確性.同時還有分時系統對各進程是分時間片處理的,可能會引起不 

同的正確性問題.按操作系統的運作方式,則有讀鎖和寫鎖來保證數據的 

一致性.所以沒有鎖機制的共享內存,必須和信號量一起使用,才能保證共 

享內存的正確操作

3.4.2消息隊列需要關鍵值 

消息隊列的操作在進程取得消息隊列的訪問權限後就必須通過關鍵 

值來讀消息隊列中的相同關鍵值的消息,寫消息時帶入消息關鍵值.這樣 

可以通過不同的關鍵值區分不同的交易,使得在同一個消息隊列可以供多 

種消息同時使用而不衝突.若讀消息隊列使用關鍵值0則讀取消息隊列中 

第一個消息,不論其關鍵值如何

3.4.3信號需要信號處理函數設置和再設置 

在用戶進程需要對某個中斷做自己定義的處理時,可以自己定義中斷 

處理函數,並設置中斷處理函數與該中斷相關聯.這樣,用戶進程在收到該 

中斷後,即調用用戶定義的函數,處理完成後用戶進程從被中斷處繼續運 

(若用戶定義的中斷函數沒有長跳函數或退出運行等會改變運行指令地 

址的系統調用).在中斷信號被處理後,該中斷的處理函數會恢復成上次缺 

省處理函數而不是保持用戶定義函數,故在用戶定義的中斷處理函數中一 

般都再定義該中斷和函數自己的關聯

3.4.4IPC的權限設置 

在消息隊列,共享內存和信號量的訪問時有用戶訪問權限設置,類同 

於文件的訪問權限的設置如(777表示rwxrwxrwx),用命令ipcs即可看到在 

系統中生成的消息隊列,共享內存和信號量的訪問權限.其意義也類似於 

文件訪問權限.只是執行位無效

在有名管道和文件方式共享內存中以系統文件的方式定義了用戶的 

訪問權限.用命令ls -l可以看到它們以系統文件方式存在並具有訪問權 

限值,並可以看到有名管道的文件類型爲p,文件方式共享內存的文件類型 

s. 

3.4.5信號中斷對系統調用一級有效 

系統在設計系統調用時就考慮了中斷處理問題.當進程運行到一個系 

統調用時發生了中斷,則進程進入該中斷處理,處理完成後,進程會跳過該 

系統調用而進入下一條程序指令

應該注意的是中斷髮生在系統調用一級而不是子程序或函數一級.比 

如一個程序在一個子程序被調用前設置了超時中斷,並在子程序中收到超 

時中斷,系統在處理完超時中斷後接着處理該子程序被中斷的系統調用之 

後的指令,而不是從調用該子程序名指令的後一條指令繼續處理

3.4.6各種IPC方式的特點 

消息隊列

通過消息隊列key值定義和生成消息隊列

任何進程只要有訪問權限並知道key即可訪問消息隊列

消息隊列爲內存塊方式數據段

消息隊列中的消息元素長度可爲系統參數限制內的任何長度

消息元素由消息類型分類,其訪問方式爲按類型訪問

在一次讀寫操作前都必須取得消息隊列標識符,即訪問權.訪問後即 

脫離訪問關係

消息隊列中的某條消息被讀後即從隊列中刪除

消息隊列的訪問具備鎖機制處理,即一個進程在訪問時另一個進程 

不能訪問

操作時要注意系統資源和效率

在權限允許時,消息隊列的信息傳遞是雙向的

共享內存 

通過共享內存key值定義和生成共享內存

任何進程只要有訪問權限並知道key即可訪問共享內存

共享內存爲內存塊方式的數據段

共享內存中的數據長度可爲系統參數限制內的任何長度

共享內存的訪問同數組的訪問方式相同

在取得共享內存標識符將共享內存與進程數據段聯接後即可開始對 

之進行讀寫操作,在所有操作完成之後再做共享內存和進程數據 

段脫離操作,才完成全部共享內存訪問過程

共享內存中的數據不會因數據被進程讀取後消失

共享內存的訪問不具備鎖機制處理,即多個進程可能同時訪問同一 

個共享內存的同一個數據單元

共享內存的使用最好和信號量一起操作,以具備鎖機制,保證數據的 

一致

在權限允許時,共享內存的信息傳遞是雙向的

信號量 

用於生成鎖機制,避免發生數據不一致

沒有其他的數據信息

不需要有父子關係或兄弟關係

信號 

信號由系統進行定義

信號的發送只要有權限即可進行

信號是一個事件發生的信息標誌,不帶有其它信息

信號不具備數據塊

信號的處理可由用戶自己定義

信號可能由用戶進程,操作系統(軟件或硬件原因)等發出

有一些信號是不可被屏蔽的

信號中斷的是系統調用級的函數

信號的信息傳遞是單向的

管道 

做爲系統的特殊設備文件,可以是內存方式的,也可以是外存方式的

管道的傳輸一般是單向的,即一個管道一向,若兩個進程要做雙向傳 

輸則需要2個管道.管道生成時即有兩端,一端爲讀,一端爲寫,兩個 

進程要協調好,一個進程從讀方讀,另一個進程向寫方寫

管道的讀寫使用流設備的讀寫函數,:read(),write. 

管道的傳輸方式爲FIFO,流方式的.不象消息隊列可以按類型讀取

有名管道 

一般爲系統特殊文件方式,使用的進程之間不一定要有父子關係 

或兄弟關係

無名管道 

一般爲內存方式,使用的進程之間一定要有父子關係或兄弟關係

文件 

文件是最簡單的進程間通訊方式,使用外部存貯器爲中介

操作麻煩,定位困難

保密程度低

容易出現數據不一致問題

佔用硬盤空間

只要有權限並知道文件名,任何進程都可對之操作

特殊處理 

爲避免出現保密問題,在打開文件,取得文件描述符後,調用 

unlink()將硬盤上的文件路徑名刪除,則硬盤上就沒有文件拷貝 

.但在進程中該文件描述符是打開的,由該進程生成的子進程中 

該文件描述符也是打開的,就可以利用系統提供的文件緩衝區做 

進程間通訊,代價是進程間必須有父子關係或兄弟關係

環境變量 

信息的傳送一般是單向的,即由父進程向子進程傳送

保密性較好

雙方必須約定環境變量名

只佔用本進程和子進程的環境變量區

共享數據段 

操作比較複雜

佔用硬盤空間,生成系統特殊文件

其他性質與共享內存相類似

流 

文件描述符的操作方式

進程間不一定要有父子關係或兄弟關係

雙向傳送信息

進程各自生成socket,bind()聯接

其他性質與管道相類似

流編程爲TCP/IP網絡編程範圍,在本文中暫不闡述

傳遞參數 

信息的傳送一般是單向的即由父進程向子進程傳送

保密性較差,用進程列表即可顯示出來

雙方必須約定參數位置

只佔用子進程的參數區.  

.進程通信與同步

4.1. 概述 

  Linux內核主要由五個子系統組成:進程調度,內存管理,虛擬文件系統,網絡接口,進程間通信。

    Linux提供了多種進程間的通信機制,其中,信號和管道是最基本的兩種,也提供 System V的進程間通信機制,包括消息隊列、信號燈及共享內存等。

 System V IPC對象權限包含在ipc_perm數據結構中,位於include/linux/ipc.h。

System V的消息是在ipc/msg.c中實現、共享內存在ipc/shm.c中實現、信號燈在ipc/sem.c中,管道在/ipc/pipe.c中實現。  

與Windows相比,在進程間通信機制上,Linux提供了標準的UNIX IPC機制,接近於IPC原語,比較底層,提供了最大的靈活性,也可以在此基礎上建立更加複雜的高級IPC機制;Windows則在基本IPC機的基礎上,提供了許多直接面嚮應用程序的高級IPC機制。

linux下進程間通信的幾種主要手段簡介:

1. 管道(Pipe)及有名管道(named pipe):管道可用於具有親緣關係進程間的通信,有名管道克服了管道沒有名字的限制,因此,除具有管道所具有的功能外,它還允許無親緣關係進程間的通信; 

2. 信號(Signal):信號是比較複雜的通信方式,用於通知接受進程有某種事件發生,除了用於進程間通信外,進程還可以發送信號給進程本身;linux除了支持Unix早期信號語義函數sigal外,還支持語義符合Posix.1標準的信號函數sigaction(實際上,該函數是基於BSD的,BSD爲了實現可靠信號機制,又能夠統一對外接口,用sigaction函數重新實現了signal函數); 

3. 消息(Message)隊列:消息隊列是消息的鏈接表,包括Posix消息隊列system V消息隊列。有足夠權限的進程可以向隊列中添加消息,被賦予讀權限的進程則可以讀走隊列中的消息。消息隊列克服了信號承載信息量少,管道只能承載無格式字節流以及緩衝區大小受限等缺點。 

4. 共享內存:使得多個進程可以訪問同一塊內存空間,是單機最快的可用IPC形式。是針對其他通信機制運行效率較低而設計的。往往與其它通信機制,如信號量結合使用,來達到進程間的同步及互斥。 

5. 信號燈(semaphore):主要作爲進程間以及同一進程不同線程之間的同步手段。 

6. 套接口(Socket)和UINX域套接字:更爲一般的進程間通信機制,可用於不同機器之間的進程間通信。起初是由Unix系統的BSD分支開發出來的,但現在一般可以移植到其它類Unix系統上:Linux和System V的變種都支持套接字。

進程同步就是要協調好2個以上的進程,使之以安排好地次序依次執行。有時候,父進程要求子進程的運算結果進行下一步的運算,或者子進程的功能是爲父進程提供了下一步執行的先決條件(如:子進程建立文件,而父進程寫入數據),此時父進程就必須在某一個位置停下來,等待子進程運行結束,而如果父進程不等待而直接執行下去的話,會出現極大的混亂。

解決進程同步問題可用信號、管道、套接字、共享內存等多種方法。簡單情況下也可以用wait系統調用簡單的予以解決。請看下面這段程序:

#include <sys/types.h>

#include <sys/wait.h>

main()

{

pid_t pc, pr;

int status;

pc=fork();

if(pc<0)

printf("Error occured on forking.\n");

else if(pc==0)

{

/* 子進程的工作 */

exit(0);

}

else

{

/* 父進程的工作 */

pr=wait(&status);

/* 利用子進程的結果 */

}

}

當fork調用成功後,父子進程各做各的事情,但當父進程的工作告一段落,需要用到子進程的結果時,它就停下來調用wait,一直等到子進程運行結束,然後利用子進程的結果繼續執行。

4.2. 管道

系統調用pipe ( ) 創建管道。管道是進程間通信最古老的方式,它包括無名管道和有名管道兩種,前者用於父進程和子進程間的通信,後者用於運行於同一臺機器上的任意兩個進程間的通信。

(無名)管道特點:

· 管道是一個單向信道,數據只能向一個方向流動;需要雙方通信時,需要建立起兩個管道; 

· 只能用於父子進程或者兄弟進程之間(具有親緣關係的進程); 

· 單獨構成一種獨立的文件系統:管道對於管道兩端的進程而言,就是一個文件,但它不是普通的文件,它不屬於某種文件系統,而是自立門戶,單獨構成一種文件系統,並且只存在與內存中。 

· 數據的讀出和寫入:一個進程向管道中寫的內容被管道另一端的進程讀出。寫入的內容每次都添加在管道緩衝區的末尾,且每次都是從緩衝區的頭部讀出數據。(先進先出)

Ex:

#include <unistd.h>

int pipe(int fileds[2]); 

fileds[0] 用於讀,fileds[1] 用於寫。 讀/寫 —— 0/1

#include <stdio.h>

#include <stdlib.h>

#include <sys/types.h>

#include <unistd.h>

int main()

{

  int pfds[2];

  char buf[30];

  pipe(pfds);

  if(fork()==0)

  {

close(pfds[0]);

sleep(2);

write(pfds[1],"abcdef",7);

exit(0);

  }

  else

  {

   close(pfds[1]);

read(pfds[0],buf,30);

wait(NULL);

exit(0);

  }

}

note:

1.向管道中寫入數據時,linux將不保證寫入的原子性,管道緩衝區一有空閒區域,寫進程就會試圖向管道寫入數據。如果讀進程不讀走管道緩衝區中的數據,那麼寫操作將一直阻塞。 

2. 寫端對讀端存在依賴性。只有在管道的讀端存在時,向管道中寫入數據纔有意義。否則,向管道中寫入數據的進程將收到內核傳來的SIGPIPE信號,應用程序可以處理該信號,也可以忽略(默認動作則是應用程序終止)。 

3.嚴格遵循先進先出(first in first out),不支持諸如lseek()等文件定位操作。

有名管道(named pipe或FIFO), 與管道不同之處在於,它與一個路徑名關聯,以FIFO的文件形式存在於文件系統中。這樣,即使與FIFO的創建進程不存在親緣關係的進程,只要可以訪問該路徑,就能夠彼此通過FIFO相互通信。

系統調用mkfifo ( ) 創建有名管道

int mkfifo(const char * pathname, mode_t mode)

該函數的第一個參數是一個普通的路徑名,也就是創建後FIFO的名字。第二個參數與打開普通文件的open()函數中的mode 參數相同。如果mkfifo的第一個參數是一個已經存在的路徑名時,會返回EEXIST錯誤,所以一般典型的調用代碼首先會檢查是否返回該錯誤,如果確實返回該錯誤,那麼只要調用打開FIFO的函數就可以了。生成了有名管道後,就可以使用一般的文件I/O函數如open、close、read、write等來對它進行操作。

Ex:

寫:

#include <sys/types.h>

#include <sys/stat.h>

#include <errno.h>

#include <fcntl.h>

#define FIFO_SERVER "/tmp/fifoserver"

main(int argc,char** argv)

//參數爲即將寫入的字節數

{

int fd;

char w_buf[4096*2];

int real_wnum;

memset(w_buf,0,4096*2);

if((mkfifo(FIFO_SERVER,O_CREAT|O_EXCL)<0)&&(errno!=EEXIST))

printf("cannot create fifoserver\n");

if(fd==-1)

if(errno==ENXIO)

printf("open error; no reading process\n");

   //設置非阻塞標誌

fd=open(FIFO_SERVER,O_WRONLY|O_NONBLOCK,0);

       //設置阻塞標誌 fd=open(FIFO_SERVER,O_WRONLY,0);

real_wnum=write(fd,w_buf,2048);

if(real_wnum==-1)

{

if(errno==EAGAIN)

printf("write to fifo error; try later\n");

}

else 

printf("real write num is %d\n",real_wnum);

}

讀: 

#include <sys/types.h>

#include <sys/stat.h>

#include <errno.h>

#include <fcntl.h>

#define FIFO_SERVER "/tmp/fifoserver"

main(int argc,char** argv)

{

char r_buf[4096*2];

int  fd;

int  r_size;

int  ret_size;

r_size=atoi(argv[1]);

printf("requred real read bytes %d\n",r_size);

memset(r_buf,0,sizeof(r_buf));

fd=open(FIFO_SERVER,O_RDONLY|O_NONBLOCK,0);

//fd=open(FIFO_SERVER,O_RDONLY,0);

//在此處可以把讀程序編譯成兩個不同版本:阻塞版本及非阻塞版本

if(fd==-1)

{

printf("open %s for read error\n");

exit();

}

while(1)

{

memset(r_buf,0,sizeof(r_buf));

ret_size=read(fd,r_buf,r_size);

if(ret_size==-1)

if(errno==EAGAIN)

printf("no data avlaible\n");

printf("real read bytes %d\n",ret_size);

   sleep(1);

}

pause();

unlink(FIFO_SERVER);

}

note:

  管道是最古老的方式,具有通用性。但Pipe_buf有限,通常幾百到幾千字節。

4.3. 消息隊列

系統 V IPC引入了三種進程間通信機制:消息、信號燈、共享內存。內核爲每種機制維護一個表,在表中存儲所有相關實例,每個表項由一個關鍵字(用戶選擇的名字)來標誌。

將一個路徑名和項目標識 轉換爲一個關鍵字:

#include <sys/types.h>

#include <sys/ipc.h>

key_t ftok(char* pathname,char proj);

Linux系統維護着一個msgque消息隊列鏈表,其中每個元素指向一個描述消息隊列的msqid_ds結構。當創建新的消息隊列時,系統將從系統內存中分配一個msqid_ds結構,同時將其插入到數組中。 

每個msqid_ds結構包含一個ipc_perm結構和指向已經進入此隊列消息的指針,以及有關隊列修改時間信息,如上次系統向隊列中寫入的時間等。

struct kern_ipc_perm

{   

     key_t   key;    //該鍵值則唯一對應一個消息隊列

   uid_t   uid;

     gid_t   gid;

uid_t   cuid;

gid_t   cgid;

mode_t  mode;

unsigned long seq;

}

msqid_ds包含兩個等待隊列:一個爲隊列寫入進程使用而另一個由隊列讀取進程使用。  

每次進程試圖向寫入隊列寫入消息時,系統將把其有效用戶和組標誌符與此隊列的ipc_perm結構中的模式進行比較。如果允許寫入操作,則把此消息從此進程的地址空間拷貝到msg數據結構中,並放置到此消息隊列尾部。由於 Linux嚴格限制可寫入消息的個數和長度,隊列中可能容納不下這個消息。此時,此寫入進程將被添加到這個消息隊列的等待隊列中,同時調用調度管理器選擇新進程運行。當有消息從此隊列中釋放時,該進程將被喚醒。  

從隊列中讀的過程與之類似。進程對這個寫入隊列的訪問權限將被再次檢驗。讀取進程將選擇隊列中第一個消息(不管是什麼類型)或者第一個某特定類型的消息。如果沒有消息可以滿足此要求,讀取進程將被添加 到消息隊列的讀取等待隊列中,然後系統運行調度管理器。當有新消息寫入隊列時,進程將被喚醒繼續執行。 

    #include <sys/types.h>

#include <sys/ipc.h>

#include <sys/msg.h>

int msgget(key_t key, int msgflg);

int msgsnd(int msqid,  struct msgbuf* msgp,  int msgsz,  int msgflg);

int msgrcv(int msqid,  struct msgbuf* msgp,  int msgsz,  long msgtyp,  int msgflg);

int msgctl(int msqid,  int cmd,  struct msqid_ds* buf);

struct mymsgbuf 


long mtype; //消息類型,正整數 
char mtext[80]; 

}; 
void send_message(int qid, struct mymsgbuf *qbuf, long type, char *text); 
void read_message(int qid, struct mymsgbuf *qbuf, long type); 
void remove_queue(int qid); 
 
int main(int argc, char *argv[]) 

  key_t key; 
  int msgqueue_id; 
  struct mymsgbuf qbuf; 

/* Create unique key via call to ftok() */ 
  key = ftok("/home/beej/somefile", 'w');  /* key = 123456  
  

/* Open the queue - create if necessary */ 
if((msgqueue_id = msgget(key, IPC_CREAT | 0666)) = = -1)  //創建一個消息隊列 

{                                                  //一般在服務器創建

perror("msgget");                                //客戶端僅輸入權限

exit(1); 

}

。。。。。。。。

return(0);  
   } 

void send_message(int qid, struct mymsgbuf *qbuf, long type, char *text) 

  qbuf->mtype = type; 
  strcpy(qbuf->mtext, text); 
  if((msgsnd(qid, (struct msgbuf *)qbuf, strlen(qbuf->mtext)+1, 0)) = =-1) 
  { 
    perror("msgsnd"); 
    exit(1); 
  } 


void read_message(int qid, struct mymsgbuf *qbuf, long type) 

  qbuf->mtype = type; 
  msgrcv(qid, (struct msgbuf *)qbuf, 80, type, 0); 


void remove_queue(int qid) 

  msgctl(qid, IPC_RMID, 0); 

Note: 已逐漸淘汰。

4.4. 信號燈(一個計數器)

信號燈主要提供對進程間共享資源訪問控制機制。相當於內存中的標誌,進程可以根據它判定是否能夠訪問某些共享資源,同時,進程也可以修改該標誌。除了用於訪問控制外,還可用於進程同步。信號燈有以下兩種類型:

二值信號燈:最簡單的信號燈形式,信號燈的值只能取0或1,類似於互斥鎖。
計算信號燈:信號燈的值可以取任意非負值(當然受內核本身的約束)。 

對信號燈的操作有下面三種類型:

打開或創建信號燈、 信號燈值操作、 獲得或設置信號燈屬性

信號燈最簡單的形式是某個可以被多個進程檢驗和設置(test&set)的內存單元。這個檢驗與設置操作對每個進程而言是不可中斷或者說是一個原子性操作;一旦啓動誰也終止不了。檢驗與設置操作的結果是信號燈當前值加1, 這個值可以是正數也可以是負數。根據這個操作的結果,進程可能可以一直睡眠到此信號燈的值被另一個進程更改爲止。信號燈可用來實現臨界區(critical region):某一時刻在此區域內的代碼只能被一個進程執行。  

如果你有多個協作進程從一個數據文件中讀取與寫入記錄。有時你可能需要這些文件訪問遵循嚴格的訪問次序。 那麼可在文件操作代碼上使用一個初始值爲1的信號燈,它帶有兩個信號燈操作,一個檢驗並對信號燈值減1,而另一個檢驗並加1。第一個訪問文件的進程將試圖將信號燈值減1,如果獲得成功則信號燈值變成了 0。此進程於是開始使用這個數據文件,但是此時如果另一進程也想將信號燈值減1,則信號燈值將爲-1,這次操作將會失敗。它將掛起執行直到第一個進程完成對此數據文件的使用。此時這個等待進程將被喚醒,這次它對信號燈的操作將成功。

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

int semget(key_t key, int nsems, int semflg);  //

int semctl(int semid, int semnum, int cmd, union semnu arg);

int semop(int semid, struct sembuf* sops, unsigned nsops);//數組、個數

struct sembuf

{

  unsigned short    sem_num; //序號

  short             sem_op; //操作,+1-1

  short             sem_flg; //IPC_NOWAIT, SEM_UNDO

};

Field          Description

   sem_num        semaphore number

   sem_op          semaphore operation

   sem_flg          semaphore flags

union semun{  //一些參數

  int val;

  struct semid_ds *buf;

  ushort *array;

}

刪除信號燈:

  union semun dummy;

  int semid;

…..

semctl(semid,0,IPC_RMID,dummy);

Ex:

#include <string.h>

#include <sys/socket.h>

#include <sys/type.h>

#include <netinet/in.h>

#include <sys/ipc.h>

#include <sys/sem.h>

#include <stdio.h>

#include <signal.h>

#define  SEM_NAME "/tmp/sem_name.tmp"

void sigint_handler(int);

int semid;

unsigned int count;

struct sembuf psembuf,vsembuf;

int main(int argc,char*argv[])

{

int i,first,second;

key_t key;

short initarray[2],outarray[2];

  

key=ftok(SEM_NAME,'a');

if(argc==1)

{

signal(SIGINT,sigint_handler);

semid=semget(key,2,0777|IPC_CREAT);

initarray[0]=initarray[1]=1;

semctl(semid,2,SETALL,initarray);//1,1

semctl(semid,2,GETALL,outarray);

printf("sem init vals %d %d\n",outarray[0],outarray[1]);

pause();

}

else if(argv[1][0]==0)

{

first=0;

second=1;

}

else

{

first=1;

second=0;

}

semid=semget(key,2,0777);

psembuf.sem_op=-1;

psembuf.sem_flg=SEM_UNDO;

vsembuf.sem_op=1;

vsembuf.sem_flg=SEM_UNDO;

  

for(count=0;;count++)

{

psembuf.sem_num=first;

semop(semid,&psembuf,1); //1—操作個數

psembuf.sem_num=second;

semop(semid,&psembuf,1);

  

semctl(semid,2,GETALL,outarray);

printf("proc %d count %d sem value %d %d\n",getpid(),

count,outarray[0],outarray[1]);

  

vsembuf.sem_num=second;

semop(semid,&vsembuf,1);

vsembuf.sem_num=first;

semop(semid,&vsembuf,1);

}

 

}

void sigint_handler(int sig)

{

semctl(semid,2,IPC_RMID,0);

exit(0);

}

4.5. 共享內存

共享內存允許一個或多個進程通過同時出現在它們虛擬地址空間中的內存來通訊。此虛擬內存的頁面出現在每個共享進程頁表中。但此頁面並不一定位於所有共享進程虛擬內存的相同位置。和其它系統V IPC對象的使用方法一樣,對共享內存區域的訪問是通過鍵和訪問權限檢驗來控制的。一旦內存被共享,則再不會檢驗進程對對象的使用方式。它依賴於其它機制,如系統V信號燈,來同步對共享內存的訪問。 共享內存是最有用的進程間通信方式,也是最快的IPC形式。由於多個進程共享同一塊內存區域,必然需要某種同步機制,互斥鎖和信號量都可以。

採用共享內存通信的一個顯而易見的好處是效率高,因爲進程可以直接讀寫內存,而不需要任何數據的拷貝。

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/shm.h>

int shmget(key_t key, int size, int shmflg);

char* shmat(int shmid, char* shmaddr, int shmflg);  //將共享區附接於shmaddr

int shmdt(char* shmaddr); // //將共享區斷開

int shmctl(int semid, int cmd, struct shmid_ds* buf);

在/proc/sys/kernel/目錄下,記錄着系統V共享內存的限制,如一個共享內存區的最大字節數shmmax,系統範圍內最大共享內存區標識符數shmmni等。

Ex:

寫:

#include <sys/ipc.h>

#include <sys/shm.h>

#include <sys/types.h>

#include <unistd.h>

typedef struct{

char name[4];

int age;

} people;

main(int argc, char** argv)

{

int shm_id,i;

key_t key;

char temp;

people *p_map;

char* name = "/dev/shm/myshm2";

key = ftok(name,0);

if(key==-1)

perror("ftok error");

shm_id=shmget(key,4096,0777|IPC_CREAT);

if(shm_id==-1)

{

perror("shmget error");

return;

}

p_map=(people*)shmat(shm_id,0,0);

temp='a';

for(i = 0;i<10;i++)

{

temp+=1;

memcpy((*(p_map+i)).name,&temp,1);

(*(p_map+i)).age=20+i;

}

if(shmdt(p_map)==-1)

perror(" detach error ");

}

讀:

#include <sys/ipc.h>

#include <sys/shm.h>

#include <sys/types.h>

#include <unistd.h>

typedef struct{

char name[4];

int age;

} people;

main(int argc, char** argv)

{

int shm_id,i;

key_t key;

people *p_map;

char* name = "/dev/shm/myshm2";

key = ftok(name,0);

if(key == -1)

perror("ftok error");

shm_id = shmget(key,4096,0777);

if(shm_id == -1)

{

perror("shmget error");

return;

}

p_map = (people*)shmat(shm_id,0,0);

for(i = 0;i<10;i++)

{

  printf( "name:%s\n",(*(p_map+i)).name );

  printf( "age %d\n",(*(p_map+i)).age );

}

if(shmdt(p_map) == -1)

perror(" detach error ");

}

4.6. UNIX域套接字

    Unix域套接字,只能與同一臺機器上的套接字相連,是面向連接的;每一個到套接字的新連接都產生一個新的通信管道。Unix域套接字經常被用來代替命名管道實現很多重要服務中的IPC;也可以用socketpair()來得到非命名Unix域套接字,與非命名管道類似。(管套)

1.命名Unix域套接字

int socket(AF_UNIX, SOCK_STREAM, 0)

int socket(AF_UNIX, SOCK_DGRAM, 0)

struct sockaddr_un

{

  short int sun_family;

  char sun_path[104];

}

需要有獨有的套接字地址。然後執行bind( ), listen( ), accept( ), 

note:

  i. 客戶機必需擁有打開文件的權限;

  ii. 客戶機調用connect( )時,若傾聽套接字的隊列已滿,立即返回ECONNREFUSED.

2.非命名Unix域套接字

int socketpair(AF_UNIX, SOCK_STREAM, 0, int sockfd[2])

int socketpair(AF_UNIX, SOCK_DGRAM, 0,int sockfd[2])

創建後,父、子進程分別關掉一個多餘的套接字,然後進行全雙工通信。

int main()

{

  int sv[2];

  char buf;

  if(socketpair(AF_UNIX,SOCK_STREAM,0,sv)<0)

   exit(1);

  

  if(fork()==0)

  {

close(sv[0]);   

read(sv[1],&buf,1);

buf=toupper(buf);

write(sv[1],&buf,1);

exit(0);

  }

  else

  {

close(sv[1]);

sleep(1);

write(sv[0],"b",1);

read(sv[0],&buf,1);

exit(0);

  }

}

.線程

5.1. 概述 

    

5.2. C++線程同步 

5.2.1概述  

   現在流行的進程線程同步互斥的控制機制,其實是由最原始最基本的4種方法實現的。由這4種方法組合優化就有了.NetJava下靈活多變的,編程簡便的線程進程控制手段。  

  這4種方法具體定義如下 在《操作系統教程》ISBN 7-5053-6193-7 一書中可以找到更加詳細的解釋  

  1臨界區:通過對多線程的串行化來訪問公共資源或一段代碼,速度快,適合控制數據訪問。  

  2互斥量:爲協調共同對一個共享資源的單獨訪問而設計的。  

  3信號量:爲控制一個具有有限數量用戶資源而設計。  

4事 件:用來通知線程有一些事件已發生,從而啓動後繼任務的開始。

5.2.2臨界區(Critical Section)

5.2.3互斥量

5.2.4信號量

5.2.5事件

5.3. 概述 

 臨界區(Critical Section)  

  保證在某一時刻只有一個線程能訪問數據的簡便辦法。在任意時刻只允許一個線程對共享資源進行訪問。如果有多個線程試圖同時訪問臨界區,那麼在有一個線程進入後其他所有試圖訪問此臨界區的線程將被掛起,並一直持續到進入臨界區的線程離開。臨界區在被釋放後,其他線程可以繼續搶佔,並以此達到用原子方式操作共享資源的目的。  

  臨界區包含兩個操作原語: 

 EnterCriticalSection() 進入臨界區

 LeaveCriticalSection() 離開臨界區  

  EnterCriticalSection()語句執行後代碼將進入臨界區以後無論發生什麼,必須確保與之匹配的LeaveCriticalSection()都能夠被執行到。否則臨界區保護的共享資源將永遠不會被釋放。雖然臨界區同步速度很快,但卻只能用來同步本進程內的線程,而不可用來同步多個進程中的線程。  

  MFC提供了很多功能完備的類,我用MFC實現了臨界區。MFC爲臨界區提供有一個CCriticalSection類,使用該類進行線程同步處理是非常簡單的。只需在線程函數中用CCriticalSection類成員函數Lock()和UnLock()標定出被保護代碼片段即可。Lock()後代碼用到的資源自動被視爲臨界區內的資源被保護。UnLock後別的線程才能訪問這些資源。 

   

  //CriticalSection 

  CCriticalSection global_CriticalSection; 

   

  // 共享資源 

  char global_Array[256]; 

   

  //初始化共享資源 

  void InitializeArray() 

  { 

   for(int i = 0;i<256;i++) 

   { 

   global_Array[i]=I; 

   } 

  } 

   

  //寫線程 

  UINT Global_ThreadWrite(LPVOID pParam) 

  { 

   CEdit *ptr=(CEdit *)pParam; 

   ptr->SetWindowText(""); 

   //進入臨界區 

  global_CriticalSection.Lock(); 

   for(int i = 0;i<256;i++) 

   { 

   global_Array[i]=W; 

   ptr->SetWindowText(global_Array); 

   Sleep(10); 

   }  

 //離開臨界區 

   global_CriticalSection.Unlock(); 

   return 0; 

  } 

   

  //刪除線程 

  UINT Global_ThreadDelete(LPVOID pParam) 

  { 

   CEdit *ptr=(CEdit *)pParam; 

   ptr->SetWindowText(""); 

   //進入臨界區 

   global_CriticalSection.Lock(); 

   for(int i = 0;i<256;i++) 

   { 

   global_Array[i]=D; 

   ptr->SetWindowText(global_Array); 

   Sleep(10); 

   } 

   

 //離開臨界區 

   global_CriticalSection.Unlock(); 

   return 0; 

  } 

   

  //創建線程並啓動線程 

  void CCriticalSectionsDlg::OnBnClickedButtonLock() 

  { 

 

 

  

 作者: 61.51.111.*2006-8-24 14:09   回覆此發言    

 

--------------------------------------------------------------------------------

 

四種進程或線程同步互斥的控制方法  

    //Start the first Thread 

   CWinThread *ptrWrite = AfxBeginThread(Global_ThreadWrite, 

   &m_Write, 

   THREAD_PRIORITY_NORMAL, 

   0, 

   CREATE_SUSPENDED); 

   ptrWrite->ResumeThread(); 

     

   //Start the second Thread 

   CWinThread *ptrDelete = AfxBeginThread(Global_ThreadDelete, 

   &m_Delete, 

   THREAD_PRIORITY_NORMAL, 

   0, 

   CREATE_SUSPENDED); 

   ptrDelete->ResumeThread(); 

  }  

    

  在測試程序中,Lock UnLock兩個按鈕分別實現,在有臨界區保護共享資源的執行狀態,和沒有臨界區保護共享資源的執行狀態。  

  程序運行結果 

     

  

  互斥量(Mutex)  

    

  互斥量跟臨界區很相似,只有擁有互斥對象的線程才具有訪問資源的權限,由於互斥對象只有一個,因此就決定了任何情況下此共享資源都不會同時被多個線程所訪問。當前佔據資源的線程在任務處理完後應將擁有的互斥對象交出,以便其他線程在獲得後得以訪問資源。互斥量比臨界區複雜。 因爲使用互斥不僅僅能夠在同一應用程序不同線程中實現資源的安全共享,而且可以在不同應用程序的線程之間實現對資源的安全共享。

   

    互斥量包含的幾個操作原語: 

    CreateMutex() 創建一個互斥量 

    OpenMutex() 打開一個互斥量 

    ReleaseMutex() 釋放互斥量 

    WaitForMultipleObjects() 等待互斥量對象  

    

  同樣MFC爲互斥量提供有一個CMutex類。使用CMutex類實現互斥量操作非常簡單,但是要特別注意對CMutex的構造函數的調用  

   

  CMutex( BOOL bInitiallyOwn = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL)  

  不用的參數不能亂填,亂填會出現一些意想不到的運行結果。 

   

  //創建互斥量 

  CMutex global_Mutex(0,0,0); 

   

  // 共享資源 

  char global_Array[256]; 

   

  void InitializeArray() 

  { 

   for(int i = 0;i<256;i++) 

   { 

   global_Array[i]=I; 

   } 

  } 

  UINT Global_ThreadWrite(LPVOID pParam) 

  { 

   CEdit *ptr=(CEdit *)pParam; 

   ptr->SetWindowText(""); 

   global_Mutex.Lock(); 

   for(int i = 0;i<256;i++) 

   { 

   global_Array[i]=W; 

   ptr->SetWindowText(global_Array); 

   Sleep(10); 

   } 

   global_Mutex.Unlock(); 

   return 0; 

  } 

   

  UINT Global_ThreadDelete(LPVOID pParam) 

  { 

   CEdit *ptr=(CEdit *)pParam; 

   ptr->SetWindowText(""); 

   global_Mutex.Lock(); 

   for(int i = 0;i<256;i++) 

   { 

   global_Array[i]=D; 

   ptr->SetWindowText(global_Array); 

   Sleep(10); 

   } 

   global_Mutex.Unlock(); 

   return 0; 

  }  

  同樣在測試程序中,Lock UnLock兩個按鈕分別實現,在有互斥量保護共享資源的執行狀態,和沒有互斥量保護共享資源的執行狀態。  

  程序運行結果 

     

  

    

  信號量(Semaphores)  

  信號量對象對線程的同步方式與前面幾種方法不同,信號允許多個線程同時使用共享資源,這與操作系統中的PV操作相同。它指出了同時訪問共享資源的線程最大數目。它允許多個線程在同一時刻訪問同一資源,但是需要限制在同一時刻訪問此資源的最大線程數目。在用CreateSemaphore()創建信號量時即要同時指出允許的最大資源計數和當前可用資源計數。一般是將當前可用資源計數設置爲最大資源計數,每增加一個線程對共享資源的訪問,當前可用資源計數就會減1,只要當前可用資源計數是大於0的,就可以發出信號量信號。但是當前可用計數減小到0時則說明當前佔用資源的線程數已經達到了所允許的最大數目,不能在允許其他線程的進入,此時的信號量信號將無法發出。線程在處理完共享資源後,應在離開的同時通過ReleaseSemaphore()函數將當前可用資源計數加1。在任何時候當前可用資源計數決不可能大於最大資源計數。  

 

 

  

 作者: 61.51.111.*2006-8-24 14:09   回覆此發言    

 

--------------------------------------------------------------------------------

 

四種進程或線程同步互斥的控制方法  

 

  PV操作及信號量的概念都是由荷蘭科學家E.W.Dijkstra提出的。信號量S是一個整數,S大於等於零時代表可供併發進程使用的資源實體數,但S小於零時則表示正在等待使用共享資源的進程數。  

   P操作申請資源: 

    (1S1; 

    (2)若S1後仍大於等於零,則進程繼續執行; 

    (3)若S1後小於零,則該進程被阻塞後進入與該信號相對應的隊列中,然後轉入進程調度。  

   

  V操作 釋放資源: 

    (1S1; 

    (2)若相加結果大於零,則進程繼續執行; 

    (3)若相加結果小於等於零,則從該信號的等待隊列中喚醒一個等待進程,然後再返回原進程繼續執行或轉入進程調度。 

   

    信號量包含的幾個操作原語: 

    CreateSemaphore() 創建一個信號量 

    OpenSemaphore() 打開一個信號量 

    ReleaseSemaphore() 釋放信號量 

    WaitForSingleObject() 等待信號量 

   

  //信號量句柄 

  HANDLE global_Semephore; 

   

  // 共享資源 

  char global_Array[256]; 

  void InitializeArray() 

  { 

   for(int i = 0;i<256;i++) 

   { 

   global_Array[i]=I; 

   } 

  } 

   

 //線程

  UINT Global_ThreadOne(LPVOID pParam) 

  { 

   CEdit *ptr=(CEdit *)pParam; 

   ptr->SetWindowText(""); 

   //等待對共享資源請求被通過 等於 P操作 

  WaitForSingleObject(global_Semephore, INFINITE); 

   for(int i = 0;i<256;i++) 

   { 

   global_Array[i]=O; 

   ptr->SetWindowText(global_Array); 

   Sleep(10); 

   } 

  

  //釋放共享資源 等於 V操作 

   ReleaseSemaphore(global_Semephore, 1, NULL); 

   return 0; 

  } 

   

  UINT Global_ThreadTwo(LPVOID pParam) 

  { 

   CEdit *ptr=(CEdit *)pParam; 

   ptr->SetWindowText(""); 

   WaitForSingleObject(global_Semephore, INFINITE); 

   for(int i = 0;i<256;i++) 

   { 

   global_Array[i]=T; 

   ptr->SetWindowText(global_Array); 

   Sleep(10); 

   } 

   ReleaseSemaphore(global_Semephore, 1, NULL); 

   return 0; 

  } 

   

  UINT Global_ThreadThree(LPVOID pParam) 

  { 

   CEdit *ptr=(CEdit *)pParam; 

   ptr->SetWindowText(""); 

   WaitForSingleObject(global_Semephore, INFINITE); 

   for(int i = 0;i<256;i++) 

   { 

   global_Array[i]=H; 

   ptr->SetWindowText(global_Array); 

   Sleep(10); 

   } 

   ReleaseSemaphore(global_Semephore, 1, NULL); 

   return 0; 

  } 

   

  void CSemaphoreDlg::OnBnClickedButtonOne() 

  { 

   //設置信號量 個資源 1同時只可以有一個線程訪問 

   global_Semephore= CreateSemaphore(NULL, 1, 1, NULL); 

   this->StartThread(); 

  

  // TODO: Add your control notification handler code here 

  } 

   

  void CSemaphoreDlg::OnBnClickedButtonTwo() 

  { 

   //設置信號量 個資源 同時只可以有兩個線程訪問 

   global_Semephore= CreateSemaphore(NULL, 2, 2, NULL); 

   this->StartThread(); 

   // TODO: Add your control notification handler code here 

  } 

   

  void CSemaphoreDlg::OnBnClickedButtonThree() 

  { 

  //設置信號量 個資源 同時只可以有三個線程訪問 

   global_Semephore= CreateSemaphore(NULL, 3, 3, NULL); 

   this->StartThread();  

  

  // TODO: Add your control notification handler code here 

  }  

  信號量的使用特點使其更適用於對Socket(套接字)程序中線程的同步。例如,網絡上的HTTP服務器要對同一時間內訪問同一頁面的用戶數加以限制,這時可以爲每一個用戶對服務器的頁面請求設置一個線程,而頁面則是待保護的共享資源,通過使用信號量對線程的同步作用可以確保在任一時刻無論有多少用戶對某一頁面進行訪問,只有不大於設定的最大用戶數目的線程能夠進行訪問,而其他的訪問企圖則被掛起,只有在有用戶退出對此頁面的訪問後纔有可能進入。  

 

 

  

 作者: 61.51.111.*2006-8-24 14:09   回覆此發言    

 

--------------------------------------------------------------------------------

 

四種進程或線程同步互斥的控制方法  

   

  程序運行結果 

     

  

    

  事件(Event)  

    

  事件對象也可以通過通知操作的方式來保持線程的同步。並且可以實現不同進程中的線程同步操作。  

  信號量包含的幾個操作原語: 

    CreateEvent() 創建一個信號量 

    OpenEvent() 打開一個事件 

    SetEvent() 回置事件 

    WaitForSingleObject() 等待一個事件 

    WaitForMultipleObjects() 等待多個事件  

  WaitForMultipleObjects 函數原型: 

    WaitForMultipleObjects( 

    IN DWORD nCount, // 等待句柄數 

    IN CONST HANDLE *lpHandles, //指向句柄數組 

    IN BOOL bWaitAll, //是否完全等待標誌 

    IN DWORD dwMilliseconds //等待時間 

    )  

  

  參數nCount指定了要等待的內核對象的數目,存放這些內核對象的數組由lpHandles來指向。fWaitAll對指定的這nCount個內核對象的兩種等待方式進行了指定,爲TRUE時當所有對象都被通知時函數纔會返回,爲FALSE則只要其中任何一個得到通知就可以返回。dwMilliseconds在這裏的作用與在WaitForSingleObject()中的作用是完全一致的。如果等待超時,函數將返回WAIT_TIMEOUT。 

   

  //事件數組 

  HANDLE global_Events[2]; 

   

  // 共享資源 

  char global_Array[256]; 

   

  void InitializeArray() 

  { 

   for(int i = 0;i<256;i++) 

   { 

   global_Array[i]=I; 

   } 

  } 

   

  UINT Global_ThreadOne(LPVOID pParam) 

  { 

   CEdit *ptr=(CEdit *)pParam; 

   ptr->SetWindowText(""); 

   for(int i = 0;i<256;i++) 

   { 

   global_Array[i]=O; 

   ptr->SetWindowText(global_Array); 

   Sleep(10); 

   } 

  

  //回置事件 

   SetEvent(global_Events[0]); 

   return 0; 

  } 

   

  UINT Global_ThreadTwo(LPVOID pParam) 

  { 

   CEdit *ptr=(CEdit *)pParam; 

   ptr->SetWindowText(""); 

   for(int i = 0;i<256;i++) 

   { 

   global_Array[i]=T; 

   ptr->SetWindowText(global_Array); 

   Sleep(10); 

   } 

  

  //回置事件 

   SetEvent(global_Events[1]); 

   return 0; 

  } 

   

  UINT Global_ThreadThree(LPVOID pParam) 

  { 

   CEdit *ptr=(CEdit *)pParam; 

   ptr->SetWindowText(""); 

  

  //等待兩個事件都被回置 

   WaitForMultipleObjects(2, global_Events, true, INFINITE); 

   for(int i = 0;i<256;i++) 

   { 

   global_Array[i]=H; 

   ptr->SetWindowText(global_Array); 

   Sleep(10); 

   } 

   return 0; 

  } 

  void CEventDlg::OnBnClickedButtonStart() 

  { 

   for (int i = 0; i < 2; i++) 

   { 

  

  //實例化事件 

   global_Events[i]=CreateEvent(NULL,false,false,NULL); 

   } 

   CWinThread *ptrOne = AfxBeginThread(Global_ThreadOne, 

   &m_One, 

   THREAD_PRIORITY_NORMAL, 

   0, 

   CREATE_SUSPENDED); 

   ptrOne->ResumeThread(); 

   

   //Start the second Thread 

   CWinThread *ptrTwo = AfxBeginThread(Global_ThreadTwo, 

   &m_Two, 

   THREAD_PRIORITY_NORMAL, 

   0, 

   CREATE_SUSPENDED); 

   ptrTwo->ResumeThread(); 

   

   //Start the Third Thread 

   CWinThread *ptrThree = AfxBeginThread(Global_ThreadThree, 

   &m_Three, 

   THREAD_PRIORITY_NORMAL, 

   0, 

   CREATE_SUSPENDED); 

   ptrThree->ResumeThread(); 

   // TODO: Add your control notification handler code here 

  }  

  

  事件可以實現不同進程中的線程同步操作,並且可以方便的實現多個線程的優先比較等待操作,例如寫多個WaitForSingleObject來代替WaitForMultipleObjects從而使編程更加靈活。  

  程序運行結果 

     

  

  總結:  

  1. 互斥量與臨界區的作用非常相似,但互斥量是可以命名的,也就是說它可以跨越進程使用。所以創建互斥量需要的資源更多,所以如果只爲了在進程內部是用的話使用臨界區會帶來速度上的優勢並能夠減少資源佔用量。因爲互斥量是跨進程的互斥量一旦被創建,就可以通過名字打開它。  

  2. 互斥量(Mutex),信號燈(Semaphore),事件(Event)都可以被跨越進程使用來進行同步數據操作,而其他的對象與數據同步操作無關,但對於進程和線程來講,如果進程和線程在運行狀態則爲無信號狀態,在退出後爲有信號狀態。所以可以使用WaitForSingleObject來等待進程和線程退出。  

  3. 通過互斥量可以指定資源被獨佔的方式使用,但如果有下面一種情況通過互斥量就無法處理,比如現在一位用戶購買了一份三個併發訪問許可的數據庫系統,可以根據用戶購買的訪問許可數量來決定有多少個線程/進程能同時進行數據庫操作,這時候如果利用互斥量就沒有辦法完成這個要求,信號燈對象可以說是一種資源計數器。

 

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