chap7.3進(jìn)程間通信(自學(xué))_第1頁
chap7.3進(jìn)程間通信(自學(xué))_第2頁
chap7.3進(jìn)程間通信(自學(xué))_第3頁
chap7.3進(jìn)程間通信(自學(xué))_第4頁
chap7.3進(jìn)程間通信(自學(xué))_第5頁
已閱讀5頁,還剩123頁未讀, 繼續(xù)免費閱讀

下載本文檔

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

文檔簡介

1、 兩個例子: 父進(jìn)程創(chuàng)建完子進(jìn)程后,需要等待子進(jìn)程運行完畢,然后清除子進(jìn)程占用的資源。這個時候,父進(jìn)程需要知道子進(jìn)程什么時候運行完畢。完成一個任務(wù)需要多個進(jìn)程協(xié)作,這時候,進(jìn)程間也需要通信來交換數(shù)據(jù)。 管道 命名管道 套接字 信號 信號量 共享內(nèi)存 消息 簡介 實現(xiàn)原理 特點 命名管道 無名管道u 管道(pipe)是UNIX中最古老的進(jìn)程間通信工具,它提供進(jìn)程之間單向通信的方法。簡單說,管道是連接一個進(jìn)程的輸出到另一個進(jìn)程的輸入的一種方法簡介簡介u 管道(pipe)的使用很廣泛,最常見是在命令行中%cat file | grep pipe | moreu UNIX中的管道用于進(jìn)程通信,是一種先

2、進(jìn)先出(FIFO)的特殊文件,通常是一個進(jìn)程向管道中寫入數(shù)據(jù),另一個進(jìn)程從管道中讀出數(shù)據(jù),從而完成通信的目的。實現(xiàn)原理實現(xiàn)原理管道的特點管道的特點u 管道單獨構(gòu)成一種特殊的文件。u 管道中,寫入的內(nèi)容每次都添加在管道的末尾,并且每次都是從管道的頭部讀出數(shù)據(jù),就像隊列不同點是:u對管道寫入時,每次write調(diào)用的結(jié)果總是附加在管道的末端,而文件的寫入不遵守這個規(guī)定,它可以通過指針隨意移動u對管道寫入時,每次寫入的字節(jié)數(shù)不能超過系統(tǒng)常量PIPE_BUF,而對文件寫入則沒有這種規(guī)則(a a)管道寫和一般文件寫)管道寫和一般文件寫(write)(write)相同點是:u 當(dāng)設(shè)備處于忙狀態(tài)時,write

3、調(diào)用將被阻塞并被延遲執(zhí)行u 當(dāng)write調(diào)用完成時,都能返回實際寫入的字節(jié)數(shù)u 對管道讀時,所有的read操作總是從管道的當(dāng)前位置開始,即是管道文件不支持指針的移動,而文件的讀不遵守這個規(guī)定,它可以通過指針隨意移動 (b b)管道讀和一般文件讀)管道讀和一般文件讀(read)(read)u 當(dāng)管道中沒有信息時,對管道進(jìn)行read操作將被阻塞,直到有數(shù)據(jù)才返回;而對空文件進(jìn)行read操作,可以返回空串,并不發(fā)生阻塞 為了創(chuàng)建無名管道,需要調(diào)用pipe函數(shù),pipe函數(shù)建立一個管道,使得兩個進(jìn)程可經(jīng)由它相互傳遞信息。我們可以將管道視為一塊空間,進(jìn)程可以經(jīng)由兩個不同的文件描述符來分享這塊空間;同時p

4、ipe函數(shù)產(chǎn)生兩個文件描述字,進(jìn)程通過使用這兩個文件描述字來存取管道信息1 創(chuàng)建無名管道創(chuàng)建無名管道#include int pipe( int fdes2 );pipe函數(shù)的唯一參數(shù)是一個由兩個整數(shù)組成的數(shù)組,該函數(shù)調(diào)用成功后將含有作為管道使用的兩個文字描述符,一個作為管道的輸入,另一個作為管道的輸出。 2 管道讀寫操作管道讀寫操作讀操作讀操作 int read( int fd, char *buf, int len );寫操作寫操作 int write( int fd, char *buf, int len );u用pipe函數(shù)創(chuàng)建的管道沒有名字,是為了一次使用而創(chuàng)建的u管道的兩個描述字f

5、des0和fdes1是同時打開的,如果從一個沒有任何進(jìn)程寫入的管道讀,read將返回EOF(文件結(jié)束)。如果向一個沒有任何進(jìn)程讀取的管道寫數(shù)據(jù),將產(chǎn)生SIGPIPE信號u對有效的管道進(jìn)行write操作時,如果管道已滿,write函數(shù)將被阻塞,直到有數(shù)據(jù)被讀出。 對有效的管道進(jìn)行read操作時,如果管道內(nèi)沒有數(shù)據(jù),read函數(shù)將被阻塞,直到有新數(shù)據(jù)到達(dá)為止。管道不允許進(jìn)行文件的定位操作,讀和寫操作都是順序的。 管道內(nèi)的數(shù)據(jù)只能被讀出1次3 管道操作特點管道操作特點4 例程例程 :簡單的管道通信:簡單的管道通信void main() int fd2, pid; char msgsend = “Hi

