UNIX進程通信_第1頁
UNIX進程通信_第2頁
UNIX進程通信_第3頁
UNIX進程通信_第4頁
UNIX進程通信_第5頁
已閱讀5頁,還剩89頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)

文檔簡介

1、進程通信:多個進程并發(fā)執(zhí)行如何協(xié)調(diào)它們之間的功能進程通信:多個進程并發(fā)執(zhí)行如何協(xié)調(diào)它們之間的功能 1 通信分為兩類通信分為兩類 控制信息的傳遞:控制信息的傳遞: 低級通信低級通信 大批量數(shù)據(jù)的傳遞:大批量數(shù)據(jù)的傳遞: 高級通信高級通信 2基本的通信方式基本的通信方式 (a)主從式通信:)主從式通信: 通信的雙方存在一種隸屬關(guān)系通信的雙方存在一種隸屬關(guān)系 (b)會話式通信:)會話式通信: 通信進程雙方采用請求應(yīng)答的方式進行通信通信進程雙方采用請求應(yīng)答的方式進行通信 使用者進程 服務(wù)者進程 request response 2基本的通信方式基本的通信方式 (c)消息或郵件通信:)消息或郵件通信:

2、通信雙方處于一個平等地位,發(fā)的消息通通信雙方處于一個平等地位,發(fā)的消息通過消息系統(tǒng)或郵件系統(tǒng)進行大批量數(shù)據(jù)傳遞。過消息系統(tǒng)或郵件系統(tǒng)進行大批量數(shù)據(jù)傳遞。 消息緩沖或郵箱 發(fā)送進程 接收進程 2基本的通信方式基本的通信方式 (d)共享存儲區(qū)通信:進程之間采用進程通信信息共享存儲區(qū)的共享存儲區(qū)通信:進程之間采用進程通信信息共享存儲區(qū)的通信方式來完成進程間的通信。通信方式來完成進程間的通信。 共享存儲區(qū)共享存儲區(qū)進程進程1進程進程2 基本通信:早期的基本通信:早期的UNIX系統(tǒng)采用,簡單的信息傳遞,協(xié)調(diào)進程之間的系統(tǒng)采用,簡單的信息傳遞,協(xié)調(diào)進程之間的同步和互斥。同步和互斥。 管道通信:大批量的數(shù)據(jù)

3、傳送,有名管道和無名管道。管道通信:大批量的數(shù)據(jù)傳送,有名管道和無名管道。 IPC :采用消息方式進行進程間通信。:采用消息方式進行進程間通信。 1基本通信基本通信(1) 鎖文件通信:鎖文件通信: 通信雙方在某個指定目錄中查找是否有個雙方約定的文件存在,并以此來決定通信雙方在某個指定目錄中查找是否有個雙方約定的文件存在,并以此來決定通信的動作以及對應(yīng)的邏輯。進程用對通信的動作以及對應(yīng)的邏輯。進程用對“鎖文件創(chuàng)建與否鎖文件創(chuàng)建與否”狀態(tài)的判定和設(shè)置狀態(tài)的判定和設(shè)置完成一個進程到另一個進程之間的通信。完成一個進程到另一個進程之間的通信。(2) 記錄鎖定文件通信記錄鎖定文件通信 即是通過對記錄(文件

4、中連續(xù)的字節(jié)組成特定的數(shù)據(jù))的鎖定來實現(xiàn)進程通信。即是通過對記錄(文件中連續(xù)的字節(jié)組成特定的數(shù)據(jù))的鎖定來實現(xiàn)進程通信。(3)信號信號 使用軟中斷信號的通信方式。信號可以做預(yù)先的說明,用戶使用時只需要根使用軟中斷信號的通信方式。信號可以做預(yù)先的說明,用戶使用時只需要根據(jù)信號的約定來完成進程之間的通信。據(jù)信號的約定來完成進程之間的通信。 基本概念基本概念 信號是信號是UNIX操作系統(tǒng)用來通知進程發(fā)生了某種事件的一種手段操作系統(tǒng)用來通知進程發(fā)生了某種事件的一種手段 信號也稱為軟中斷,它提供了一種處理異步事件的方法信號也稱為軟中斷,它提供了一種處理異步事件的方法 信號也可以用于進程之間進行通信和實現(xiàn)

5、進程同步處理信號也可以用于進程之間進行通信和實現(xiàn)進程同步處理信號的定義:信號的定義: 信號是傳送給進程的一種信號是傳送給進程的一種事件事件通知,通知,UNIX系統(tǒng)為每一種可能的系統(tǒng)為每一種可能的事件定義了一組信號,每一個信號有一個信號名,名字均以事件定義了一組信號,每一個信號有一個信號名,名字均以SIG打頭打頭很多種情況會產(chǎn)生信號很多種情況會產(chǎn)生信號當用戶在終端按下某些鍵時,產(chǎn)生終端生成的信號,例如:當用戶在終端按下某些鍵時,產(chǎn)生終端生成的信號,例如:按下按下Delete鍵或者鍵或者Ctrl-c通常產(chǎn)生一個中斷信號通常產(chǎn)生一個中斷信號 SIGINT,這是停止正在,這是停止正在運行的程序的一種常

6、用手段運行的程序的一種常用手段硬件例外會產(chǎn)生信號,例如:硬件例外會產(chǎn)生信號,例如:零作為除數(shù)、非法存儲器訪問等。這種情況通常是由硬件而不是零作為除數(shù)、非法存儲器訪問等。這種情況通常是由硬件而不是UNIX內(nèi)核內(nèi)核檢測到的,但由內(nèi)核向發(fā)生此錯誤的哪個進程發(fā)送相應(yīng)的信號,比如發(fā)生非檢測到的,但由內(nèi)核向發(fā)生此錯誤的哪個進程發(fā)送相應(yīng)的信號,比如發(fā)生非法存儲器訪問時,信號法存儲器訪問時,信號SIGSEGV將被內(nèi)核發(fā)送到執(zhí)行了該非法訪問的進程。將被內(nèi)核發(fā)送到執(zhí)行了該非法訪問的進程。如果發(fā)生了某種必須讓進程知道的情況時也會生成信號。這里的情況不是硬如果發(fā)生了某種必須讓進程知道的情況時也會生成信號。這里的情況不

