Linux 程序間通訊 --- 訊號通訊
訊號 ( signal ) 機制是 UNIX 系統中最為古老的程序間通訊機制,很多條件可以產生一個訊號.
訊號的產生: 1,當用戶按下某些按鍵時,產生訊號.
2,硬體異常產生訊號:除數為 0 ,無效的儲存訪問等等.這些情況通常由硬體檢測到,將其通知核心,
然後核心產生適當的訊號通知程序,例如,核心對正訪問一個無效儲存區的程序產生一個 SIGSEGV 訊號.
3,程序用 kill 函式 將訊號傳送給另一個程序.
4,使用者可用 kill 命令將訊號傳送給其他程序.
訊號型別 :
訊號處理: 當某訊號出現時,將按照下列三種方式中的一種進行處理.
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 () 函式激發相應的訊號.