1. 程式人生 > 其它 >《Unix/Linux系統程式設計》第6章學習筆記 20191329馬靜怡

《Unix/Linux系統程式設計》第6章學習筆記 20191329馬靜怡

第6章 訊號和訊號處理

訊號和中斷

中斷是從I/O裝置或協處理器傳送到CPU的外部請求,將CPU從正常執行轉移到中斷處理。訊號是傳送給程序的請求,將程序從正常執行轉移到中斷處理。
(1)人員中斷
人員的每個動作函式都是通過本能或經驗實現的,每個中斷都分配由一個唯一的ID識別號和一個預先安裝的動作函式,人在收到中斷請求時“執行”動作函式。

  • 來自硬體的中斷
  • 來自其他人的中斷
  • 自己造成的中斷
  • 不可遮蔽(NMI)
  • 可遮蔽

(2)程序中斷
每個程序中斷都被轉換為一個唯一ID號,傳送給程序。Unix/Linux中的程序中斷成為訊號,編號為1—31.程序也可遮蔽某些型別的訊號以推遲處理,必要時可能修改訊號動作函式。

  • 來自硬體的中斷:終端、間隔定時器的“^C”組合鍵等。
  • 來自其他程序的中斷:kill(pid,SIG#)、death_of_child等。
  • 自己造成的中斷:除以0、無效地址等。

(3)硬體中斷
每個中斷都有唯一的中斷向量號。CPU不會導致任何自己造成的中斷(除非出錯)。

  • 來自硬體的中斷:定時器、I/O裝置等。
  • 來自其他處理器的中斷:FFP、DMA、多處理器系統中的其他CPU。
  • 自己造成的中斷:除以0、保護錯誤、INT指令。

(4)程序的陷阱錯誤
程序可能會自己造成中斷,是由被CPU識別為異常的錯誤引起的(除以0、無效地址、非法指令、越權……),它會陷入作業系統核心,將陷阱原因轉換為訊號編號傳送給自己。

Unix/Linux訊號示例

  • Ctrl+C組合鍵:轉換為SIGINT(2)訊號,傳送給終端,終止當前執行的程序。程序對大多數訊號的預設操作是呼叫核心的kexit(exitValue)函式來終止。
  • 使用者可使用nohup a.out命令在後臺執行程式,nohup會使sh復刻子程序來執行程式,但子程序會忽略SIGNUP(1)訊號。
  • 通過ps -u LTD發現後臺程序仍在執行,可使用kill pid (or kill -s 9 pid)殺死程序。執行殺死的程序向pid標識的目標程序傳送一個SIGTERM(15)訊號,請求它死亡。
    如果程序忽略該訊號,可能拒絕死亡,可以使用kill -s 9 pid
    必能殺死。

Unix/Linux中的訊號處理

1.訊號型別

Unix/Linux支援31種不同的訊號,每種訊號在signal.h檔案中都有定義,每種訊號都有一個符號名。

2.訊號來源

  • 來自硬體中斷的訊號
    中斷健(Ctrl+C)產生一個SIGINT(2)訊號
    間隔定時器:SIGALRM(14)、SIGVTALRM(26)或SIGPROF(27)
    其他硬體錯誤:匯流排錯誤、I/O陷阱等
  • 來自異常的訊號
    SIGFPE(8):浮點異常(除以0)
    SIGSEGV(11):段錯誤
  • 來自其他程序的訊號
    可使用kill(pid,sig)系統呼叫向pid標識的目標程序傳送訊號。

3.安裝訊號捕捉函式

程序可使用系統呼叫:

int r = signal(int signal_number, void *handler);

來修改選定訊號編號的處理函式。

訊號處理步驟

  • 處於核心模式會檢查思念好並處理未完成的訊號。
  • 重置使用者安裝的訊號捕捉函式
  • 訊號和喚醒

訊號與異常

  • 程序遇到異常,會陷入核心模式。若在核心模式下發生異常,則列印一條PANIC錯誤資訊並停止;若在使用者模式下發生異常,則終止並以記憶體轉儲進行除錯。
  • 讓程序通過預先安裝的訊號捕捉函式處理使用者模式下的程式錯誤。
  • 特殊情況下,會讓某個程序通過訊號殺死另一個程序。

Linux中的IPC

  • 管道和FIFO
  • 訊號
  • System V IPC
  • POSIX訊息佇列
  • 執行緒同步機制
  • 套接字

實踐內容

段錯誤捕捉函式

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<setjmp.h>
jmp_buf env;
int count = 0;
void handler(int sig,siginfo_t *siginfo,void *context)
{
printf("handler:sig=&d from PID=%d UID=%d count=%d\n",sig,siginfo->si_pid,siginfo->si_uid,++count);
if (count>=4)
longjmp(env,1234);
}
int BAD()
{
int *ip=0;
printf("in BAD():try to dereference NULL pointer\n");
*ip=123;
printf("should not see this line\n");
}
int main(int argc,char *argv[])
{
int r;
struct sigaction act;
memset (&act,0,sizeof(act));
act.sa_sigaction = &handler;
act.sa_flags=SA_SIGINFO;
sigaction(SIGSEGV, &act,NULL);
if((r=setjmp(env))==0)
BAD();
else
printf("proc %d survived SEGMENTATION FAULT:r=%d\n",getpid(),r);
printf("proc %d looping\n");
while(1);
}

實現一個訊息IPC

#include<stdio.h>
#include<signal.h>
#include<string.h>
#define LEN 64
int ppipe[2];
int pid;
char line[LEN];
int parent()
{
printf("parent %d running\n",getpid());
close(ppipe[0]);
while(1){
printf("parent %d: input a line : \n",getpid());
fgets(line,LEN,stdin);
line[strlen(line)-1]=0;
printf("parent %d write to pipe\n",getpid());
write(ppipe[1],line,LEN);
printf("parent %d send signal 10 to %d\n",getpid(),pid);
kill(pid,SIGUSR1);
}
}
void chandler(int sig)
{
printf("\nchild %d got an interrupt sig=%d\n",getpid(),sig);
read(ppipe[0],line,LEN);
printf("child %d get a message = %s\n",getpid(),line);
}
int child()
{
char msg[LEN];
int parent = getppid();
printf("child %d running\n",getpid());
close(ppipe[1]);
signal(SIGUSR1,chandler);
while(1);
}
int main()
{
pipe(ppipe);
pid=fork();
if(pid)
parent();
else
child();
}