7、是硬件產(chǎn)生的,而是軟條件,例如:件產(chǎn)生的,而是軟條件,例如:當進程設(shè)置的定時器到期時將生成當進程設(shè)置的定時器到期時將生成SIGALRM信號信號當進程向一個管道寫數(shù)據(jù),而此管道已不存在讀數(shù)據(jù)方時,生成當進程向一個管道寫數(shù)據(jù),而此管道已不存在讀數(shù)據(jù)方時,生成SIGPIPE信號信號某些系統(tǒng)調(diào)用將產(chǎn)生信號,例如某些系統(tǒng)調(diào)用將產(chǎn)生信號,例如kill函數(shù)將允許進程發(fā)送任何信號給本進程,其他的進程或者進程組函數(shù)將允許進程發(fā)送任何信號給本進程,其他的進程或者進程組raise函數(shù)能夠發(fā)送任何一個信號給調(diào)用它的進程函數(shù)能夠發(fā)送任何一個信號給調(diào)用它的進程生成信號的事件可以歸并為三大類生成信號的事件可以歸并為三大類1

8、.程序錯誤程序錯誤 例如零作除數(shù)、非法存儲器訪問等例如零作除數(shù)、非法存儲器訪問等2. 外部事件外部事件 例如用戶按下例如用戶按下Delete鍵、定時器到期等鍵、定時器到期等3. 顯式請求顯式請求 進程主動調(diào)用進程主動調(diào)用kill函數(shù)或者函數(shù)或者raise函數(shù)函數(shù)同步信號和異步信號同步信號和異步信號信號的生成可以是同步的,也可以是異步的。信號的生成可以是同步的,也可以是異步的。 同步信號與程序中的某個具體操作相關(guān)并且在那個操作進行時同時產(chǎn)生。同步信號與程序中的某個具體操作相關(guān)并且在那個操作進行時同時產(chǎn)生。 多數(shù)程序錯誤生成的信號是同步的,例如除零錯或內(nèi)存訪問錯多數(shù)程序錯誤生成的信號是同步的,例如

9、除零錯或內(nèi)存訪問錯 由進程顯示請求而生成的給自己的信號也是同步的,例如由進程顯示請求而生成的給自己的信號也是同步的,例如raise系統(tǒng)調(diào)用系統(tǒng)調(diào)用 異步信號是接收該信號的進程控制之外的事件生成的信號異步信號是接收該信號的進程控制之外的事件生成的信號 一般外部事件總是異步的生成信號一般外部事件總是異步的生成信號 作用于其他進程的作用于其他進程的kill系統(tǒng)調(diào)用也異步地生成信號系統(tǒng)調(diào)用也異步地生成信號 異步信號可以在進程運行任意時刻產(chǎn)生,進程無法預(yù)期信號到達的時刻異步信號可以在進程運行任意時刻產(chǎn)生,進程無法預(yù)期信號到達的時刻對信號的處理對信號的處理無論是同步還是異步信號,信號發(fā)生時,系統(tǒng)對信號可以

10、采用無論是同步還是異步信號,信號發(fā)生時,系統(tǒng)對信號可以采用3種處理方式種處理方式忽略信號忽略信號 大部分信號都可以被忽略,只有大部分信號都可以被忽略,只有2個除外個除外SIGSTOP和和SIGKILL。這兩個信號是為了給。這兩個信號是為了給root用戶提供殺掉或停止任何進用戶提供殺掉或停止任何進程的一種手段。程的一種手段。調(diào)用默認動作調(diào)用默認動作 系統(tǒng)為每種信號規(guī)定了一個默認動作,如果用戶進系統(tǒng)為每種信號規(guī)定了一個默認動作,如果用戶進程沒有為某個信號設(shè)置句柄,則該信號到達時,對該信號的處理程沒有為某個信號設(shè)置句柄,則該信號到達時,對該信號的處理由由UNIX內(nèi)核來完成。通常的默認動作有內(nèi)核來完成

11、。通常的默認動作有: core dump, 終止進程,終止進程,忽略信號,進程掛起等幾種。忽略信號,進程掛起等幾種。捕獲信號捕獲信號 需要告訴需要告訴UNIX系統(tǒng)內(nèi)核,當該信號出現(xiàn)時,調(diào)用專門系統(tǒng)內(nèi)核,當該信號出現(xiàn)時,調(diào)用專門提供的一個函數(shù),類似于提供的一個函數(shù),類似于MFC中的事件函數(shù)的概念。這個函數(shù)稱中的事件函數(shù)的概念。這個函數(shù)稱為信號句柄,或者簡稱為句柄,它專門對產(chǎn)生信號的事件作出處為信號句柄,或者簡稱為句柄,它專門對產(chǎn)生信號的事件作出處理。系統(tǒng)調(diào)用理。系統(tǒng)調(diào)用sigal為特定信號設(shè)置信號句柄為特定信號設(shè)置信號句柄UNIX系統(tǒng)中常用的信號系統(tǒng)中常用的信號信號信號描述描述默認處理默認處理S

12、IGABRT進程異常中止,調(diào)用進程異常中止,調(diào)用abort函數(shù)生成該信號函數(shù)生成該信號異常中止異常中止SIGALRM實時鬧鐘實時鬧鐘進程退出進程退出SIGFPE算術(shù)例外算術(shù)例外異常中止異常中止SIGHUP掛起進程掛起進程進程退出進程退出SIGILL非法硬件指令非法硬件指令異常終止異常終止SIGKILL終止進程終止進程進程退出進程退出SIGPIPE寫沒有讀者的管道寫沒有讀者的管道進程退出進程退出UNIX系統(tǒng)中常用的信號系統(tǒng)中常用的信號信號信號描述描述默認處理默認處理SIGQUIT終端終端quit字符字符 (ctrl-)異常終止異常終止SIGSEGV非法存儲訪問(地址越界)非法存儲訪問(地址越界)

