Linux_訊號與訊號量
訊號:
訊號機制是類UNIX系統中的一種重要的程序間通訊手段之一。我們經常使用訊號來向一個程序傳送一個簡短的訊息。例如:假設我們啟動一個程序通過socket讀取遠端主機發送過來的網路資料包,此時由於網路因素當前主機還沒有收到相應的資料,當前程序被設定為可中斷等待狀態(TASK_INTERRUPTIBLE),此時我們已經失去耐心,想提前結束這個程序,於是可以通過kill命令想這個程序傳送KILL訊號,核心會喚醒該程序,執行它的訊號處理函式,KILL訊號的預設處理是退出該程序。
另外應用程式可以通過signal()等函式來為一個訊號設定預設處理函式。例如當用戶按下CTRL+C時,shell將會發出SIGINT訊號,SIGINT的預設處理函式是執行程序的退出程式碼,如下所示:
可以通過類似下面的命令顯式的給一個程序傳送一個訊號:
kill -2 pid事實上,程序也不知道訊號到底什麼時候到達。訊號是非同步的,一個程序不可能等待訊號的到來,也不知道訊號會到來,那麼,程序是如何發現和接受訊號呢?實際上,訊號的接收不是由使用者程序來完成的,而是由核心代理。當一個程序P2向另一個程序P1傳送訊號後,核心接受到訊號,並將其放在P1的訊號隊列當中。當P1再次陷入核心態時,會檢查訊號佇列,並根據相應的訊號調取相應的訊號處理函式。
訊號檢測和響應時機
剛才我們說,當P1再次陷入核心時,會檢查訊號佇列。那麼,P1什麼時候會再次陷入核心呢?陷入核心後在什麼時機會檢測訊號佇列呢?
- 當前程序由於系統呼叫、中斷或異常而進入系統空間以後,從系統空間返回到使用者空間的前夕。
- 當前程序在核心中進入睡眠以後剛被喚醒的時候(必定是在系統呼叫中),或者由於不可忽略訊號的存在而提前返回到使用者空間。
進入訊號處理函式
發現訊號後,根據訊號向量,知道了處理函式,那麼該如何進入訊號處理程式,又該如何返回呢?
我們知道,使用者程序提供的訊號處理函式是在使用者態裡的,而我們發現訊號,找到訊號處理函式的時刻處於核心態中,所以我們需要從核心態跑到使用者態去執行訊號處理程式,執行完畢後還要返回核心態。
#include <signal.h> #include <stdio.h> void int_handler(int signum) { printf("\nSIGINT signal handler.\n"); printf("exit.\n"); exit(-1); } int main() { signal(SIGINT, int_handler); printf("int_handler set for SIGINT\n"); while(1) { printf("go to sleep.\n"); sleep(60); } return 0; }
訊號量:
一.什麼是訊號量訊號量的使用主要是用來保護共享資源,使得資源在一個時刻只有一個程序(執行緒)所擁有。訊號量的值為正的時候,說明它空閒。所測試的執行緒可以鎖定而使用它。若為0,說明它被佔用,測試的執行緒要進入睡眠佇列中,等待被喚醒。
二.訊號量的分類
(1) 核心訊號量,由核心控制路徑使用
(2)使用者態程序使用的訊號量,這種訊號量又分為POSIX訊號量和SYSTEM V訊號量。
1、POSIX訊號量又分為有名訊號量和無名訊號量:
(1)有名訊號量,其值儲存在檔案中,所以它既可以用於執行緒,也可以用於相關程序間,甚至是不相關程序。
(2)無名訊號量,其值儲存在記憶體中。無名訊號量常用於多執行緒間的同步,同時也用於相關程序間的同步。也就是說,無名信號量必須是多個程序(執行緒)的共享變數,無名訊號量要保護的變數也必須是多個程序(執行緒)的共享變數,這兩個條件是缺一不可的。
倘若對訊號量沒有以上的全面認識的話,你就會很快發現自己在訊號量的森林裡迷失了方向。
POSIX 訊號量與SYSTEM V訊號量的比較:
1. 對POSIX來說,訊號量是個非負整數。常用於執行緒間同步。而SYSTEM
V訊號量則是一個或多個訊號量的集合,它對應的是一個訊號量結構體,這個結構體是為SYSTEM V IPC服務的,訊號量只不過是它的一部分。常用於程序間同步。
2.POSIX訊號量的引用標頭檔案是“<semaphore.h>”,而SYSTEM V訊號量的引用標頭檔案是“<sys/sem.h>”。
3.從使用的角度,System V訊號量是複雜的,而Posix訊號量是簡單。比如,POSIX信號量的建立和初始化或PV操作就很非常方便。
無名訊號量:
#include <pthread.h>
#include <semaphore.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
int number; // 被保護的全域性變數
sem_t sem_id;
void* thread_one_fun(void *arg)
{
sem_wait(&sem_id);
printf("thread_one have the semaphore\n");
number++;
printf("number = %d\n",number);
sem_post(&sem_id);
}
void* thread_two_fun(void *arg)
{
sem_wait(&sem_id);
printf("thread_two have the semaphore \n");
number--;
printf("number = %d\n",number);
sem_post(&sem_id);
}
int main(int argc,char *argv[])
{
number = 1;
pthread_t id1, id2;
sem_init(&sem_id, 0, 1);
pthread_create(&id1,NULL,thread_one_fun, NULL);
pthread_create(&id2,NULL,thread_two_fun, NULL);
pthread_join(id1,NULL);
pthread_join(id2,NULL);
printf("main,,,\n");
return 0;
}
有名訊號量:
//File1: server.c </u>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define SHMSZ 27
char SEM_NAME[]= "vik";
int main()
{
char ch;
int shmid;
key_t key;
char *shm,*s;
sem_t *mutex;
//name the shared memory segment
key = 1000;
//create & initialize semaphore
mutex = sem_open(SEM_NAME,O_CREAT,0644,1);
if(mutex == SEM_FAILED)
{
perror("unable to create semaphore");
sem_unlink(SEM_NAME);
exit(-1);
}
//create the shared memory segment with this key
shmid = shmget(key,SHMSZ,IPC_CREAT|0666);
if(shmid<0)
{
perror("failure in shmget");
exit(-1);
}
//attach this segment to virtual memory
shm = shmat(shmid,NULL,0);
//start writing into memory
s = shm;
for(ch='A';ch<='Z';ch++)
{
sem_wait(mutex);
*s++ = ch;
sem_post(mutex);
}
//the below loop could be replaced by binary semaphore
while(*shm != '*')
{
sleep(1);
}
sem_close(mutex);
sem_unlink(SEM_NAME);
shmctl(shmid, IPC_RMID, 0);
exit(0);
}
//File 2: client.c</u>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define SHMSZ 27
char SEM_NAME[]= "vik";
int main()
{
char ch;
int shmid;
key_t key;
char *shm,*s;
sem_t *mutex;
//name the shared memory segment
key = 1000;
//create & initialize existing semaphore
mutex = sem_open(SEM_NAME,0,0644,0);
if(mutex == SEM_FAILED)
{
perror("reader:unable to execute semaphore");
sem_close(mutex);
exit(-1);
}
//create the shared memory segment with this key
shmid = shmget(key,SHMSZ,0666);
if(shmid<0)
{
perror("reader:failure in shmget");
exit(-1);
}
//attach this segment to virtual memory
shm = shmat(shmid,NULL,0);
//start reading
s = shm;
for(s=shm;*s!=NULL;s++)
{
sem_wait(mutex);
putchar(*s);
sem_post(mutex);
}
//once done signal exiting of reader:This can be replaced by
another semaphore
*shm = '*';
sem_close(mutex);
shmctl(shmid, IPC_RMID, 0);
exit(0);
}
System V訊號量:
int sem_id = 0; /* semget的返回值,全域性 */
#define MUTEX 0 /* 用於返回臨界區的訊號量在集合中的序數 */
#define NUM_SEM 1 /* 集合中訊號量的個數 */
#define SEM_KEY 0x11223344 /*保證核心中的唯一性 */
void P(int sem_num)
{
struct sembuf sem;
sem.sem_num = MUTEX;
sem.sem_op = -1;
sem.sem_flg = 0;
if( -1 == semop(sem_id, &sem, 1) )
{
/* 錯誤處理 */
}
}
void V(int sem_num)
{
struct sembuf sem;
sem.sem_num = MUTEX;
sem.sem_op = 1;
sem.sem_flg = 0;
if( -1 == semop(sem_id, &sem, 1) )
{
/* 錯誤處理 */
}
}
主函式:
int main()
{
...
int semid;
....
semid = semget(SEM_KEY, 0, 0); /* panduan 判斷該型號量組是否已經存在 */
if( -1 == semid )
{
semid = semget(SEM_KEY, NUM_SEM, IPC_CREAT | IPC_EXCL | 0666);
if( -1 == semid)
{
/* 錯誤處理 */
}
else
{
semctl(sem_id, MUTEX, SETVAL, 1); /* 初始值為1。錯誤處理略 */
}
}
...
P(MUTEX);
/* 臨界區程式碼段 */
V(MUTEX);
...
}
最全面的linux訊號量解析:http://blog.csdn.net/qinxiongxu/article/details/7830537
訊號只是一個數字,數字為0-31表示不同的訊號,如下表所示。
編號 |
訊號名 |
預設動作 |
說明 |
1 |
SIGHUP |
程序終止 |
終端斷開連線 |
2 |
SIGINT |
程序終止 |
使用者在鍵盤上按下CTRL+C |
3 |
SIGQUIT |
程序意外結束(Dump) |
使用者在鍵盤上按下CTRL+\ |
4 |
SIGILL |
程序意外結束(Dump) |
遇到非法指令 |
5 |
SIGTRAP |
程序意外結束(Dump) |
遇到斷電,用於除錯 |
6 |
SIGABRT/SIGIOT |
程序意外結束(Dump) |
|
7 |
SIGBUS |
程序意外結束(Dump) |
匯流排錯誤 |
8 |
SIGFPE |
程序意外結束(Dump) |
浮點異常 |
9 |
SIGKILL |
程序終止 |
其他程序傳送SIGKILL將導致目標程序終止 |
10 |
SIGUSR1 |
程序終止 |
應用程式可自定義使用 |
11 |
SIGSEGV |
程序意外結束(Dump) |
非法的記憶體訪問 |
12 |
SIGUSR2 |
程序終止 |
應用程式可自定義使用 |
13 |
SIGPIPE |
程序終止 |
管道讀取端已經關閉,寫入端程序會收到該訊號 |
14 |
SIGALRM |
程序終止 |
定時器到時 |
15 |
SIGTERM |
程序終止 |
傳送該訊號使目標程序終止 |
16 |
SIGSTKFLT |
程序終止 |
堆線錯誤 |
17 |
SIGCHLD |
忽略 |
子程序退出時會向父程序傳送該訊號 |
18 |
SIGCONT |
忽略 |
程序繼續執行 |
19 |
SIGSTOP |
程序暫停 |
傳送該訊號會使目標程序進入TASK_STOPPED狀態 |
20 |
SIGTSTP |
程序暫停 |
在終端上按下CTRL+Z |
21 |
SIGTTIN |
程序暫停 |
後臺程序從控制終端讀取資料 |
22 |
SIGTTOU |
程序暫停 |
後臺程序從控制終端讀取資料 |
23 |
SIGURG |
忽略 |
socket收到設定緊急指標標誌的網路資料包 |
24 |
SIGXCPU |
程序意外結束(Dump) |
程序使用CPU已經超過限制 |
25 |
SIGXFSZ |
程序意外結束(Dump) |
程序使用CPU已經超過限制 |
26 |
SIGVTALRM |
程序終止 |
程序虛擬定時器到期 |
27 |
SIGPROF |
程序終止 |
程序Profile定時器到期 |
28 |
SIGMNCH |
忽略 |
程序終端視窗大小改變 |
29 |
SIGIO |
程序暫停 |
用於非同步IO |
29 |
SIGPOLL |
程序暫停 |
用於非同步IO |
30 |
SIGPWR |
程序暫停 |
電源失效 |
31 |
SIGUNUSED |
程序暫停 |
保留未使用 |