6、! Kid .n”; char msgrecv32; if( pipe( fd ) = -1 ) exit(1); if( (pid=fork() = 0 ) /pid =0 子進(jìn)程 close( fd1 ); printf(“before read data from pipe!n”); read( fd0, msgrecv, strlen(msgsend); printf(“read %s from pipen”, msgrecv); else /父進(jìn)程 close( fd0 ); printf(“Parent sleeping .”); sleep(3); / 迫使子進(jìn)程先執(zhí)行 prin

7、tf(“Parent wake up !n”); write( fd1, msgsend, strlen(msgsend) ); wait(); exit(0);簡介使用命令創(chuàng)建操作使用函數(shù)創(chuàng)建和管道的區(qū)別編程實例 基本上,無名管道只能在父子進(jìn)程之間進(jìn)行通信,兩個沒有父子關(guān)系的進(jìn)程,將不能使用這種方式進(jìn)行通信;因此在無名管道的基礎(chǔ)上,UNIX系統(tǒng)引入了有名管道的方式1. 使用命令創(chuàng)建有名管道使用命令創(chuàng)建有名管道UNIX系統(tǒng)中有兩個命令可以創(chuàng)建有名管道m(xù)knod myfifo p或者mknod a=rw myfifo%ls l myfifoprw-rw-rw- 1 lisi user 0 Nov

8、 27 18:30 myfifop表示該文件為有名管道文件2操作有名管道操作有名管道有名管道建立后,可以使用一些普通的命令對它進(jìn)行操作,如果一般的文件操作一樣%cat cfile.c myfifo &將文件cfile.c中的內(nèi)容傳遞到有名管道m(xù)yfifo中% cat myfifo將有名管道m(xù)yfifo中的內(nèi)容讀出,并顯示在標(biāo)準(zhǔn)輸出上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 mkf

9、ifo( “/home/user1/myfifo”, 0666 );int mknod( “/home/user1/myfifo”, 0666|S_IFIFO, 0);進(jìn)行讀寫操作前必須使用進(jìn)行讀寫操作前必須使用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非阻塞標(biāo)志非阻塞標(biāo)志int read( int fd, char *buf, int len );int

10、write( int fd, char *buf, int len );int close (int fd);4.有名管道和無名管道的區(qū)別有名管道和無名管道的區(qū)別特性有名管道無名管道進(jìn)程的使用資格沒有限制必須有父子關(guān)系文件名稱有文件名,可以用ls l查看,其文件屬性為p沒有文件名稱數(shù)據(jù)讀寫的次序先進(jìn)先出先進(jìn)先出建立的方式使用mkfifo或者mknod函數(shù)使用pipe函數(shù)刪除的方式使用rm命令或者unlink函數(shù)被使用完后,會被自動刪除A.客戶端進(jìn)程完成的工作 1.建立私有有名管道 2.打開服務(wù)端的共有有名管道 3.等待用戶輸入命令 4.將私有管道名和命令寫入公有管道 5.從私有管道中讀服務(wù)端返

11、回的結(jié)果B.服務(wù)端進(jìn)程完成的工作 1.建立服務(wù)端公有有名管道 2.從公有有名管道中讀取客戶數(shù)據(jù) 3.執(zhí)行命令,并將結(jié)果寫入客戶私有管道5.例程例程 :一個基于有名管道的:一個基于有名管道的C/S模式應(yīng)用,客戶端發(fā)送命令到服模式應(yīng)用,客戶端發(fā)送命令到服務(wù)器,服務(wù)器執(zhí)行命令,并將命令返回信息回傳給客戶端務(wù)器,服務(wù)器執(zhí)行命令,并將命令返回信息回傳給客戶端#include FILE * popen(char *command, char *mode);該函數(shù)類似于system函數(shù),它調(diào)用command參數(shù)指定的命令,并返回指向命令執(zhí)行結(jié)果信息的文件句柄。#include FILE * pclose(F

12、ILE *fp);該函數(shù)關(guān)閉由popen函數(shù)返回的文件句柄跟例程相關(guān)的兩個函數(shù)跟例程相關(guān)的兩個函數(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|066

13、6, 0) 0 ) / 打開客戶端建立的私有管道并準(zhǔn)備寫入數(shù)據(jù)打開客戶端建立的私有管道并準(zhǔn)備寫入數(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ù)寫入客戶的私有管道 mem

14、set(buffer, 0, 4096); pclose(fin); 基本概念基本概念 信號是信號是UNIX操作系統(tǒng)用來通知進(jìn)程發(fā)生了某種事件的一種手段操作系統(tǒng)用來通知進(jìn)程發(fā)生了某種事件的一種手段 信號也稱為軟中斷,它提供了一種處理異步事件的方法信號也稱為軟中斷,它提供了一種處理異步事件的方法 信號也可以用于進(jìn)程之間進(jìn)行通信和實現(xiàn)進(jìn)程同步處理信號也可以用于進(jìn)程之間進(jìn)行通信和實現(xiàn)進(jìn)程同步處理 UNIX系統(tǒng)為每一種可能的事件定義了一組信號,每一個信號有一個信系統(tǒng)為每一種可能的事件定義了一組信號,每一個信號有一個信號名,名字均以號名,名字均以SIG打頭打頭哪些情況會產(chǎn)生信號哪些情況會產(chǎn)生信號當(dāng)用戶在

15、終端按下某些鍵時,產(chǎn)生終端生成的信號,例如:按下Delete鍵或者Ctrl-c通常產(chǎn)生一個中斷信號 SIGINT,這是停止正在運行的程序的一種常用手段硬件例外會產(chǎn)生信號,例如:零作為除數(shù)、非法存儲器訪問等。這種情況通常是由硬件而不是UNIX內(nèi)核檢測到的,但由內(nèi)核向發(fā)生此錯誤的哪個進(jìn)程發(fā)送相應(yīng)的信號,比如發(fā)生非法存儲器訪問時,信號SIGSEGV將被內(nèi)核發(fā)送到執(zhí)行了該非法訪問的進(jìn)程。如果發(fā)生了某種必須讓進(jìn)程知道的情況時也會生成信號。這里的情況不是硬件產(chǎn)生的,而是軟條件,例如:當(dāng)進(jìn)程設(shè)置的定時器到期時將生成SIGALRM信號當(dāng)進(jìn)程向一個管道寫數(shù)據(jù),而此管道已不存在讀數(shù)據(jù)方時,生成SIGPIPE信號某