13、異常終止異常終止SIGTERM終止進程終止進程退出退出SIGTRAP硬件自陷硬件自陷異常終止異常終止SIGUSR1用戶自定義信號用戶自定義信號退出退出SIGUSR2用戶自定義信號用戶自定義信號退出退出SIGSTOP停止進程停止進程退出退出指定和改變信號的動作指定和改變信號的動作 對于每一種信號,進程可以指定要么忽略它,要么采取默認動作,對于每一種信號,進程可以指定要么忽略它,要么采取默認動作,或者為它指定一個自定義的捕獲函數(shù);進程也可以在任何時候?qū)σ粋€或者為它指定一個自定義的捕獲函數(shù);進程也可以在任何時候?qū)σ粋€信號重新指定其動作或者回到其原先的動作。信號重新指定其動作或者回到其原先的動作。系統(tǒng)

14、調(diào)用系統(tǒng)調(diào)用 signal函數(shù)函數(shù)void* signal(int sig, void(* func)(int) ) ;參數(shù)參數(shù)sig是個整數(shù),指明該系統(tǒng)調(diào)用處理哪一個信號是個整數(shù),指明該系統(tǒng)調(diào)用處理哪一個信號參數(shù)參數(shù)func指明信號指明信號sig發(fā)生時,系統(tǒng)可以采取的發(fā)生時,系統(tǒng)可以采取的3種動作之一:種動作之一:常數(shù)常數(shù) SIG_DFL 指明對信號采用默認動作指明對信號采用默認動作常數(shù)常數(shù) SIG_IGN 指明該信號發(fā)生時,系統(tǒng)將忽略它指明該信號發(fā)生時,系統(tǒng)將忽略它信號句柄地址信號句柄地址 用戶自定義的信號處理函數(shù),信號發(fā)生時,系統(tǒng)將調(diào)用該函數(shù)用戶自定義的信號處理函數(shù),信號發(fā)生時,系統(tǒng)將調(diào)

15、用該函數(shù)進行處理進行處理void* signal(int sig, void(* func)(int) )函數(shù)的注意事項函數(shù)的注意事項 由由signal函數(shù)建立的信號句柄應(yīng)當是一個僅有一個整形參數(shù)且函數(shù)建立的信號句柄應(yīng)當是一個僅有一個整形參數(shù)且沒有返回值的函數(shù),其整形參數(shù)指明該生成的信號沒有返回值的函數(shù),其整形參數(shù)指明該生成的信號 signal函數(shù)的返回值是指向信號函數(shù)的返回值是指向信號sig的前一次有效動作的指針,的前一次有效動作的指針,當該函數(shù)調(diào)用成功,將返回當該函數(shù)調(diào)用成功,將返回SIG_DFL、SIG_IGN,或者信號句柄,或者信號句柄地址地址 當信號發(fā)生時,如果當信號發(fā)生時,如果fu

16、nc指向信號句柄,系統(tǒng)在把控制轉(zhuǎn)到信指向信號句柄,系統(tǒng)在把控制轉(zhuǎn)到信號句柄之前,將首先改變該信號的動作為號句柄之前,將首先改變該信號的動作為SIG_DFL 如果如果signal調(diào)用出錯,它返回調(diào)用出錯,它返回SIG_ERR,唯一的錯誤碼是,唯一的錯誤碼是EINVAL,即是,即是sig給出的信號數(shù)非法給出的信號數(shù)非法實例:實例:sig.c#include void signal_handle(int the_signal); /定義一個信號句柄函數(shù)的原型定義一個信號句柄函數(shù)的原型void main( void ) printf(“the process id is %dn”, getpid();

17、 if ( signal(SIGUSR1, signal_handle ) = SIG_ERR ) err_exit(“can not catch SIGUSR1n”); if ( signal(SIGUSR2, signal_handle ) = SIG_ERR ) err_exit(“can not catch SIGUSR2n”); for( ; ; ) pause(); void signal_handle(int the_signal) if ( the_signal = SIGUSR1 ) printf(“the sigal is SIGUSR1”); else if ( the_

18、signal = SIGUSR2) printf(“the sigal is SIGUSR2”); else printf(“the received signal is %dn”, the_signal);%a.out&the process id is 7346%執(zhí)行上面源代碼編譯后的程執(zhí)行上面源代碼編譯后的程序,并調(diào)度到后臺運行序,并調(diào)度到后臺運行kill -USR1 7346發(fā)送信號發(fā)送信號SIGUSR1給進程號給進程號為為7346的進程的進程the sigal is SIGUSR1 %后臺進程后臺進程7346收到收到SIGUSR1信信號并調(diào)用信號句柄號并調(diào)用信號句柄kill

19、-USR2 7346發(fā)送信號發(fā)送信號SIGUSR2給進程號給進程號為為7346的進程的進程the sigal is SIGUSR2 %后臺進程后臺進程7346收到收到SIGUSR2信信號并調(diào)用信號句柄號并調(diào)用信號句柄kill 7346發(fā)送信號發(fā)送信號SIGTERM給進程號為給進程號為7346的進程的進程,表示要結(jié)束該進程表示要結(jié)束該進程1+Terminated a.out&%后臺進程后臺進程7346收到收到SIGTERM信號信號,調(diào)用調(diào)用默認信號動作,該動作終止進程默認信號動作,該動作終止進程信號的生成信號的生成信號的產(chǎn)生,可以使用三個系統(tǒng)調(diào)用信號的產(chǎn)生,可以使用三個系統(tǒng)調(diào)用int r

20、aise( int sig );int kill( pid_t pid, int sig ); unisigned int alarm( unsigned int seconds ); raiseraise函數(shù)的實例函數(shù)的實例#include void sig_tstp_handle( int sig ) signal(SIGTSTP, SIG_DFL); /*做清理動作做清理動作,SIGTSTP是一個交互停止信號,類似于停止信號是一個交互停止信號,類似于停止信號SIGSTOP*/ . . . . . raise(SIGTSTP);void main() signal(SIGTSTP, sig

21、_tstp_handle); . . . . . . . /事務(wù)處理代碼事務(wù)處理代碼 killkill函數(shù)的實例函數(shù)的實例#include void catch_sig_int( ing signo ) printf(“SIGINT was caught,user pressed keydeleten”); void main() int i = 0; signal( SIGINT, catch_sig_int ); for( i=0; i5; i+ ) printf(“ Sleep called #%dn”, i ); sleep(1); printf(“exiting.n”); alarm

