Linux系统编程---6(信号的机制,信号4要素,Linu常规信号表,定时器)
信號的概念
信號在我們的生活中隨處可見, 如:古代戰爭中摔杯為號;現代戰爭中的信號彈;體育比賽中使用的信號槍… 他們都有共性:
Unix 早期版本就提供了信號機制,但不可靠,信號可能丟失。Berkeley 和 AT&T 都對信號模型做了更改,增加 了可靠信號機制。但彼此不兼容。POSIX.1 對可靠信號例程進行了標準化。
信號的機制
A 給 B 發送信號,B 收到信號之前執行自己的代碼,收到信號后,不管執行到程序的什么位置,都要暫停運行, 去處理信號,處理完畢再繼續執行。與硬件中斷類似——異步模式。但信號是軟件層面上實現的中斷,早期常被稱 為“軟中斷”。
信號的特質
由于信號是通過軟件方法實現,其實現手段導致信號有很強的延時性。但對于用戶來說,這個延 遲時間非常短,不易察覺。
每個進程收到的所有信號,都是由內核負責發送的,內核處理。
與信號相關的事件和狀態
產生信號:
遞達:
遞送并且到達進程。產生到遞達是瞬時的
未決:
產生和遞達之間的狀態。主要由于阻塞(屏蔽)導致該狀態。
信號的處理方式:
Linux 內核的進程控制塊 PCB 是一個結構體,task_struct, 除了包含進程 id,狀態,工作目錄,用戶 id,組 id, 文件描述符表,還包含了信號相關的信息,主要指阻塞信號集和未決信號集。
阻塞信號集(信號屏蔽字):
描述信號屏蔽狀態,將某些信號加入集合,對他們設置屏蔽,當屏蔽 x 信號后,再收到該信號,該信號 的處理將推后(解除屏蔽后) 阻塞信號集影響未決信號集。
未決信號集:
沒有處理掉
信號的編號
可以使用 kill–l 命令查看當前系統可使用的信號有哪些。
不存在編號為 0 的信號。其中 1-31 號信號稱之為常規信號(也叫普通信號或標準信號),34-64 稱之為實時信 號,驅動編程與硬件相關。名字上區別不大。而前 32 個名字各不相同。
信號 4 要素
與變量三要素類似的,每個信號也有其必備 4 要素,分別是:
可通過 man7signal 查看幫助文檔獲取。也可查看/usr/src/linux-headers-3.16.0-30/arch/s390/include/uapi/asm/signal.h
在標準信號中,有一些信號是有三個“Value” ,第一個值通常對 alpha 和 sparc 架構有效,中間值針對 x86、arm 和其他架構,最后一個應用于 mips 架構。一個‘-’表示在對應架構上尚未定義該信號。
不同的操作系統定義了不同的系統信號。因此有些信號出現在 Unix 系統內,也出現在 Linux 中,而有的信號出 現在 FreeBSD 或 MacOS 中卻沒有出現在 Linux 下。這里我們只研究 Linux 系統中的信號。
默認動作:
注意從 man7signal 幫助文檔中可看到 :The signals SIGKILL and SIGSTOP cannot be caught, blocked , orignored.
這里特別強調了 9)SIGKILL 和 19)SIGSTOP 信號,不允許忽略和捕捉,只能執行默認動作。甚至不能將其設置為 阻塞。
另外需清楚,只有每個信號所對應的事件發生了,該信號才會被遞送(但不一定遞達),不應亂發信號! !
Linux 常規信號一覽表
信號的產生
終端按鍵產生信號
硬件異常產生信號
kill 函數/命令產生信號
kill 命令產生信號:kill-SIGKILL pid
kill 函數:給指定進程發送指定信號(不一定殺死)
int kill (pid_tpid,int sig); 成功:0;失敗:-1(ID 非法,信號非法,普通用戶殺 init 進程等權級問題),設置 errno sig:不推薦直接使用數字,應使用宏名,因為不同操作系統信號編號可能不同,但名稱一致。
循環創建 5 個子進程,殺死第三個進程
#include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<signal.h>/** 循環創建 5 個子進程 */#define N 5 int main() {int i;pid_t pid,q;for(i=0;i<N;i++){pid = fork();if(pid == 0)break;if(i==2)q=pid;}if(i<5){while(1){printf("I am child %d,getpid= %d\n",i,getpid());sleep(1);} }else{sleep(1);kill(q,SIGKILL);while(1);} return 0; }
殺死第一個cat,后面的都被殺死,因為第一個cat是管道的寫端,如果第一個cat被殺,也就沒有寫入,所以后面的cat也就讀不到數據
進程組
:每個進程都屬于一個進程組,進程組是一個或多個進程集合,他們相互關聯,共同完成一個實體任務, 每個進程組都有一個進程組長,默認進程組 ID 與進程組長 ID 相同。
權限保護
super 用戶(root)可以發送信號給任意用戶,普通用戶是不能向系統用戶發送信號的。 kill-9(root 用 戶的 pid) 是不可以的。同樣,普通用戶也不能向其他普通用戶發送信號,終止其進程。 只能向自己創建的進程發 送信號。普通用戶基本規則是:發送者實際或有效用戶 ID== 接收者實際或有效用戶 ID
raise 和 abort 函數
raise 函數:給當前進程發送指定信號(自己給自己發) raise(signo)==kill(getpid(),signo); int raise(int sig); 成功:0,失敗非 0 值
abort 函數:給自己發送異常終止信號 6)SIGABRT 信號,終止并產生 core 文件 void abort(void); 該函數無返回
軟件條件產生信號
alarm 函數
設置定時器(鬧鐘)。在指定 seconds 后,內核會給當前進程發送 14)SIGALRM 信號。進程收到該信號,默認動 作終止。
每個進程都有且只有唯一個定時器。
unsigned int alarm (unsigned int seconds); 返回 0 或剩余的秒數,無失敗。
常用:取消定時器 alarm(0),返回舊鬧鐘余下秒數。 例:alarm(5) → 3sec → alarm(4) → 5sec → alarm(5) → alarm(0) (取消鬧鐘)
定時,自然定時法,與進程狀態無關(自然定時法)!就緒、運行、掛起(阻塞、暫停)、終止、僵尸…無論進程處于何種狀態, alarm 都計時。
編寫程序,測試你使用的計算機 1 秒鐘能數多少個數。
#include<stdio.h> #include<unistd.h>int main() {int i;alarm(1);//1s后發信號for(i=0;;i++) //死循環看在鬧鐘的1s時間內跑多少下 printf("%d\n",i);return 0; }使用 time 命令查看程序執行的時間
實際執行時間 = 系統時間 + 用戶時間 + 等待時間(文件I/O消耗很大)
setitimer 函數
設置定時器(鬧鐘)。 可代替 alarm 函數。精度微秒 us,可以實現周期定時。
struct itimerval {struct timeval it_interval; /* next value */struct timeval it_value; /* current value */};struct timeval {time_t tv_sec; /* seconds */suseconds_t tv_usec; /* microseconds */};int setitimer (int which,const struct itimerval * new_value, struct itimerval *old_value); 成功:0;失敗:-1,設置 errno
參數:
which:指定定時方式
使用 setitimer 函數實現 alarm 函數,計算1是內計算機計算次數
#include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<sys/time.h> /* struct itimerval{struct timeval{it_value.tv_sec;it_value.tv_usec;}it_value;struct timeval{it_value.tv_sec;it_value.tv_usec;}it_interval;}it,oldit; */unsigned int my_alarm(unsigned int sec) {struct itimerval it, oldit;int ret;it.it_value.tv_sec=sec;it.it_value.tv_usec=0;it.it_interval.tv_sec=0;it.it_interval.tv_usec=0;ret= setitimer(ITIMER_REAL,&it,&oldit);if(ret == -1){perror("setitimer");exit(1);} return oldit.it_value.tv_sec; }int main() {int i;my_alarm(1); //alarm(sec)for(i=0;;i++)printf("%d\n",i);return 0; }結合 manpage 編寫程序,測試 it_interval、it_value 這兩個參數的作用。
void (*signal (int signum,void ( *sighandler_t)(int)))(int);
#include<stdio.h> #include<sys/time.h> #include<signal.h>void myfunc(int signo) {printf("hello world\n"); }int main(void) {struct itimerval it,oldit;signal(SIGALRM,myfunc); //注冊SIGALRM信號的捕捉處理函數//sighandler_t tml=signal(SIGALRM,myfunc);it.it_value.tv_sec=5;//定時5秒中it.it_value.tv_usec=0;//0微秒it.it_interval.tv_sec=3;//第一個和第二個之間間隔時間3秒it.it_interval.tv_usec=0;if(setitimer(ITIMER_REAL,&it,&oldit) == -1){perror("setitimer error");return -1; } while(1);return 0; }注意
總結
以上是生活随笔為你收集整理的Linux系统编程---6(信号的机制,信号4要素,Linu常规信号表,定时器)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux下文件的多进程拷贝
- 下一篇: Linux系统编程----7(信号集,信