1. 程式人生 > >Linux 程序間通訊 --- 訊號通訊

Linux 程序間通訊 --- 訊號通訊

訊號  ( signal ) 機制是 UNIX 系統中最為古老的程序間通訊機制,很多條件可以產生一個訊號.

訊號的產生:           1,當用戶按下某些按鍵時,產生訊號.

          2,硬體異常產生訊號:除數為 0 ,無效的儲存訪問等等.這些情況通常由硬體檢測到,將其通知核心,

                然後核心產生適當的訊號通知程序,例如,核心對正訪問一個無效儲存區的程序產生一個 SIGSEGV 訊號.

          3,程序用 kill 函式 將訊號傳送給另一個程序.

          4,使用者可用 kill 命令將訊號傳送給其他程序.

訊號型別 :

          下面是幾種常見的訊號:           SIGHUP :從終端上發出的結束訊號.           SIGINT   :來自鍵盤的中斷訊號 ( ctrl + c ) .           SIGKILL :該訊號結束接收訊號的程序 .           SIGTERM:kill 命令發出 的訊號.           SIGCHLD:標識子程序停止或結束的訊號.           SIGSTOP:來自鍵盤 ( ctrl + z ) 或除錯程式的停止執行訊號.           

訊號處理:           當某訊號出現時,將按照下列三種方式中的一種進行處理.

          1,忽略此訊號:                     大多數訊號都按照這種方式進行處理,但有兩種訊號卻決不能被忽略.

                    它們是:SIGKILL 和 SIGSTOP . 這兩種訊號不能被忽略的原因是:它們向

                    超級使用者提供了一種終止或停止程序的方法.

          2,執行使用者希望的動作:                     通知核心在某種訊號發生時,呼叫一個使用者函式,在使用者函式中,執行使用者希望的處理.

          3,執行系統預設動作:                     對大多數訊號的系統預設動作是終止該程序.

        當系統捕捉到某個訊號時,可以忽略該訊號或是使用指定的處理函式來處理該訊號,或者使用系統預設的方式.

        訊號處理的主要方法有兩種,一種是使用簡單的 signal 函式,另一個是使用訊號集函式.

訊號傳送 :          訊號傳送的主要函式有 kill 和 raise .

         區別:

          kill 既可以向自身傳送訊號,也可以向其他程序傳送訊號,與 kill 函式不同的是,raise 函式是向   自身   傳送訊號.

          函式:

                    #include < sys/types.h >

                    #include < signal.h >

                    int kill ( pid_t  pai, int signo )

                   int  raise ( int  signo )

           kill 的 pid 引數有四種不同情況:            1, pid  > 0,將訊號傳送給程序 ID 為 pid 的程序.

           2,pid = 0,將訊號傳送給同組的程序.

           3,pid < 0,將訊號傳送給其程序組 ID 等於 pid 絕對值的程序.

           4,pid = -1,將訊號傳送給所有程序.

Alarm訊號鬧鐘:           使用 alarm 函式可以設定一個時間值 ( 鬧鐘時間 ),當所設定的時間到了時,產生 SIGALRM 訊號,

          如果不能撲捉此訊號,則預設動作是終止該程序.

          unsigned int alarm  ( unsigned int seconds )

          經過了指定的 seconds 秒後會產生訊號 SIGALRM,每個程序只能有一個鬧鐘時間,如果在呼叫 alarm 時,以前已為該程序設定過鬧鐘時間,而且它還沒有超時,以前等級的鬧鐘時間則被新值替換;如果有以前登記的尚未超時的鬧鐘時間,而這次 seconds 值是0 ,則表示取消以前的鬧鐘.

          int   pause ( void ):           pause 函式使呼叫程序掛起直至捕捉到一個訊號,只有執行了一個訊號處理函式後,掛起才結束.

         void ( *signal ( int signo  ,   void ( *func ) ( int ) ) ) ( int ):           #include < signal.h >

          void ( *signal ( int signo  ,   void ( *func ) ( int ) ) ) ( int )

          如何理解:

          typedef  void  ( *sighandler_t ) ( int )

          sighandler_t  signal ( int  signum , sighandler_t  handler )

          Func 可能的值是:

          1,SIG_IGN  :忽略此訊號.

          2,SIG_DFL :按照系統預設方式處理.

          3,訊號處理函式名:使用該函式處理.

例項 --- 小測試:

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
 
void my_func(int sign_no)
{
    if(sign_no==SIGINT)
        printf("I have get SIGINT\n");
    else if(sign_no==SIGQUIT)
        printf("I have get SIGQUIT\n");
}
int main()
{
    printf("Waiting for signal SIGINT or SIGQUIT \n ");
    
    /*註冊訊號處理函式*/
    signal(SIGINT, my_func);
    signal(SIGQUIT, my_func);
    
    pause();
    exit(0);
}

        測試方法:在終端下將該程序執行起來,然後 程序pause 了,我們再用 kill 給程序傳送訊號,

       在另一終端下ps aux  可以找到執行程序的程序號.

       然後kill  -s  SIGQUIT  +程序號  我們可以在前一個終端下看到 I have get SIGQUIT.

      SIGUSR1 :

      kill  -usr1 PID .

例項 --- 按鍵驅動非同步通知:

驅動非同步通知應用程式.