16、些系統(tǒng)調(diào)用將產(chǎn)生信號,例如kill函數(shù)將允許進(jìn)程發(fā)送任何信號給本進(jìn)程,其他的進(jìn)程或者進(jìn)程組raise函數(shù)能夠發(fā)送任何一個信號給調(diào)用它的進(jìn)程同步信號和異步信號同步信號和異步信號信號的生成可以是同步的,也可以是異步的。信號的生成可以是同步的,也可以是異步的。 同步信號與程序中的某個具體操作相關(guān)并且在那個操作進(jìn)行時同時產(chǎn)生。同步信號與程序中的某個具體操作相關(guān)并且在那個操作進(jìn)行時同時產(chǎn)生。 多數(shù)程序錯誤生成的信號是同步的,例如除零錯或內(nèi)存訪問錯多數(shù)程序錯誤生成的信號是同步的,例如除零錯或內(nèi)存訪問錯 由進(jìn)程顯示請求而生成的給自己的信號也是同步的,例如由進(jìn)程顯示請求而生成的給自己的信號也是同步的,例如ra

17、ise系統(tǒng)調(diào)用系統(tǒng)調(diào)用 異步信號是接收該信號的進(jìn)程控制之外的事件生成的信號異步信號是接收該信號的進(jìn)程控制之外的事件生成的信號 一般外部事件總是異步的生成信號一般外部事件總是異步的生成信號 作用于其他進(jìn)程的作用于其他進(jìn)程的kill系統(tǒng)調(diào)用也異步地生成信號系統(tǒng)調(diào)用也異步地生成信號 異步信號可以在進(jìn)程運行任意時刻產(chǎn)生,進(jìn)程無法預(yù)期信號到達(dá)的時刻異步信號可以在進(jìn)程運行任意時刻產(chǎn)生,進(jìn)程無法預(yù)期信號到達(dá)的時刻對信號的處理對信號的處理無論是同步還是異步信號,信號發(fā)生時,系統(tǒng)對信號可以采用3種處理方式忽略信號忽略信號 大部分信號都可以被忽略,只有2個除外SIGSTOP和SIGKILL。這兩個信號是為了給ro

18、ot用戶提供殺掉或停止任何進(jìn)程的一種手段。調(diào)用默認(rèn)動作調(diào)用默認(rèn)動作 系統(tǒng)為每種信號規(guī)定了一個默認(rèn)動作,如果用戶進(jìn)程沒有為某個信號設(shè)置句柄,則該信號到達(dá)時,對該信號的處理由UNIX內(nèi)核來完成。通常的默認(rèn)動作有: core dump, 終止進(jìn)程,忽略信號,進(jìn)程掛起等幾種。捕獲信號捕獲信號 需要告訴UNIX系統(tǒng)內(nèi)核,當(dāng)該信號出現(xiàn)時,調(diào)用專門提供的一個函數(shù),類似于MFC中的事件函數(shù)的概念。這個函數(shù)稱為信號句柄,或者簡稱為句柄,它專門對產(chǎn)生信號的事件作出處理。系統(tǒng)調(diào)用sigal為特定信號設(shè)置信號句柄UNIXUNIX系統(tǒng)中常用的信號系統(tǒng)中常用的信號信號描述默認(rèn)處理SIGABRT進(jìn)程異常中止,調(diào)用abort

19、函數(shù)生成該信號異常中止SIGALRM實時鬧鐘進(jìn)程退出SIGFPE算術(shù)例外異常中止SIGHUP掛起進(jìn)程進(jìn)程退出SIGILL非法硬件指令異常終止SIGKILL終止進(jìn)程進(jìn)程退出SIGPIPE寫沒有讀者的管道進(jìn)程退出UNIX系統(tǒng)中常用的信號系統(tǒng)中常用的信號信號描述默認(rèn)處理SIGQUIT終端quit字符 (ctrl-)異常終止SIGSEGV非法存儲訪問(地址越界)異常終止SIGTERM終止進(jìn)程退出SIGTRAP硬件自陷異常終止SIGUSR1用戶自定義信號退出SIGUSR2用戶自定義信號退出SIGSTOP停止進(jìn)程退出指定和改變信號的動作指定和改變信號的動作 對于每一種信號,進(jìn)程可以指定要么忽略它,要么采取

20、默認(rèn)動作,或者為它指定一個自定義的捕獲函數(shù);進(jìn)程也可以在任何時候?qū)σ粋€信號重新指定其動作或者回到其原先的動作。系統(tǒng)調(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 指明對信號采用默認(rèn)動作指明對信號采用默認(rèn)動作常數(shù)常數(shù) SIG_IGN 指明該信號發(fā)生時,系統(tǒng)將忽略它指明該信號發(fā)生時,系統(tǒng)將忽略它信號句柄地址

21、信號句柄地址 用戶自定義的信號處理函數(shù),信號發(fā)生時,系統(tǒng)將調(diào)用該函數(shù)用戶自定義的信號處理函數(shù),信號發(fā)生時,系統(tǒng)將調(diào)用該函數(shù)進(jìn)行處理進(jìn)行處理void* signal(int sig, void(* func)(int) )函數(shù)的注意事項 由signal函數(shù)建立的信號句柄應(yīng)當(dāng)是一個僅有一個整形參數(shù)且沒有返回值的函數(shù),其整形參數(shù)指明該生成的信號 signal函數(shù)的返回值是指向信號sig的前一次有效動作的指針,當(dāng)該函數(shù)調(diào)用成功,將返回SIG_DFL、SIG_IGN,或者信號句柄地址 當(dāng)信號發(fā)生時,如果func指向信號句柄,系統(tǒng)在把控制轉(zhuǎn)到信號句柄之前,將首先改變該信號的動作為SIG_DFL 如果sig

22、nal調(diào)用出錯,它返回SIG_ERR,唯一的錯誤碼是EINVAL,即是sig給出的信號數(shù)非法實例:實例:sig.c#include void signal_handle(int the_signal); /定義一個信號句柄函數(shù)的原型void main( void ) printf(“the process id is %dn”, getpid(); if ( signal(SIGUSR1, signal_handle ) = SIG_ERR ) err_exit(“can not catch SIGUSR1n”); if ( signal(SIGUSR2, signal_handle ) =

23、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_signal = SIGUSR2) printf(“the sigal is SIGUSR2”); else printf(“the received signal is %dn”, the_signal);%a.out&the process id