22、(unsigned int seconds); alarm函數(shù)和時間有點關(guān)系函數(shù)和時間有點關(guān)系,這個函數(shù)可以在這個函數(shù)可以在seconds秒后向自己發(fā)送一個秒后向自己發(fā)送一個SIGALRM信號信號. main() unsigned int i; alarm(1); for(i=0; ; i+) printf(I=%d,i); 信號操作:有時候我們希望進程正確的執(zhí)行信號操作:有時候我們希望進程正確的執(zhí)行,而不想進程受到信號的而不想進程受到信號的影響影響,比如我們希望上面那個程序在比如我們希望上面那個程序在1秒鐘之后不結(jié)束秒鐘之后不結(jié)束.這個時候我們就這個時候我們就要進行信號的操作要進行信號的操作

23、. 信號操作最常用的方法是信號屏蔽,即是進程屏蔽掉某些指定的信號操作最常用的方法是信號屏蔽,即是進程屏蔽掉某些指定的信號信號. 信號屏蔽要用到信號屏蔽要用到信號集信號集的概念和幾個重要的函數(shù)的概念和幾個重要的函數(shù). 信號集信號集 該數(shù)據(jù)結(jié)構(gòu)可以表示系統(tǒng)支持的每一個信號該數(shù)據(jù)結(jié)構(gòu)可以表示系統(tǒng)支持的每一個信號. 在在 signal.h中定義了中定義了 sigset_t 來描述信號集來描述信號集主要的信號操作函數(shù)主要的信號操作函數(shù) int sigemptyset(sigset_t *set); int sigfillset(sigset_t *set); int sigaddset(sigset_t

24、 *set,int signo); int sigdelset(sigset_t *set,int signo); int sigismember(sigset_t *set,int signo); int sigprocmask(int how,const sigset_t *set,sigset_t *oset); !注意:信號操作函數(shù)注意:信號操作函數(shù) 不能對信號不能對信號SIGKILL和和SIGSTOP進行阻塞屏蔽進行阻塞屏蔽以一個實例來解釋使用這幾個函數(shù)以一個實例來解釋使用這幾個函數(shù). int main() sigset_t intmask; sigemptyset( &in

25、tmask ); / 將信號集合初始化為空將信號集合初始化為空 sigaddset( &intmask, SIGINT ); / 加入中斷加入中斷 Ctrl+C 信號信號 sigaddset( &intmask, SIGTSTP ); / 加入加入 SIGTSTP信號信號 sigaddset( &intmask, SIGALRM ); / 加入加入 SIGTSTP信號信號 sigaddset( &intmask, SIGTERM ); / 加入加入 SIGTERM信號信號 sigprocmask(SIG_BLOCK,&intmask,NULL); /阻塞

26、信號集阻塞信號集intmask中的所有信號中的所有信號,不保存原來信號集所以不保存原來信號集所以oset為為NULL /* 進行事務(wù)操作,期間進程不會被用戶中斷,保證事務(wù)處理的完成進行事務(wù)操作,期間進程不會被用戶中斷,保證事務(wù)處理的完成 */ sigprocmask(SIG_UNBLOCK,&intmask,NULL); /放開上面阻塞的信號集放開上面阻塞的信號集 return;等待信號:如果程序是由外部事件所驅(qū)動的,或者使用信號進行同步,等待信號:如果程序是由外部事件所驅(qū)動的,或者使用信號進行同步,則需要等待信號的到達則需要等待信號的到達. UNIXUNIX系統(tǒng)提供函數(shù)系統(tǒng)提供函數(shù)p

27、ause() pause() 和和 sigsuspendsigsuspend()()來等待信號來等待信號intint pause(void); pause(void);int sigsuspend( sigset_t int sigsuspend( sigset_t * *sigmasksigmask););以一個實例來解釋使用這個函數(shù)以一個實例來解釋使用這個函數(shù). int main() . . . . . . / 功能代碼功能代碼 sigset_t mask; sigset_t old_mask sigemptyset(&mask); / 將信號集合初始化為空將信號集合初始化為空 s

28、igaddset(&intmask, SIGUSR1); / 加入加入 SIGUSR1 信號信號 sigprocmask(SIG_BLOCK,&mask, &old_mask); /獲取系統(tǒng)當前信號屏蔽獲取系統(tǒng)當前信號屏蔽 sigsuspend( &old_mask);/等待信號的到達,且是當前信號屏蔽集外的信等待信號的到達,且是當前信號屏蔽集外的信號號 sigprocmask(SIG_SETMASK ,&old_mask, NULL ); /恢復(fù)系統(tǒng)原來的信號屏蔽恢復(fù)系統(tǒng)原來的信號屏蔽 . . . . . . . . return;u 管道(管道(pi

29、pe)是)是UNIX中最古老的進程間通信工具,它提供進中最古老的進程間通信工具,它提供進程之間單向通信的方法。簡單說,管道是連接一個進程的輸出到程之間單向通信的方法。簡單說,管道是連接一個進程的輸出到另一個進程的輸入的一種方法另一個進程的輸入的一種方法簡介簡介u 管道(管道(pipe)的使用很廣泛,最常見是在命令行中)的使用很廣泛,最常見是在命令行中%cat file | grep pipe | morefilecatgrepmore終端終端管道管道u UNIX中的管道用于進程通信,是一種先進先出(中的管道用于進程通信,是一種先進先出(FIFO)的特)的特殊文件,通常是一個進程向管道中寫入數(shù)據(jù)

30、,另一個進程從管殊文件,通常是一個進程向管道中寫入數(shù)據(jù),另一個進程從管道中讀出數(shù)據(jù),從而完成通信的目的。道中讀出數(shù)據(jù),從而完成通信的目的。實現(xiàn)原理實現(xiàn)原理管道的特點管道的特點u 管道單獨構(gòu)成一種特殊的文件管道單獨構(gòu)成一種特殊的文件。u 管道中,寫入的內(nèi)容每次都添加在管道的末尾,并且每次都是管道中,寫入的內(nèi)容每次都添加在管道的末尾,并且每次都是從管道的頭部讀出數(shù)據(jù),就像隊列從管道的頭部讀出數(shù)據(jù),就像隊列不同點是:不同點是:u對管道寫入時,每次對管道寫入時,每次write調(diào)用的結(jié)果總是附加在管道的末端,而調(diào)用的結(jié)果總是附加在管道的末端,而文件的寫入不遵守這個規(guī)定,它可以通過指針隨意移動文件的寫入不

