UNIX環境高級編程,linux信號(一)--unix環境高級編程讀書筆記

 2023-11-19 阅读 31 评论 0

摘要:???? 1.信號的概念 ???? 在這里要給出一個信號的準確概念感覺很困難,可以這么說,信號就是進程之間或者內核與進程間異步通信的一種機制,有點類似于中斷的性質。在? linux? 系統中有? 31? 種信號,每一種信號都以? SIG? 三個字母開頭,例如?

???? 1.信號的概念

???? 在這里要給出一個信號的準確概念感覺很困難,可以這么說,信號就是進程之間或者內核與進程間異步通信的一種機制,有點類似于中斷的性質。在? linux? 系統中有? 31? 種信號,每一種信號都以? SIG? 三個字母開頭,例如? SIGABRT? 是夭折信號,就是調用? abort? 函數產生的信號,SIGALRM? 是調用? alarm? 函數定時時間溢出后產生的信號。

???? 對每一個信號,系統有三種處理方式:

1.忽略這種信號,就是對這種信號不做任何處理。但是,對于 ?SIGKILL ?和 ?SIGSTOP ?信號,卻不能忽略。因為這兩種信號,通常被操作系統用來終止失去控制的進程。
2.捕捉該信號。當一個進程收到該信號時,就調用相應的信號處理函數,來對該信號做出相應的動作。
3.按系統默認方式處理信號,一般的默認動作是終止程序。

???? 2.signal函數

UNIX環境高級編程、???? 當需要捕捉某種信號時,需要用到? signal? 函數來注冊處理該信號的函數。這個? signal? 函數比較特殊,它的返回值是一個函數指針,這個函數指針指向之前處理該信號的函數。它有兩個參數,第一個參數用來指定信號,第二個參數用來指定處理該信號的函數。它的函數原型如下:

       #include <signal.h>typedef void (*sighandler_t)(int);sighandler_t signal(int signum, sighandler_t handler);

舉一個簡單的? signal? 的使用例子如下:
#include <signal.h>
#include <stdio.h>static void sig_usr(int signo);int main(void)
{if(signal(SIGUSR1,sig_usr)==SIG_ERR){printf("signal(SIGUSR1) error\n");return -1;}if(signal(SIGUSR2,sig_usr)==SIG_ERR){printf("signal(SIGUSR2) error\n");return -1;}for(;;){pause();}return 0;
}static void sig_usr(int signo)
{if(signo==SIGUSR1){printf("received SIGUSR1\n");}else if(signo==SIGUSR2){printf("received SIGUSR2\n");}else{printf("received signal:%d\n",signo);}
}
將程序編譯為? a.out? 之后,用下面的命令在后臺運行:
./a.out &

在終端上輸出程序的PID如下:

[1] 6212
我們輸入如下命令:
kill -SIGUSR1 6212

可以看到程序輸出
received SIGUSR1

???? 3.可再入函數

實現shell。???? 在信號處理的過程中會產生一些問題,例如,當在? main? 函數中正在調用? malloc? 函數在堆上動態分配內存空間的時候,產生了一個信號,需要去執行這個信號的信號處理函數。而在這個信號處理函數中,也需要調用? malloc? 函數,這時候就有可能發生問題。因為? malloc? 為它分配的存儲區保存一個鏈接表,而如果在main函數處理這張鏈接表時,在信號處理函數調用了? malloc,就使進程遭到了破壞。

???? 再舉一個例子,如果在? main? 函數中,剛剛調用完? getpwnam? 函數,這時候來了一個信號,需要去執行信號處理函數,在這個函數中也許要調用? getpwnam? 函數,這就造成了? main? 函數調用? getpwnam? 得到的信息丟失。

???? 通過上面的兩個例子產生的問題,linux? 規定了一個可再入函數表,這個表中列舉了在信號處理函數中可以調用的函數。當然這張表中肯定沒有包括? malloc? 函數和? getpwnam? 函數,這些函數有另一個名字,叫不可再入函數。

???? 就算對于可再入函數,我們知道每一個進程只有一個? errno ,如果在? main? 函數中設定了 errno? 的值以后,如果在信號處理程序中調用的某個可再入函數也可能會修改了? errno? 的值。所以要求我們在信號處理程序前保存現場,在信號處理程序要結束時,恢復現場。

???? 4.kill和raise以及幾個術語

linux 安全????? kill函數用來向指定的進程或者進程組發送信號,raise函數用來向進程自己發送信號。進程只能向和它所有者相同的進程發送信號,當然超級用戶進程可以向各個進程發送信號。

???? 信號的產生:當造成信號的事件發生時,為進程產生一個信號。

???? 當產生了信號以后,內核會在進程表中設置某種形式的標記,這個過程稱為信號遞送。

???? 在信號產生和信號遞送這個時間,稱為信號未決。

???? 5.alarm函數和pause函數

linux調試工具gdb。???? alarm? 函數用來定時一段時間,當定時時間溢出時,會產生? SIGALRM? 信號。pause? 函數用來使進程掛起,直到捕捉到一個信號,并且等待執行完信號處理函數之后,pause? 函數才會返回? -1? 。它們的函數原型如下:

       #include <unistd.h>unsigned int alarm(unsigned int seconds);int pause(void);
alarm? 函數的參數單位是秒,它的返回值是上一次調用? alarm? 時剩余的時間。當參數? seconds? 為? 0? 時,如果上次的定時時間還未到,則取消上次的定時,這是一個很常用的功能。

???? alarm? 和? pause? 函數可以用來實現sleep函數,如下:

#include <signal.h>
#include <stdio.h>static void sig_alrm(int signo)
{return;
}unsigned int sleep1(unsigned int nsecs)
{if(signal(SIGALRM,sig_alrm)==SIG_ERR){printf("signal(SIGALRM) error\n");return nsecs;}alarm(nsecs);pause();return (alarm(0));
}int main(void)
{printf("before sleep1\n");sleep1(2);printf("after sleep1\n");return 0;
}

在這個實現的函數中存在著一個競爭條件,當執行完? alarm(nsecs)? 之后,可能還沒有執行? pause? 時,定時時間已經到了。這時執行? sig_alrm? 信號處理函數,之后再執行? pause,如果系統中再沒有信號產生,這就會使進程一直掛起。雖然這種情況極少發生,但是這總是一個? bug,當它發生錯誤時,會很難查找。可以用? setjmp? 和? longjmp? 來消除這個潛在的bug:
#include <stdio.h>
#include <signal.h>
#include <setjmp.h>static jmp_buf env_buf;static void sig_alrm(int signo)
{longjmp(env_buf,1);
}unsigned int sleep2(unsigned int nsecs)
{if(signal(SIGALRM,sig_alrm)==SIG_ERR){printf("signal(SIGALRM) error\n");return nsecs;}if(setjmp(env_buf)==0){alarm(nsecs);pause();}return (alarm(0));
}int main(void)
{printf("before sleep1\n");sleep2(2);printf("after sleep1\n");return 0;
}


vim實用技巧、這樣,即使在還沒有執行? pause? 時,時間就已經溢出了,也不會發上上面的情況。alarm? 函數還用來為會阻塞的操作定時,防止它們進入永久阻塞的狀態。這個功能類似于看門狗的功能,主要防止程序跑飛。


版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/1/183711.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息