1,應用程式 註冊訊號處理函式  signal ( SIGIO ,  my_signal_fun ) :

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
 
int fd;        //全域性變數;
 
/*
應用程式 不會主動 的去讀鍵值;
my_signal_fun 什麼時候被呼叫呢?
在驅動程式的中斷處理函式 static irqreturn_t buttons_irq(int irq, void *dev_id) 中,
有訊號傳送函式 kill_fasync (&button_async, SIGIO, POLL_IN) ;
當有按鍵按下時候,就會給應用程式傳送一個訊號;
這個訊號就會觸發 應用程式 呼叫訊號處理函式 signal(SIGIO, my_signal_fun); 
訊號處理函式指向了 void my_signal_fun(int signum) 函式;
*/
 
void my_signal_fun(int signum)
{
    unsigned char key_val;
    read(fd, &key_val, 1);    //讀取鍵值;
    printf("key_val: 0x%x\n", key_val);  
}
 
int main(int argc, char **argv)
{
    int Oflags;
 
    signal(SIGIO, my_signal_fun);   /* 應用程式註冊訊號處理函式 */
                    //  SIGIO 表示 Io 有資料可供讀取;
    
    fd = open("/dev/buttons", O_RDWR | O_NONBLOCK);
    if (fd < 0)
    {
        printf("can't open!\n");
        return -1;
    }
                    /* 驅動程式發訊號 */
    fcntl(fd, F_SETOWN, getpid());   /* 訊號發給誰,是通過這段程式告訴核心的 */
                    /* getpid() 獲取應用程式PID */
    
                    /* 改變 Oflags 為非同步通知 FASYNC*/
    Oflags = fcntl(fd, F_GETFL);    //讀取 Oflags;
                    //改變 Oflags;
    fcntl(fd, F_SETFL, Oflags | FASYNC);  //這樣,驅動程式裡面的 .fasync  =  sixth_drv_fasync, 函式指標就會被呼叫;
                         // 改變 FASYNC標誌, 最終呼叫到驅動的 fasync -> fasync_helper 
                        //來初始化/或/釋放 fasync_struct;
    while (1)
    {
        sleep(1000);
    }
    
    return 0;
}

2,誰發訊號:驅動程式:

3,發給誰:應用程式  ;  應用程式要告訴驅動程式 其PID:

4,驅動程式怎麼發訊號:呼叫函式:kill_fasync :

在驅動程式開頭 定義結構 fasync_struct :

static struct fasync_struct *button_async;

初始化 fasync_struct 結構體 使用 fasync_helper : 在應用程式呼叫 .fasync  =  sixth_drv_fasync, 時候,呼叫 fasync_helper 函式 來初始化  struct fasync_struct *button_async  結構體;

struct fasync_struct *button_async  這個結構在 發訊號的時候 kill_fasync (&button_async, SIGIO, POLL_IN)  能用的到;

static int sixth_drv_fasync (int fd, struct file *filp, int on)
{
    printk("driver: sixth_drv_fasync\n");
    return fasync_helper (fd, filp, on, &button_async);  /* 初始化結構體 */
}
 
static struct file_operations sencod_drv_fops = {
    .owner   =  THIS_MODULE,    /* 這是一個巨集,推向編譯模組時自動建立的__this_module變數 */
    .open    =  sixth_drv_open,     
    .read     =    sixth_drv_read,       
    .release =  sixth_drv_close,
    .poll    =  sixth_drv_poll,
    .fasync     =  sixth_drv_fasync,   /****************************  FASYNC  ********************************/
};

在按鍵驅動 服務程式 中傳送訊號 kill_fasync : kill_fasync 需要三個引數:

第一個:&button_async 包含程序 ID , 也就是 發給誰,

第二個:  發什麼,發  SIGIO 這個訊號,SIGIO 表示 Io 有資料可供讀取;

第三個:POLL_IN , 原因, 表示有資料在等待讀取;

static irqreturn_t buttons_irq(int irq, void *dev_id)
{
    struct pin_desc * pindesc = (struct pin_desc *)dev_id;
    unsigned int pinval;
    
    pinval = s3c2410_gpio_getpin(pindesc->pin);
 
    if (pinval)
    {
        /* 鬆開 */
        key_val = 0x80 | pindesc->key_val;
    }
    else
    {
        /* 按下 */
        key_val = pindesc->key_val;
    }
 
        ev_press = 1;                  /* 表示中斷髮生了 */
        wake_up_interruptible(&button_waitq);   /* 喚醒休眠的程序 */
    
    kill_fasync (&button_async, SIGIO, POLL_IN);                      /********  kill_fasync 傳送訊號  *********/
    
    return IRQ_RETVAL(IRQ_HANDLED);
}

總結: 為了使得裝置支援非同步通知機制,驅動程式最終涉及以下 3 項工作:

1,支援 F_SETOWN 命令 : 能在這個控制命令處理中設定 filp->f_owner 為對應程序 ID;

此項操作由核心完成,裝置驅動無須處理;

2,支援 F_SETFL 命令的處理: 當 FASYNC 標誌改變的時候,驅動操作中的 fasync () 函式將得以執行;

驅動程式實現 fasync ()  函式;

3,呼叫 kill_fasync () 函式: 在裝置資源可獲得時, 呼叫 kill_fasync () 函式激發相應的訊號.