31、遵守這個規(guī)定,它可以通過指針隨意移動u對管道寫入時,每次寫入的字節(jié)數(shù)不能超過系統(tǒng)常量對管道寫入時,每次寫入的字節(jié)數(shù)不能超過系統(tǒng)常量PIPE_BUF,而對文件寫入則沒有這種規(guī)則而對文件寫入則沒有這種規(guī)則(a)管道寫和一般文件寫)管道寫和一般文件寫(write)相同點是:相同點是:u 當設(shè)備處于忙狀態(tài)時,當設(shè)備處于忙狀態(tài)時,write調(diào)用將被阻塞并被延遲執(zhí)行調(diào)用將被阻塞并被延遲執(zhí)行u 當當write調(diào)用完成時,都能返回實際寫入的字節(jié)數(shù)調(diào)用完成時,都能返回實際寫入的字節(jié)數(shù)u 對管道讀時,所有的對管道讀時,所有的read操作總是從管道的當前位置開始,操作總是從管道的當前位置開始,即是管道文件不支持指針

32、的移動,而文件的讀不遵守這個規(guī)定,即是管道文件不支持指針的移動,而文件的讀不遵守這個規(guī)定,它可以通過指針隨意移動它可以通過指針隨意移動 (b)管道讀和一般文件讀)管道讀和一般文件讀(read)u 當管道中沒有信息時,對管道進行當管道中沒有信息時,對管道進行read操作將被阻塞,直操作將被阻塞,直到有數(shù)據(jù)才返回;而對空文件進行到有數(shù)據(jù)才返回;而對空文件進行read操作,可以返回空串,操作,可以返回空串,并不發(fā)生阻塞并不發(fā)生阻塞 為了創(chuàng)建無名管道,需要調(diào)用為了創(chuàng)建無名管道,需要調(diào)用pipe函數(shù)函數(shù),pipe函數(shù)建立一個管函數(shù)建立一個管道,使得兩個進程可經(jīng)由它相互傳遞信息。我們可以將管道視為一道,使

33、得兩個進程可經(jīng)由它相互傳遞信息。我們可以將管道視為一塊空間,進程可以經(jīng)由兩個不同的文件描述符來分享這塊空間;同塊空間,進程可以經(jīng)由兩個不同的文件描述符來分享這塊空間;同時時pipe函數(shù)產(chǎn)生兩個文件描述字,進程通過使用這兩個文件描述字函數(shù)產(chǎn)生兩個文件描述字,進程通過使用這兩個文件描述字來存取管道信息來存取管道信息1 創(chuàng)建無名管道創(chuàng)建無名管道#include int pipe( int fdes2 );pipe函數(shù)的唯一參數(shù)是一個由兩個整數(shù)組成的數(shù)組,該函數(shù)調(diào)用成函數(shù)的唯一參數(shù)是一個由兩個整數(shù)組成的數(shù)組,該函數(shù)調(diào)用成功后將含有作為管道使用的兩個文字描述符,一個作為管道的輸入,功后將含有作為管道使用

34、的兩個文字描述符,一個作為管道的輸入,另一個作為管道的輸出。另一個作為管道的輸出。 2 管道讀寫操作管道讀寫操作讀操作讀操作 int read( int fd, char *buf, int len );寫操作寫操作 int write( int fd, char *buf, int len );u 用用pipe函數(shù)創(chuàng)建的管道沒有名字,是為了一次使用而創(chuàng)建的函數(shù)創(chuàng)建的管道沒有名字,是為了一次使用而創(chuàng)建的3 管道操作特點管道操作特點u 管道的兩個描述字管道的兩個描述字fdes0和和fdes1是同時打開的,如果從一是同時打開的,如果從一個沒有任何進程寫入的管道讀,個沒有任何進程寫入的管道讀,rea

35、d將返回將返回EOF(文件結(jié)束)。如(文件結(jié)束)。如果向一個沒有任何進程讀取的管道寫數(shù)據(jù),將產(chǎn)生果向一個沒有任何進程讀取的管道寫數(shù)據(jù),將產(chǎn)生SIGPIPE信號信號u 對有效的管道進行對有效的管道進行write操作時,如果管道已滿,操作時,如果管道已滿,write函數(shù)將被函數(shù)將被阻塞,直到有數(shù)據(jù)被讀出。阻塞,直到有數(shù)據(jù)被讀出。u 對有效的管道進行對有效的管道進行read操作時,如果管道內(nèi)沒有數(shù)據(jù),操作時,如果管道內(nèi)沒有數(shù)據(jù),read函函數(shù)將被阻塞,直到有新數(shù)據(jù)到達為止。數(shù)將被阻塞,直到有新數(shù)據(jù)到達為止。u 管道內(nèi)的數(shù)據(jù)只能被讀出管道內(nèi)的數(shù)據(jù)只能被讀出1次次u 管道不允許進行文件的定位操作,讀和寫