24、 is 7346%執(zhí)行上面源代碼編譯后的程執(zhí)行上面源代碼編譯后的程序,并調(diào)度到后臺運行序,并調(diào)度到后臺運行kill -USR1 7346發(fā)送信號發(fā)送信號SIGUSR1給進(jìn)程號給進(jìn)程號為為7346的進(jìn)程的進(jìn)程the sigal is SIGUSR1 %后臺進(jìn)程后臺進(jìn)程7346收到收到SIGUSR1信信號并調(diào)用信號句柄號并調(diào)用信號句柄kill -USR2 7346發(fā)送信號發(fā)送信號SIGUSR2給進(jìn)程號給進(jìn)程號為為7346的進(jìn)程的進(jìn)程the sigal is SIGUSR2 %后臺進(jìn)程后臺進(jìn)程7346收到收到SIGUSR2信信號并調(diào)用信號句柄號并調(diào)用信號句柄kill 7346發(fā)送信號發(fā)送信號SIGT

25、ERM給進(jìn)程號為給進(jìn)程號為7346的進(jìn)程的進(jìn)程,表示要結(jié)束該進(jìn)程表示要結(jié)束該進(jìn)程1+Terminated a.out&%后臺進(jìn)程后臺進(jìn)程7346收到收到SIGTERM信號信號,調(diào)用調(diào)用默認(rèn)信號動作,該動作終止進(jìn)程默認(rèn)信號動作,該動作終止進(jìn)程#include void catch_sig_int( ing signo ) /* signal( SIGINT, catch_sig_int );*/ printf(“SIGINT was caught,user pressed keydeleten”); /* signal( SIGINT, catch_sig_int );*/void main()

