從windows到linux的轉換(1):訊號與訊息以及SIGPIPE 的處理__linux

來源:互聯網
上載者:User

一.訊號與訊息
     linux裡邊的訊號和win下邊的的訊息有基本相同的同能,都有各種各樣的訊號(訊息)及其相應的訊號(訊息)處理函數。
    1. 訊號處理函數添加。
     signal函數。
    下邊是從進階編程上拷貝來。平平無奇,一看就明白了。
    定義:
        typedef void Sigfunc(int);
        Sigfunc *signal(int, Sigfunc *);
        將一個訊號和他的處理函數綁定在一起。非常簡單的東西。 在wind下,用了很多遍了把。
        需要注意的是他的傳回值:
        #define SIG_ERR (void (*)())-1
        #define SIG_DFL  (void (*)())  0
        #define SIG_IGN  (void (*)())  1
 
        以前曾經講過,一定要注意傳回值的處理。一定要判斷函數的傳回值。
                   
    2. 所有的wind message都是可以截獲的,但是Linux下,SIGSTOP,SIGKILL訊號是不能被截獲的。保留這兩個訊號不被截獲是為了讓系統,使用者,可以直接kill掉任何的進程(當然是有許可權殺的進程).
   
    3. 系統函數是可以被訊號打斷的。
        進階編程這樣寫道:
            進程捕捉到訊號並繼續執行時,它首先執行該訊號處理常式中的指令。如果從訊號處理常式返回(例如沒有調用e x i t或l o n g j m p),則繼續執行在捕捉到訊號時進程正在執行的正常指令序列(這類似於硬體中斷髮生時所做的)。但在訊號處理常式中,不能判斷捕捉到訊號時進程執行到何處。
                  
            也就是說,系統不給你保留當時的棧環境,直接跳出正在執行的系統函數。這與對硬中斷的處理(中斷環境儲存,執行中斷代碼,中斷環境恢複)是不同的。
                  
            比如說,sleep(60)的過程中,進程收到一個訊號。這個訊號會打斷這個函數的執行,也就是沒有睡夠,睡了20秒,30秒就出來了。這個造成的後果還小一點,你想想,要是在寫檔案的過程中,寫了一半就被打斷,那會出現什麼後果。            
                  
            我們常用的對這個事情的處理辦法:          
            1. 屏蔽掉不需要的或者所有的的訊號.
                sigset_t sets;//一個空的sigset集合。
                sigfillset(&sets);//填充所有的訊號。
                sigprocmask(SIG_BLOCK, &sets, 0);
                順便介紹一下sigprocmask的一個參數。需要注意,進程有當前的阻塞訊號的集合。對集合的操作。
                    SIG_BLOCK:
                        The resulting set shall be the union of the current set and the signal set pointed to by set.
                    SIG_SETMASK:
                        The resulting set shall be the signal set pointed to by set.
                    SIG_UNBLOCK
                        The resulting set shall be the intersection of the current set and the complement of the signal set pointed to by set.
        
                所有的訊號都屏蔽了,那麼如何用訊號讓程式正常退出那。 linux可不比 windows還有一個關閉按鈕讓你按。大多數程式得用到訊號讓程式正常退出,而且不能把程式直接kill掉,因正常退出的過程中通常需要做一部份(比如清理等)工作。這時,你可以讓程式阻塞等待一個被屏蔽掉的訊號上,通常是,SIGTERM.有點相當於windows裡邊的close訊息。
                    while( sigwaitinfo(&sets, 0) != SIGTERM );
                   
                SIGTERM訊號來了,程式準備退出。呵呵。
                          
                第二個參數是sig的info,不用的話設定為NULL. 用的話,自己查協助。
               
                sigtimedwait只不過加了一個時間參數。不過,linux下的逾時和win下邊的不太一樣。他的逾時是與現在的時間作對比的。下邊是我寫的一個函數。用來獲得逾時的時間struct timespec結構。
               
                int getTimeOut(int sec,long nsec,struct timespec& abstimeout)
                {
                     struct timeval now;
                    
                     gettimeofday(&now, 0);
                     abstimeout.tv_sec = now.tv_sec + sec;
                     abstimeout.tv_nsec = (now.tv_usec * 1000) + nsec;
                     if (abstimeout.tv_nsec >= 1000000000)
                     {
                           abstimeout.tv_sec++;
                           abstimeout.tv_nsec -= 1000000000;
                     }
                    
                     return 0;
                }
                                        
                                        
                一般有這這種需要的程式,都是在主線程裡邊屏蔽掉所有訊號,然後,在另外的線程裡邊阻塞等待一個訊號。那麼,其他線程的函數繼承主線程裡邊的訊號屏蔽的辦法,屏蔽掉所有訊號。不過,用gdb調試的時候,gdb產生的訊號,用這些辦法是攔不住的。這種辦法只能攔住系統發來的訊號。

                                 
            2. 對傳回值進行處理。 "手工" 支援這種非強制中斷。
                對可能中斷的函數做應用層級的處理,例如:
                   
                                     while( ::close(fd) == -1 && errno == EINTR );
                                    
                sleep被中斷,會返回剩餘的秒數.write會返回已經正確寫入的位元組數目。等等。利用這些資訊,可以做一下函數重入,只是寫起來比較麻煩。
                           
                           
               
                需要注意的是:在Linux裡邊,系統函數分成可再入的和不可再入的。區分的原則是這個函數是否更改了全域資料結構。也就是,這個函數在重入以前,要考慮上一次進入的時候,對全域資料結構的更改。也就是需要的話,自己做點環境儲存和恢複。比如:
               
                進階編程這麼寫到:
                    要瞭解在訊號處理常式中即使調用列於表1 0 - 3中的函數,因為每個進程只有一個e r r n o變數,所以我們可能修改了其原先的值。考慮一個訊號處理常式,它恰好在m a i n剛設定e r r n o之後被調用。如果該訊號處理常式調用r e a d,則它可能更改e r r n o的值,從而取代了剛由m a i n設定的值。因此,作為一個通用的規則,當在訊號處理常式中調用表1 0 - 3中列出的函數時,應當在其前儲存,在其後恢複errno。
                                                       
                       
               還需要注意的是,sigaction裡邊有個參數,SIG_RESTART. 他的意思是,當函數被訊號中斷以後,系統可以自動重新調用這個函數一遍。而且有的版本還不支援這個參數。
              
               試著想像一下:參數不變的條件下,你的write函數重新調用一遍會出現什麼後果。
              
               進階編程裡邊是這麼講的:
                    P O S I X . 1允許實現再起動系統調用,但這並不是必需的。系統V的預設工作方式是不再起動系統調用。但是S V R 4使用s i g a c t i o n時(見1 0 . 1 4節),可以指定SA_RESTART選擇項以再起動由該訊號中斷的系統調用。在4 . 3 + B S D中,系統調用的再起動依賴於調用了哪一個函數設定訊號處理方式配置。早期的與4 . 3 B S D相容的s i g v e c函數使被該訊號中斷的系統調用自動再起動。但是,使用較新的與POSIX . 1相容的sigaction則不使它們再起動。但如同在S V R 4中一樣,在sigaction中可以使用SA_RESTART選擇項,使核心再起動由該訊號中斷的系統調用。
                           
                  
               在windows下,你不用擔心訊息會中斷系統調用。不過,也可以認為喪失的是對非強制中斷訊號的的快速相應。

二. 幾個特殊的訊號
    1. SIGPIPE:
    在linux下寫socket的程式的時候,如果嘗試send到一個disconnected socket上,就會讓底層拋出一個SIGPIPE訊號。
    這個訊號的預設處理方法是退出進程,大多數時候這都不是我們期望的。因此我們需要重載這個訊號的處理方法。調用以下代碼,即可安全的屏蔽SIGPIPE:
        struct sigaction sa;
        sa.sa_handler = SIG_IGN;
        sigaction( SIGPIPE, &sa, 0 );
   
    把這幾行代碼,看作是windows下的WSAStartUp()就好了。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.