36、操作都是順序的管道不允許進行文件的定位操作,讀和寫操作都是順序的4 例程例程 :最簡單的管道通信:最簡單的管道通信void main() int fd2, pid; char msgsend = “Hi! Kid .n”; char msgrecv32; if( pipe( fd ) = -1 ) exit(1); if( (pid=fork() = 0 ) /pid =0 子進程子進程 close( fd1 ); printf(“before read data from pipe!n”); read( fd0, msgrecv, strlen(msgsend); printf(“read

37、%s from pipen”, msgrecv); else /父進程父進程 close( fd0 ); printf(“Parent sleeping .”); sleep(3); / 迫使子進程先執(zhí)行迫使子進程先執(zhí)行 printf(“Parent wake up !n”); write( fd1, msgsend, strlen(msgsend) ); wait(); exit(0); 基本上,無名管道只能在父子進程之間進行通信,兩個沒有父子關(guān)基本上,無名管道只能在父子進程之間進行通信,兩個沒有父子關(guān)系的進程,將不能使用這種方式進行通信;因此在無名管道的基礎(chǔ)上,系的進程,將不能使用這種方式

38、進行通信;因此在無名管道的基礎(chǔ)上,UNIX系統(tǒng)引入了有名管道的方式系統(tǒng)引入了有名管道的方式1.使用命令創(chuàng)建有名管道使用命令創(chuàng)建有名管道UNIX系統(tǒng)中有兩個命令可以創(chuàng)建有名管道系統(tǒng)中有兩個命令可以創(chuàng)建有名管道%mknod myfifo p或者或者%mkfifo a=rw myfifo%ls l myfifoprw-rw-rw- 1 lisi user 0 Nov 27 18:30 myfifop表示該文件為有名管道文件表示該文件為有名管道文件2.使用命令操作有名管道使用命令操作有名管道有名管道建立后,可以使用一些普通的命令對它進行操作,如果一有名管道建立后,可以使用一些普通的命令對它進行操作,如

39、果一般的文件操作一樣般的文件操作一樣%cat cfile.c myfifo &將文件將文件cfile.c中的內(nèi)容傳遞到有名管道中的內(nèi)容傳遞到有名管道m(xù)yfifo中中% cat myfifo將有名管道將有名管道m(xù)yfifo中的內(nèi)容讀出,并顯示在標準輸出上中的內(nèi)容讀出,并顯示在標準輸出上3.使用函數(shù)創(chuàng)建有名管道使用函數(shù)創(chuàng)建有名管道#include #include int mkfifo( char *path, mode_t mode );int mknod( char *path, mode_t mode, dev_t dev );int mkfifo( “/home/user1/myf

40、ifo”, 0666 );int mknod( “/home/user1/myfifo”, 0666|S_IFIFO, 0);對有名管道進行讀寫操作前必須使用對有名管道進行讀寫操作前必須使用open函數(shù)打開管道文件函數(shù)打開管道文件其他相關(guān)函數(shù)其他相關(guān)函數(shù)int open(const char* filename,int flags,mode_t mode); open函數(shù)不能以函數(shù)不能以O(shè)_RDWR方式打開方式打開 open函數(shù)可以指定函數(shù)可以指定O_NONBLOCK非阻塞標志非阻塞標志int read( int fd, char *buf, int len );int write( int

41、fd, char *buf, int len );int close (int fd);A.客戶端進程完成的工作客戶端進程完成的工作 1.建立私有有名管道建立私有有名管道 2.打開服務(wù)端的共有有名管道打開服務(wù)端的共有有名管道 3.等待用戶輸入命令等待用戶輸入命令 4.將私有管道名和命令寫入公有管道將私有管道名和命令寫入公有管道 5.從私有管道中讀服務(wù)端返回的結(jié)果從私有管道中讀服務(wù)端返回的結(jié)果5.例程例程 :一個基于有名管道的:一個基于有名管道的C/S模式應(yīng)用,客戶端發(fā)送命令到服務(wù)器,服模式應(yīng)用,客戶端發(fā)送命令到服務(wù)器,服務(wù)器執(zhí)行命令,并將命令返回信息回傳給客戶端務(wù)器執(zhí)行命令,并將命令返回信息回

42、傳給客戶端B.服務(wù)端進程完成的工作服務(wù)端進程完成的工作 1.建立服務(wù)端公有有名管道建立服務(wù)端公有有名管道 2.從公有有名管道中讀取客戶數(shù)據(jù)從公有有名管道中讀取客戶數(shù)據(jù) 3.執(zhí)行命令,并將結(jié)果寫入客戶私有管道執(zhí)行命令,并將結(jié)果寫入客戶私有管道#include FILE * popen(char *command, char *mode);該函數(shù)類似于該函數(shù)類似于system函數(shù),它調(diào)用函數(shù),它調(diào)用command參數(shù)指定的命令,并返回參數(shù)指定的命令,并返回指向命令執(zhí)行結(jié)果信息的文件句柄。指向命令執(zhí)行結(jié)果信息的文件句柄。跟例程相關(guān)的兩個函數(shù)跟例程相關(guān)的兩個函數(shù)#include FILE * pclo

43、se(FILE *fp);該函數(shù)關(guān)閉由該函數(shù)關(guān)閉由popen函數(shù)返回的文件句柄函數(shù)返回的文件句柄/客戶端代碼客戶端代碼#define PUBLIC /tmp/publicstruct message char fifo_name256; char cmd_line4096;void main() int n,privatefifo, publicfifo; char buffer4096; struct message msg; sprintf(msg.fifo_name, /tmp/fifo%d, getpid(); if( mknod(msg.fifo_name, S_IFIFO|0666

44、, 0) 0 ) / 打開客戶端建立的私有管道并準備寫入數(shù)據(jù)打開客戶端建立的私有管道并準備寫入數(shù)據(jù) privatefifo = open(msg.fifo_name, O_WRONLY | O_NDELAY ); if( privatefifo = -1 ) 錯誤處理錯誤處理 fin = popen(msg.cmd_line, “r”); /執(zhí)行命令執(zhí)行命令 while( n = read(fileno(fin), buffer, 4096) 0 ) write(privatefifo, buffer, n); /將從將從fin讀到的數(shù)據(jù)寫入客戶的私有管道讀到的數(shù)據(jù)寫入客戶的私有管道 mems

45、et(buffer, 0, 4096); pclose(fin); 4.有名管道和無名管道的區(qū)別有名管道和無名管道的區(qū)別特性特性有名管道有名管道無名管道無名管道進程的使用資格進程的使用資格沒有限制沒有限制必須有父子關(guān)系必須有父子關(guān)系文件名稱文件名稱有文件名,可以用有文件名,可以用ls l查看,查看,其文件屬性為其文件屬性為p沒有文件名稱沒有文件名稱數(shù)據(jù)讀寫的次序數(shù)據(jù)讀寫的次序先進先出先進先出先進先出先進先出建立的方式建立的方式使用使用mkfifo或者或者mknod函數(shù)函數(shù)使用使用pipe函數(shù)函數(shù)刪除的方式刪除的方式使用使用rm命令或者命令或者unlink函數(shù)函數(shù)被使用完后,會被自動刪除被使用完