26、 int i = 0; signal( SIGINT, catch_sig_int ); for( i=0; i5; i+ ) printf(“ Sleep called #%dn”, i ); sleep(1); printf(“exiting.n”); 信號的生成信號的生成信號的產(chǎn)生,可以使用三個系統(tǒng)調(diào)用信號的產(chǎn)生,可以使用三個系統(tǒng)調(diào)用int raise( int sig ); 功能功能:給進(jìn)程自己發(fā)送一個信號給進(jìn)程自己發(fā)送一個信號int kill( pid_t pid, int sig ); 功能功能:發(fā)送一個信號給進(jìn)程或者進(jìn)程組發(fā)送一個信號給進(jìn)程或者進(jìn)程組unisigned int a

27、larm( unsigned int seconds ); 功能功能:在在seconds秒后向自己發(fā)送一個秒后向自己發(fā)送一個SIGALRM信號信號.raiseraise函數(shù)的實例函數(shù)的實例#include void sig_tstp_handle( int sig ) signal(SIGTSTP, SIG_DFL); /*做清理動作做清理動作,SIGTSTP是一個交互停止信號,類似于停止信號是一個交互停止信號,類似于停止信號SIGSTOP*/ . . . . . raise(SIGTSTP);void main() signal(SIGTSTP, sig_tstp_handle); . .

28、. . . . . /事務(wù)處理代碼事務(wù)處理代碼 killkill函數(shù)的實例函數(shù)的實例 alarm函數(shù)的實例函數(shù)的實例 main() unsigned int i; alarm(1); for(i=0; ; i+) printf(I=%d,i); 信號操作:有時候我們希望進(jìn)程正確的執(zhí)行信號操作:有時候我們希望進(jìn)程正確的執(zhí)行,而不想進(jìn)程受到信號的而不想進(jìn)程受到信號的影響影響,比如我們希望上面那個程序在比如我們希望上面那個程序在1秒鐘之后不結(jié)束秒鐘之后不結(jié)束.這個時候我們就這個時候我們就要進(jìn)行信號的操作要進(jìn)行信號的操作. 信號操作最常用的方法是信號屏蔽,即是進(jìn)程屏蔽掉某些指定的信號操作最常用的方法是

29、信號屏蔽,即是進(jìn)程屏蔽掉某些指定的信號信號. 信號屏蔽要用到信號屏蔽要用到信號集信號集的概念和幾個重要的函數(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 *set,int signo); int sigdelset(sigset_t

30、*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進(jìn)行阻塞屏蔽進(jìn)行阻塞屏蔽以一個實例來解釋使用這幾個函數(shù)以一個實例來解釋使用這幾個函數(shù). int main() sigset_t intmask; sigemptyset( &intmask ); / 將信號集合初始化為空將信號集合初始化為空 sigaddset( &i

31、ntmask, SIGINT ); / 加入中斷加入中斷 Ctrl+C 信號信號 sigaddset( &intmask, SIGTSTP ); / 加入加入 SIGTSTP信號信號 sigaddset( &intmask, SIGALRM ); / 加入加入 SIGTSTP信號信號 sigaddset( &intmask, SIGTERM ); / 加入加入 SIGTERM信號信號 sigprocmask(SIG_BLOCK,&intmask,NULL); /阻塞信號集阻塞信號集intmask中的所有信號中的所有信號,不保存原來信號集所以不保存原來信號集所以oset為為NULL /* 進(jìn)行事

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

33、nt pause(void);功能:在新的信號到來之前,暫時停止程序的運行功能:在新的信號到來之前,暫時停止程序的運行int sigsuspend( sigset_t int sigsuspend( sigset_t * *sigmask);sigmask);功能:將進(jìn)程信號的掩碼替換并暫時中止進(jìn)程功能:將進(jìn)程信號的掩碼替換并暫時中止進(jìn)程以一個實例來解釋使用這個函數(shù)以一個實例來解釋使用這個函數(shù). int main() . . . . . . / 功能代碼功能代碼 sigset_t mask; sigset_t old_mask sigemptyset(&mask); / 將信號集合初始化為空將

34、信號集合初始化為空 sigaddset(&mask, SIGUSR1); / 加入加入 SIGUSR1 信號信號 sigprocmask(SIG_BLOCK,&mask, &old_mask); /獲取系統(tǒng)當(dāng)前信號屏蔽獲取系統(tǒng)當(dāng)前信號屏蔽 sigsuspend( &old_mask);/等待信號的到達(dá),且是當(dāng)前信號屏蔽集外的信等待信號的到達(dá),且是當(dāng)前信號屏蔽集外的信號號 sigprocmask(SIG_SETMASK ,&old_mask, NULL ); /恢復(fù)系統(tǒng)原來的信號屏蔽恢復(fù)系統(tǒng)原來的信號屏蔽 . . . . . . . . return;信號量信號量( () )用以控制進(jìn)程之間的同

35、步控制,用來對資源做鎖定的工用以控制進(jìn)程之間的同步控制,用來對資源做鎖定的工作;使進(jìn)程間共用一個計數(shù)值作;使進(jìn)程間共用一個計數(shù)值. .信號信號( () )是核心程序送給用戶進(jìn)程的信息,其目的是通知進(jìn)程一些突發(fā)是核心程序送給用戶進(jìn)程的信息,其目的是通知進(jìn)程一些突發(fā)的事件,和軟硬件的狀態(tài)有關(guān),被處理的優(yōu)先級高于用戶進(jìn)程。的事件,和軟硬件的狀態(tài)有關(guān),被處理的優(yōu)先級高于用戶進(jìn)程。1.1.基本概念基本概念 信號量是一種程序設(shè)計方法,它主要用來控制并發(fā)的進(jìn)程之間的互斥情信號量是一種程序設(shè)計方法,它主要用來控制并發(fā)的進(jìn)程之間的互斥情形形 和生產(chǎn)和生產(chǎn)/ /消費消費 情形情形 信號量是信號量是Dijkstra

36、Dijkstra在上世紀(jì)在上世紀(jì)6060年代末設(shè)計出來的,它的模型模擬的是鐵年代末設(shè)計出來的,它的模型模擬的是鐵路通行控制信號路通行控制信號 在在UNIXUNIX操作系統(tǒng)中,信號量是一個支持兩種操作的整型對象,這兩種操操作系統(tǒng)中,信號量是一個支持兩種操作的整型對象,這兩種操作即是作即是P P操作和操作和V V操作操作。2.2.信號量的初始化信號量的初始化 在操作一個信號量之前必須調(diào)用在操作一個信號量之前必須調(diào)用semgetsemget函數(shù)進(jìn)行初始化,所謂初始化即函數(shù)進(jìn)行初始化,所謂初始化即是建立是建立 以及相關(guān)的數(shù)據(jù)結(jié)構(gòu)。以及相關(guān)的數(shù)據(jù)結(jié)構(gòu)。 在在UNIXUNIX系統(tǒng)中,所有信號量函數(shù)都是對系

37、統(tǒng)中,所有信號量函數(shù)都是對 進(jìn)行處理,即信號量進(jìn)行處理,即信號量標(biāo)識引用的是一組由獨立信號組成的信號量集合,這一點類似于信號集標(biāo)識引用的是一組由獨立信號組成的信號量集合,這一點類似于信號集signalsetsignalset。2.2.信號量的初始化信號量的初始化#include int semget( key_t key, int nsems, int semflg);返回值:如果成功,則返回信號量集的IPC標(biāo)識符。如果失敗,則返回-1數(shù)數(shù)keykey是關(guān)鍵字值是關(guān)鍵字值打開和存取操作與參數(shù)打開和存取操作與參數(shù)semflgsemflg中的內(nèi)容相關(guān)中的內(nèi)容相關(guān)參數(shù)參數(shù)nsemsnsems指出了一

38、個新的信號量集中應(yīng)該創(chuàng)建的信號量的個數(shù)指出了一個新的信號量集中應(yīng)該創(chuàng)建的信號量的個數(shù) semget semget函數(shù)用來創(chuàng)建一個新的信號量集,或者得到一個已存的函數(shù)用來創(chuàng)建一個新的信號量集,或者得到一個已存的信號量集的標(biāo)識信號量集的標(biāo)識2.2.信號量初始化的示例信號量初始化的示例 int semid; key_t key=56789; semid = semget( key, 1, IPC_CREAT|IPC_EXCL|0666 ); /創(chuàng)建一個關(guān)鍵字為創(chuàng)建一個關(guān)鍵字為56789的信號量集,并設(shè)置權(quán)限為的信號量集,并設(shè)置權(quán)限為0666,即是所有用戶都能讀和,即是所有用戶都能讀和寫該信號量集,信

39、號量集中只有寫該信號量集,信號量集中只有1個信號量,如果信號量集已經(jīng)存在,則函數(shù)返回錯誤個信號量,如果信號量集已經(jīng)存在,則函數(shù)返回錯誤 int semid; key_t key=56789; semid = semget( key, 1, IPC_CREAT|0666 ); /創(chuàng)建一個信號量集,如同剛才的信號量集,關(guān)鍵字為56789,權(quán)限為所有用戶都能讀寫,如果該信號量集不存在,則創(chuàng)建之,并返回新建的信號量集的標(biāo)識,否則就直接返回該已存在的信號量集標(biāo)識 int semid; key_t key=56789; semid = semget( key, 1, 0666); /試圖打開一個信號量集,

40、如果該信號量集存在,則返回該信號量集的標(biāo)識,如果該信試圖打開一個信號量集,如果該信號量集存在,則返回該信號量集的標(biāo)識,如果該信號量集不存在,則函數(shù)返回錯號量集不存在,則函數(shù)返回錯4.4.信號量的操作信號量的操作函數(shù)函數(shù)semop用于操作信號量集合,它的調(diào)用接口比較復(fù)雜,因為它試圖用一種最用于操作信號量集合,它的調(diào)用接口比較復(fù)雜,因為它試圖用一種最通用的接口包括各種可能的情形通用的接口包括各種可能的情形include int semop(int semid, struct sembuf *sops, size_t nsops);返回值:0,如果成功。-1,如果失敗第一個參數(shù)是關(guān)鍵字值。第二個參數(shù)

41、是指向?qū)⒁僮鞯臄?shù)組的指針。第三第一個參數(shù)是關(guān)鍵字值。第二個參數(shù)是指向?qū)⒁僮鞯臄?shù)組的指針。第三個參數(shù)是數(shù)組中的操作的個數(shù)。個參數(shù)是數(shù)組中的操作的個數(shù)。4.4.信號量的操作信號量的操作參數(shù)參數(shù) struct sembuf 的詳細(xì)說明的詳細(xì)說明struct sembuf unsigned short int sem_num ; /信號量個數(shù)信號量個數(shù) short int sem_op; / 信號量操作類型信號量操作類型 short int sem_flg; /信號量信號量操作標(biāo)志操作標(biāo)志 ;如果sem_op大于0,那么操作將sem_op加入到信號量的值中,并喚醒等待信號增加的進(jìn)程;如果為0,當(dāng)信

42、號量的值是0的時候,函數(shù)返回,否則阻塞直到信號量的值為0;如果小于0,函數(shù)判斷信號量的值加上這個負(fù)值.如果結(jié)果為0喚醒等待信號量為0的進(jìn)程,如果小于0函數(shù)阻塞.如果大于0,那么從信號量里面減去這個值并返回.4.4.信號量的操作信號量的操作semop 函數(shù)的調(diào)用需要注意:函數(shù)的調(diào)用需要注意:n semop的調(diào)用只有當(dāng)數(shù)組中所有的信號量操作都能成功時,它才成功返回的調(diào)用只有當(dāng)數(shù)組中所有的信號量操作都能成功時,它才成功返回 0n 如果數(shù)組中有某個信號量操作不能完成,則所有信號量的操作都不會執(zhí)行,如果數(shù)組中有某個信號量操作不能完成,則所有信號量的操作都不會執(zhí)行,此時調(diào)用將返回錯或者被阻塞。此時調(diào)用將返

43、回錯或者被阻塞。n 被阻塞的被阻塞的semop調(diào)用能被下面的情況之一恢復(fù):所有信號量的操作都能完成;調(diào)用能被下面的情況之一恢復(fù):所有信號量的操作都能完成;信號量集合被刪除;信號量集合被刪除; 進(jìn)程收到一個信號被終止進(jìn)程收到一個信號被終止5.5.信號量例程信號量例程下面的例程說明了信號量的簡單用法,該程序可以同時運行多次。這個程序?qū)⑤斚旅娴睦陶f明了信號量的簡單用法,該程序可以同時運行多次。這個程序?qū)⑤敵雒钚袇?shù),每個參數(shù)輸出一行,不過程序每輸出一個字符,就將出命令行參數(shù),每個參數(shù)輸出一行,不過程序每輸出一個字符,就將sleep一下,一下,該時間為隨機數(shù)。注意:為了每一行的輸出來自同一個進(jìn)程,

44、必須使用信號量的該時間為隨機數(shù)。注意:為了每一行的輸出來自同一個進(jìn)程,必須使用信號量的PV操作保證進(jìn)程的互斥訪問。操作保證進(jìn)程的互斥訪問。/ 建立信號量集合的函數(shù)#include int open_semaphore( key_t key, int numsems ) int sid; if( numsems 1 ) return -1; sid = semget( key, numsems, IPC_CREAT|0666 ); return sid;5.5.信號量例程信號量例程/ P操作的的函數(shù),企圖進(jìn)入關(guān)鍵區(qū)int semaphore_P( int sem_id ) struct semb

45、uf sbuf; sbuf.sem_num = 0; / 信號量個數(shù) sbuf.sem_op = -1; /減少信號量的值,表示請求資源 sbuf.sem_flg = SEM_UNDO; if( semop( sem_id, &sbuf, 1) = -1 ) fprintf( stderr, “semaphore P failedn” ); return 0; return 1;5.5.信號量例程信號量例程/ V操作的的函數(shù),離開關(guān)鍵區(qū)int semaphore_V( int sem_id ) struct sembuf sbuf; sbuf.sem_num = 0; / 信號量編號 sbuf

46、.sem_op = 1; / 增加信號量的值,表示釋放資源 sbuf.sem_flg = SEM_UNDO; if( semop( sem_id, &sbuf, 1) = -1 ) fprintf( stderr, “semaphore V failed!n” ); return 0; return 1;5.5.信號量例程信號量例程/ 程序的主函數(shù)void main(int argc, char *argv) int sem_id; / 信號集標(biāo)識 int i = 0; char *cp; srand( (unsigned int) getpid() ); /以進(jìn)程id作為隨機數(shù)種子 sem_

47、id = open_semaphore( (key_t)12345, 1 ); / 創(chuàng)建/打開信號量集,并獲得信號量集的標(biāo)識5.5.信號量例程信號量例程 for( i=0; i argc; i+ ) cp = argvi; / 第第i個命令行參數(shù)個命令行參數(shù) if( semaphore_P( sem_id) = 0 ) exit(1); / 關(guān)鍵區(qū)開始關(guān)鍵區(qū)開始 printf(“process %d:”, getpid(); fflush(stdout); while( *cp != NULL ) putchar( *cp ); fflush(stdout); sleep( rand() %

48、3 ); /sleep 0 2秒秒 cp+; / 關(guān)鍵區(qū)結(jié)束關(guān)鍵區(qū)結(jié)束 if( semaphore_V( sem_id) = 0 ) exit(1); printf(“n %d finishedn”, getpid() ); /關(guān)鍵區(qū)外,可能影響其他進(jìn)程的輸出關(guān)鍵區(qū)外,可能影響其他進(jìn)程的輸出5.5.信號量例程信號量例程 輸出結(jié)果輸出結(jié)果% a.out 1 test semaphore&1 7411% a.out are you success?process 7411:a.outprocess 7411:1process 7432:a.outprocess 7432:areprocess 74

49、32:youprocess 7411:testprocess 7411:semaphoreprocess 7432:su7411 finishedccess?7432 finished1 + Done a.out 1 test semaphore 概念概念 特點特點 創(chuàng)建創(chuàng)建 連接連接 分離分離 例程例程1.1.基本概念基本概念 共享存儲是物理存儲器中一段可以由2個以上的進(jìn)程共享的存儲空間。共享存儲段具有大小和物理存儲地址,想要訪問共享存儲段的進(jìn)程可以連接這段存儲區(qū)域到自己的地址空間中任何適合的地方,其他進(jìn)程也一樣。這樣,多個進(jìn)程就可以訪問相同的物理存儲2.2.共享存儲的特點共享存儲的特點u

50、共享存儲提供了進(jìn)程間共享數(shù)據(jù)的最快途徑u 共享存儲并不在讀寫數(shù)據(jù)之間提供任何同步方法3.3.共享存儲段的創(chuàng)建共享存儲段的創(chuàng)建同消息隊列一樣,需要訪問共享存儲的進(jìn)程必須調(diào)用shmget函數(shù)獲得共享存儲標(biāo)識,也稱為打開/創(chuàng)建一個共享存儲段include int shmget( key_t key, size_t size, int shmflg);共享存儲段的創(chuàng)建示例共享存儲段的創(chuàng)建示例 int shmid; int shmid; key_t key=56789; key_t key=56789; shmid = shmget( key, 1024, IPC_CREAT|0666 ); shmid

51、 = shmget( key, 1024, IPC_CREAT|0666 ); /創(chuàng)建一個關(guān)鍵字為創(chuàng)建一個關(guān)鍵字為5678956789的共享段,并設(shè)置權(quán)限為的共享段,并設(shè)置權(quán)限為06660666,即是所有用戶都能讀和寫,即是所有用戶都能讀和寫該共享段,如果該段已經(jīng)存在,則直接得到其共享段的該共享段,如果該段已經(jīng)存在,則直接得到其共享段的idid int shmid; int shmid; key_t key=56789; key_t key=56789; shmid = shmget( key, 1024, IPC_CREAT|IPC_EXCL|0666 ); shmid = shmget(

52、key, 1024, IPC_CREAT|IPC_EXCL|0666 ); /創(chuàng)建一個關(guān)鍵字為創(chuàng)建一個關(guān)鍵字為5678956789的共享段,并設(shè)置權(quán)限為的共享段,并設(shè)置權(quán)限為06660666,即是所有用戶都能讀,即是所有用戶都能讀和寫該共享段,如果該段已經(jīng)存在,則函數(shù)返回錯誤和寫該共享段,如果該段已經(jīng)存在,則函數(shù)返回錯誤 int shmid; int shmid; key_t key=56789; key_t key=56789; shmid = shmget( key, 0, 0666 ); shmid = shmget( key, 0, 0666 ); /打開一個關(guān)鍵字為打開一個關(guān)鍵字為5

53、678956789的共享段,如果該段已經(jīng)存在,則函數(shù)返回該段的的共享段,如果該段已經(jīng)存在,則函數(shù)返回該段的idid,如果該,如果該段不存在,則函數(shù)返回錯段不存在,則函數(shù)返回錯4.4.共享存儲段的連接共享存儲段的連接當(dāng)進(jìn)程調(diào)用當(dāng)進(jìn)程調(diào)用shmgetshmget創(chuàng)建或者打開一個共享存儲段后,還必須調(diào)用創(chuàng)建或者打開一個共享存儲段后,還必須調(diào)用shmatshmat函數(shù)連接這個共享函數(shù)連接這個共享段到自己的地址空間,然后才能讀寫它段到自己的地址空間,然后才能讀寫它include void * shmat(int shmid, void *shmaddr, int shmflg);返回值:如果成功,則返回

54、共享內(nèi)存段連接到進(jìn)程中的地址。如果失敗,則返回返回值:如果成功,則返回共享內(nèi)存段連接到進(jìn)程中的地址。如果失敗,則返回- 15.5.共享存儲段的分離共享存儲段的分離當(dāng)進(jìn)程不再需要一個共享存儲段時,可以使用函數(shù)當(dāng)進(jìn)程不再需要一個共享存儲段時,可以使用函數(shù)shmdtshmdt將它從進(jìn)程的地址空間分離將它從進(jìn)程的地址空間分離, ,當(dāng)進(jìn)當(dāng)進(jìn)程退出執(zhí)行時,系統(tǒng)會自動分離它連接的所有共享存儲段程退出執(zhí)行時,系統(tǒng)會自動分離它連接的所有共享存儲段include int shmdt( void * shmaddr );返回值:如果失敗,則返回返回值:如果失敗,則返回- 16.6.簡單例程:服務(wù)端向共享段寫入數(shù)據(jù),

55、客戶端從共享段讀出數(shù)據(jù)并顯簡單例程:服務(wù)端向共享段寫入數(shù)據(jù),客戶端從共享段讀出數(shù)據(jù)并顯示示/ 服務(wù)端程序include void main() char c; int shmid; key_t key = 56789; char *shm, *s; if( (shmid = shmget(key, 27, IPC_CREAT|0666) 0 ) 錯誤處理 . . . . /建立共享存儲段 if( ( shm = shmat( shmid,NULL,0) 0 ) 錯誤處理 . . . . / 連接共享存儲段 s = shm; for( c =a; c = z; c+ ) *s = c; s+;

56、*s = NULL; /表示存儲的字符的結(jié)束 while( *shm != * ) sleep(1); exit( 0 );客戶端程序客戶端程序include void main() int shmid; key_t key = 56789; char *shm, *s; if( (shmid = shmget(key, 0, 0666) 0 ) 錯誤處理 . . . . /建立共享存儲段 if( ( shm = shmat( shmid,NULL,0) 0 ) 錯誤處理 . . . . / 連接共享存儲段 for( s= shm; *s != NULL; s+ ) putchar( *s )

57、; putchar(n); *shm = *; / 設(shè)置字符,通知服務(wù)端 shmdt( shm ); / 分離共享存儲段 exit(0); 基本概念基本概念 套接字定義套接字定義 介紹在UNIX環(huán)境下,如何使用套接字API編寫網(wǎng)絡(luò)應(yīng)用程序。大部分網(wǎng)絡(luò)應(yīng)用系統(tǒng)都可以分為客戶端(client)和服務(wù)器端(server),就是通常所說的C/S結(jié)構(gòu),最典型也最常見的,上網(wǎng)就是一種C/S模式的應(yīng)用,瀏覽器ie是客戶端,web服務(wù)器作為服務(wù)器端。 網(wǎng)間進(jìn)程通信網(wǎng)間進(jìn)程通信網(wǎng)間進(jìn)程通信要解決的是不同主機進(jìn)程間的相互通信問題(可把同機進(jìn)程通信看作是其中的特例)。為此,首先要解決的是網(wǎng)間進(jìn)程標(biāo)識問題。同一主機上

58、,不同進(jìn)程可用進(jìn)程號(process ID)唯一標(biāo)識。但在網(wǎng)絡(luò)環(huán)境下,各主機獨立分配的進(jìn)程號不能唯一標(biāo)識該進(jìn)程。 其次,操作系統(tǒng)支持的網(wǎng)絡(luò)協(xié)議眾多,不同協(xié)議的工作方式不同,地址格式也不同。因此,網(wǎng)間進(jìn)程通信還要解決多重協(xié)議的識別問題。 端口端口端口是一種抽象的軟件結(jié)構(gòu)(包括一些數(shù)據(jù)結(jié)構(gòu)和I/O緩沖區(qū))。應(yīng)用程序(即進(jìn)程)通過系統(tǒng)調(diào)用與某端口建立連接(binding)后,傳輸層傳給該端口的數(shù)據(jù)都被相應(yīng)進(jìn)程所接收,相應(yīng)進(jìn)程發(fā)給傳輸層的數(shù)據(jù)都通過該端口輸出。在TCP/IP協(xié)議的實現(xiàn)中,端口操作類似于一般的I/O操作,進(jìn)程獲取一個端口,相當(dāng)于獲取本地唯一的I/O文件,可以用一般的讀寫原語訪問。 端口端

59、口端口號的分配有兩種基本分配方式:全局分配;本地分配TCP/IP端口號的分配中綜合了上述兩種方式。TCP/IP將端口號分為兩部分,少量的作為保留端口,以全局方式分配給服務(wù)進(jìn)程。因此,每一個標(biāo)準(zhǔn)服務(wù)器都擁有一個全局公認(rèn)的端口(即周知口,well-known port),即使在不同機器上,其端口號也相同。剩余的為自由端口,以本地方式進(jìn)行分配。 地址地址 網(wǎng)絡(luò)通信中通信的兩個進(jìn)程分別在不同的機器上。在互連網(wǎng)絡(luò)中,兩臺機器可能位于不同的網(wǎng)絡(luò),這些網(wǎng)絡(luò)通過網(wǎng)絡(luò)互連設(shè)備(網(wǎng)關(guān),網(wǎng)橋,路由器等)連接。因此需要三級尋址:1. 某一主機可與多個網(wǎng)絡(luò)相連,必須指定一特定網(wǎng)絡(luò)地址;2. 網(wǎng)絡(luò)上每一臺主機應(yīng)有其唯一的

60、地址;3. 每一主機上的每一進(jìn)程應(yīng)有在該主機上的唯一標(biāo)識符。 通常主機地址由網(wǎng)絡(luò)ID和主機ID組成,在TCP/IP協(xié)議中用32位整數(shù)值表示;TCP和UDP均使用16位端口號標(biāo)識用戶進(jìn)程。 網(wǎng)絡(luò)字節(jié)順序 不同的計算機存放多字節(jié)值的順序不同,因此為保證數(shù)據(jù)的正確性,在網(wǎng)絡(luò)協(xié)議中須指定網(wǎng)絡(luò)字節(jié)順序。TCP/IP協(xié)議使用16位整數(shù)和32位整數(shù)的高價先存格式。 連接 兩個進(jìn)程間的通信鏈路稱為連接。連接在內(nèi)部表現(xiàn)為一些緩沖區(qū)和一組協(xié)議機制,在外部表現(xiàn)出比無連接高的可靠性。n 半相關(guān) 網(wǎng)絡(luò)中用一個三元組可以在全局唯一標(biāo)志一個進(jìn)程: (協(xié)議,本地地址,本地端口號) 這樣一個三元組,叫做一個半相關(guān)(half-a

溫馨提示

  • 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)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論