46、后,會被自動刪除 在在IPC (Interprocess communication)的通信模式通常有三種:消息的通信模式通常有三種:消息隊列、信號量、共享內(nèi)存。隊列、信號量、共享內(nèi)存。消息隊列方式消息隊列方式:使用一個消息結(jié)構(gòu)完成進程間分類格式化數(shù):使用一個消息結(jié)構(gòu)完成進程間分類格式化數(shù)據(jù)的傳送據(jù)的傳送共享存儲區(qū)方式共享存儲區(qū)方式:允許進程間共享:允許進程間共享 虛地址空間的某些區(qū)域,虛地址空間的某些區(qū)域,以達到信息傳送的效果以達到信息傳送的效果信號量方式:信號量方式:允許進程在一組信號量上進行交互,完成同步允許進程在一組信號量上進行交互,完成同步地執(zhí)行地執(zhí)行UNIX系統(tǒng)中,可以使用系統(tǒng)中,

47、可以使用 ipcs命令得到當前系統(tǒng)命令得到當前系統(tǒng)IPC的所的所有信息有信息 在在IPC的通信模式下,每個的通信模式下,每個IPC對象有一個唯一的名字,稱之為對象有一個唯一的名字,稱之為“鍵鍵”(key)即關(guān)鍵字)即關(guān)鍵字, 類似于文件的文件描述符。類似于文件的文件描述符。 在在IPC的通信模式下,的通信模式下,key的使用使得一個的使用使得一個IPC對象為多個進程所共用。對象為多個進程所共用。不使用不使用key,進程無法存取對象。,進程無法存取對象。 IPC對象一旦被建立之后,即是全局的。對象一旦被建立之后,即是全局的。上面我們探討過信號的使用,下面介紹消息的運用,先將他們進行一下比較上面我

48、們探討過信號的使用,下面介紹消息的運用,先將他們進行一下比較特性特性信號信號消息消息數(shù)據(jù)內(nèi)容數(shù)據(jù)內(nèi)容只是一些預(yù)設(shè)好的代碼用以表示系只是一些預(yù)設(shè)好的代碼用以表示系統(tǒng)發(fā)生的某些狀況統(tǒng)發(fā)生的某些狀況為一組連續(xù)語句或者符號,通為一組連續(xù)語句或者符號,通常量不會太大常量不會太大(比如比如1024字節(jié)字節(jié))用途用途擔任進程間少量信息的傳送,多半擔任進程間少量信息的傳送,多半是核心程序用來通知用戶進程一些是核心程序用來通知用戶進程一些異常的狀況異常的狀況用于進程之間彼此進行數(shù)據(jù)交用于進程之間彼此進行數(shù)據(jù)交換換發(fā)送時刻發(fā)送時刻任何時間任何時間不是任何時刻都可以發(fā)送不是任何時刻都可以發(fā)送發(fā)送者辨識發(fā)送者辨識不知

49、道發(fā)送者是誰不知道發(fā)送者是誰明確知道發(fā)送者是誰明確知道發(fā)送者是誰送往對象送往對象某個進程某個進程消息隊列消息隊列處理方法處理方法可以處理或者不予理會可以處理或者不予理會必須要處理必須要處理數(shù)據(jù)傳輸效率數(shù)據(jù)傳輸效率不適合較大量的信息傳輸,效率差不適合較大量的信息傳輸,效率差對中等數(shù)量的數(shù)據(jù)傳送效率好對中等數(shù)量的數(shù)據(jù)傳送效率好 消息隊列使進程能將格式化的數(shù)據(jù)送往任意的進程。消息隊消息隊列使進程能將格式化的數(shù)據(jù)送往任意的進程。消息隊列主要由三部分組成:消息隊列表、消息頭、消息文本。列主要由三部分組成:消息隊列表、消息頭、消息文本。 其中消息隊列表中每個隊列項包含有該隊列的頭尾指針,描其中消息隊列表中

50、每個隊列項包含有該隊列的頭尾指針,描述了一個隊列的位置。述了一個隊列的位置。 消息頭表中包含所有消息隊列索引等信息。消息頭有指向消消息頭表中包含所有消息隊列索引等信息。消息頭有指向消息正文的指針。息正文的指針。 消息正文指存放真正消息的數(shù)據(jù)區(qū),即信息的實體。消息正文指存放真正消息的數(shù)據(jù)區(qū),即信息的實體。隊列1隊列2隊列3隊列nmessage10message11message1m消息正文消息正文消息正文 1 1 消息隊列的創(chuàng)建或獲取消息隊列的創(chuàng)建或獲取include include include int msgget(key_t key,int msgflg); 消息隊列的創(chuàng)建或獲取需要用到

51、消息隊列的創(chuàng)建或獲取需要用到msggetmsgget函數(shù)函數(shù)參數(shù)參數(shù)Key:Key:關(guān)鍵字值。關(guān)鍵字值。參數(shù)參數(shù)MsgflgMsgflg: :打開和存取操作權(quán)限設(shè)置打開和存取操作權(quán)限設(shè)置 IPC_CREATIPC_CREAT:如果內(nèi)核中沒有此隊列,則創(chuàng)建它。:如果內(nèi)核中沒有此隊列,則創(chuàng)建它。 IPC_EXCLIPC_EXCL和和IPC_CREATIPC_CREAT一起使用時,如果隊列已經(jīng)存在,則失敗一起使用時,如果隊列已經(jīng)存在,則失敗1.1.消息隊列的創(chuàng)建示例消息隊列的創(chuàng)建示例 int msgid; key_t key=56789; msgid = msgget( key, 0666 ); /

52、打開一個關(guān)鍵字為打開一個關(guān)鍵字為56789的消息隊列,如果該隊列已經(jīng)存在,的消息隊列,如果該隊列已經(jīng)存在,則函數(shù)返回該消息隊列的則函數(shù)返回該消息隊列的id,如果該隊列不存在,則函數(shù)返回,如果該隊列不存在,則函數(shù)返回錯錯 int msgid; key_t key=56789; msgid = msgget( key, IPC_CREAT|IPC_EXCL|0666 ); /創(chuàng)建一個關(guān)鍵字為創(chuàng)建一個關(guān)鍵字為56789的消息隊列,并設(shè)置權(quán)限為的消息隊列,并設(shè)置權(quán)限為0666,即是所有,即是所有用戶都能讀和寫該消息隊列,如果該隊列已經(jīng)存在,則函數(shù)返回錯誤用戶都能讀和寫該消息隊列,如果該隊列已經(jīng)存在,則

53、函數(shù)返回錯誤-1 int msgid; key_t key=56789; msgid = msgget( key, IPC_CREAT|0666 ); /創(chuàng)建一個關(guān)鍵字為創(chuàng)建一個關(guān)鍵字為56789的消息隊列,并設(shè)置權(quán)限為的消息隊列,并設(shè)置權(quán)限為0666,即,即是所有用戶都能讀和寫該消息隊列,如果該隊列已經(jīng)存在,則直接是所有用戶都能讀和寫該消息隊列,如果該隊列已經(jīng)存在,則直接得到其隊列的標識得到其隊列的標識id2.2.消息發(fā)送消息發(fā)送include include include int msgsnd(int msgid,const void *msgp,size_t msgsz int msg

54、flg);消息發(fā)送需要用到函數(shù)消息發(fā)送需要用到函數(shù)msgsndmsgsnd返回值:如果成功,返回值:如果成功,0 0;如果失敗,;如果失敗,-1-1參數(shù)參數(shù)msgidmsgid是消息隊列標識符,它是由系統(tǒng)調(diào)用是消息隊列標識符,它是由系統(tǒng)調(diào)用msggetmsgget返回的。返回的。參數(shù)參數(shù)msgpmsgp,是指向消息緩沖區(qū)的指針。,是指向消息緩沖區(qū)的指針。參數(shù)參數(shù)msgszmsgsz中包含的是消息的字節(jié)大小中包含的是消息的字節(jié)大小 參數(shù)參數(shù)msgflgmsgflg可以設(shè)置為可以設(shè)置為0 0(此時為忽略此參數(shù)),或者使用(此時為忽略此參數(shù)),或者使用IPC_NOWAITIPC_NOWAIT如果消息

55、隊列已滿,那么此消息則不會寫入到消息隊列中,控制將返回到調(diào)用進程中。如果沒有指明,調(diào)用進程將會掛起,直到消息可以寫入到隊列中。3.3.消息接收消息接收include include include Int msgrcv(int msqid,struct msgbuf Int msgrcv(int msqid,struct msgbuf * *msgp,int msgsz,long mtype,int msgflgmsgp,int msgsz,long mtype,int msgflg););消息接收需要用到函數(shù)消息接收需要用到函數(shù)msgrcvmsgrcv返回值:如果成功,則返回復(fù)制到消息緩沖區(qū)

56、的字節(jié)數(shù)。如果失敗,則返回-1參數(shù)參數(shù)msgidmsgid用來指定將要讀取消息的隊列。用來指定將要讀取消息的隊列。參數(shù)參數(shù)msgpmsgp代表要存儲消息的消息緩沖區(qū)的地址。代表要存儲消息的消息緩沖區(qū)的地址。參數(shù)參數(shù)msgszmsgsz是消息緩沖區(qū)的長度,不包括是消息緩沖區(qū)的長度,不包括mtypemtype的長度,它可以按照如下的方的長度,它可以按照如下的方法計算:法計算:msgsz=sizeof(structmymsgbuf)-sizeof(longmsgsz=sizeof(structmymsgbuf)-sizeof(long););參數(shù)參數(shù)msgflgmsgflg是要從消息隊列中讀取的消息

57、的類型。如果此參數(shù)的值為是要從消息隊列中讀取的消息的類型。如果此參數(shù)的值為0 0,那么,那么隊列中最長時間的一條消息將返回。隊列中最長時間的一條消息將返回。如果調(diào)用中使用了IPC_NOWAIT作為標志,那么當沒有數(shù)據(jù)可以使用時,調(diào)用將把ENOMSG返回到調(diào)用進程中。否則,調(diào)用進程將會掛起,直到隊列中的一條消息滿足msgrcv()的參數(shù)要求。如果當客戶端等待一條消息的時候隊列為空,將會返回EIDRM。如果進程在等待消息的過程中捕捉到一個信號,則返回EINTR4.4.消息隊列的控制消息隊列的控制include include include int msgctl(int msgid,int cmd

58、,struct msqid_ds *buf); UNIX UNIX系統(tǒng)提供系統(tǒng)提供 msgctlmsgctl函數(shù)實現(xiàn)對消息隊列的控制函數(shù)實現(xiàn)對消息隊列的控制IPC_STAT:讀取消息隊列的數(shù)據(jù)結(jié)構(gòu)msqid_ds,并將其存儲在buf指定的地址中。IPC_SET設(shè)置消息隊列的數(shù)據(jù)結(jié)構(gòu)msqid_ds中的ipc_perm元素的值。這個值取自buf參數(shù)。IPC_RMID從系統(tǒng)內(nèi)核中移走消息隊列。3.3.示例示例1 1有兩個程序有兩個程序program1program1和和program2,program2,其中其中program2program2接受用戶輸入,并將輸入內(nèi)容發(fā)接受用戶輸入,并將輸入內(nèi)容

59、發(fā)送到送到program1program1,program1program1顯示接收到的消息。顯示接收到的消息。 如果用戶輸入如果用戶輸入endend,則兩個程序都,則兩個程序都結(jié)束結(jié)束/ program1.cinclude include include struct my_msg long mytype; char text256;void main() int running = 1; int msgid; struct my_msg msgbuf; msgid = msgget( ( key_t )1234, IPC_CREAT|0666 ); if( msgid 0 ) exit(1

60、); while( running ) if( msgrcv( msgid, (void *)&msgbuf, 256, 1, 0) = -1) / 錯誤處理錯誤處理 printf(“you wrote: %s”, msgbuf.text ); if( strncmp( msgbuf.txt, “end” , 3 ) = = 0 ) running = 0; if( msgctl( msgid, IPC_RMID, 0 ) = -1 ) / 錯誤處理錯誤處理 exit( 0 );/ program2.cinclude include include struct my_msg long mytype; char text256;void main() int running = 1; int msgid; struct my_msg msgb

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
  • 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論