目錄
前言:
前文回顧與補充:
信號的保存
PCB里的信號保存?
sigset_t
信號集操作函數
信號的處理
信號捕捉的流程:?編輯
操作系統的運行原理
硬件中斷
時鐘中斷
死循環
軟中斷?
總結:
前言:
在上一篇文章,我已經為大家詳細的闡述了信號產生的物種方式,相信大家對于信號已經有了較為深刻的印象與理解了。
那么今天我們就來繼續談論信號的另外兩個重要話題:信號的保存與捕捉。
前文回顧與補充:
我們之前說,?捕捉信號一共有三種方式,分別是:默認,忽略,與自定義。
在使用signal時,有兩個宏分別代表默認與忽略的處理:
#include <iostream>
#include <unistd.h>
#include <signal.h>int main()
{::signal(2,SIG_IGN);//ignore 忽略:本身就是一種處理方法,什么也不用做直接忽略掉::signal(2,SIG_DFL);//default:執行該信號默認的處理方法return 0;
}
我們之前說的所有信號的產生,最后都要由OS執行,這是為什么呢?
:因為OS是進程的管理者
信號的處理是否會被立即處理?
:不會,會在合適的時候進行處理
一個信號如果不是被立即處理,那么信號是否需要暫時被進程記錄下來?記錄在哪里最合適?
:需要,會被記錄在進程的PCB中
我們先補充一點信號的其他知識:
實際執行信號的處理動作被稱為信號遞達(Delivery)
信號從產生到遞達之間的狀態,被稱為信號未決(Pending)
進程可以選擇阻塞(block)某個信號
被阻塞的信號產生時將保持在未決狀態,直到進程解除對此信號的阻塞,才會執行遞達的動作。
這里要注意,阻塞與忽略是不同的,只要信號被阻塞就不會遞達,而忽略時在遞達之后我們選擇的一種處理信號的動作
信號的保存
PCB里的信號保存?
我們剛剛說,由于信號的處理不是立即處理,所以我們需要保存好進程,就保存在進程的PCB中,我們先來看一下具體保存信號的結構:
這個pending就是我們之前說的信號位圖,保存這個進程是否收到了相應的信號。
這個blcok也是一個位圖,保存的是對應位置的信號是否被我們阻塞/屏蔽了。
而這個handler,則是一個函數指針數組,里面存儲的就是對應信號的默認處理方法。我們信號的編號-1,就是對應的數組下標。
這也就是為什么我們只需要signal一次,就能永久改變處理方法的原因:因為我們是直接把方法拷貝替換成了自己的處理函數。
sigset_t
信號集操作函數
由于保管信號的是位圖結構,我們不好每次都進行位操作來修改位圖,所以有對應的系統調用:

?
如果我們想要恢復成老的數據,該怎么辦?
所以有輸出型參數oldset供我們使用。

?
#include <iostream>
#include <string>
#include <functional>
#include <vector>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>int main()
{//初始化,把指定的位圖全部清0sigset_t block,oblock;sigemptyset(&block);sigemptyset(&oblock);//設置信號,此時我們有沒有把對2號信號的屏蔽,設置進入內核中?:只是在用戶棧上設置了block的位圖結構// 并沒有設置進入內核中!sigaddset(&block,2);//把我們對2號信號的屏蔽,設置進入內核中sigprocmask(SIG_SETMASK,&block,&oblock);while(true){//獲取該進程的pending表并打印sigset_t pending;sigpending(&pending);std::cout<<getpid()<<":";for(int i=31;i>=0;i--){if(sigismember(&pending,i))//挨個挨個檢查是否存在在該位圖里{std::cout<<"1";}else{std::cout<<"0";}}std::cout<<std::endl;sleep(1);}return 0;
}
運行代碼,我們打開另外一個bash給該進程發送信號
那我們此時在增加一段代碼,使得他在一定時間后自動解除屏蔽試試??
#include <iostream>
#include <string>
#include <functional>
#include <vector>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>void handler(int signo)
{std::cout<<"我已經解除屏蔽"<<signo<<std::endl;
}int main()
{signal(2,handler);//初始化,把指定的位圖全部清0sigset_t block,oblock;sigemptyset(&block);sigemptyset(&oblock);//設置信號,此時我們有沒有把對2號信號的屏蔽,設置進入內核中?:只是在用戶棧上設置了block的位圖結構// 并沒有設置進入內核中!sigaddset(&block,2);//把我們對2號信號的屏蔽,設置進入內核中sigprocmask(SIG_SETMASK,&block,&oblock);int count=0;while(true){//獲取該進程的pending表并打印sigset_t pending;sigpending(&pending);std::cout<<getpid()<<":";for(int i=31;i>0;i--){if(sigismember(&pending,i))//挨個挨個檢查是否存在在該位圖里{std::cout<<"1";}else{std::cout<<"0";}}std::cout<<std::endl;count++;if(count>=10){sigprocmask(SIG_SETMASK, &oblock, nullptr);//解除屏蔽}sleep(1);}return 0;
}
?運行代碼,可以看見
?
信號的處理
?我們可以先說出結論,之前所說的合適的時候,指的就是,進程在從內核態切換為用戶態時,會檢測當前進程的pending與blcok,根據這兩個位圖是決定是否執行處理方法handler。
這里引出了兩個新概念,用戶態與內核態。
大家不用急,且聽我慢慢道來:
信號捕捉的流程:

信號處理函數的代碼是在??空間的,處理過程還是比較復雜的,我們可以舉例如下:?
用戶程序注冊了SIGQUIT 信號的處理函數 sighandler 。
當前正在執行main函數,這個時候會發生中斷或者異常切換到內核態(為什么一定會發生中斷我后面會解釋)
在中斷處理完畢后,要返回用戶態的main函數前會檢查到有信號SIGQUIT 遞達。
內核決定返回用戶態后不是恢復main函數的上下文繼續執行代碼,而是執行sighhandler函數(此時sighandler 和 main 函數使?不同的堆棧空間,它們之間不存在調?和被調?的關系,是兩個獨?的控制流程)
操作系統的運行原理
我們這里不得不插一句嘴說一下操作系統的運行原理,只有這樣才能讓大家理解內核態與用戶態更加深入。
這一內容還是很重要的,我們會填上之前說的很多坑。
硬件中斷
我們之前在鍵盤產生信號時一直在說硬件中斷,這個到底是怎么一回事呢?
?
當我們的硬件準備就緒時,就會開始中斷,每一個硬件都有自己的一個中斷號,并且進程會通過高電壓的形式通知CPU,我已經準備就緒了。當我們的CPU知道硬件中斷后,會去獲取這個硬件對應的中斷號。
此時,CPU會保護現場,包括存儲此時運行進程代碼數據的CPU的數據,保存在PCB中(之前講頁表時我們提到過保護現場這個現象)?。
在這之后,會根據中斷號來進行處理的方法,即:
這個中斷向量表IDT,本質是還是一個函數指針數組,每一個硬件對應的中斷號,就對應了下標。
所以每一個硬件產生中斷后,他的處理方法我們一開始就知道了,該如何處理。
這里的中斷處理例程,一共有四步:
1、保存現場
2、根據中斷號,查中斷向量表
3、調用對應的處理方法
4、恢復現場
而這個中斷向量表,是操作系統的?部分,啟動就加載到內存中了。
通過外部硬件中斷,操作系統就不需要對外設進?任何周期性的檢測或者輪詢
?
時鐘中斷
但是我們還是有一個疑問,進程可以在操作系統的指揮下,被調度,被執?,那么操作系統??被誰指揮,被誰推動執?呢?
外部設備可以觸發硬件中斷,但是這個是需要??或者設備??觸發,有沒有??可以定期觸發的 設備?
這里就要引出我們時鐘中斷的概念了
我們規定了一個時間,固定的觸發硬件中斷,這個中斷,被我們稱為時鐘中斷,負責定期幫我們實現中斷!!
而這個中斷在中斷向量表的處理方法只有一個,那就是去調度進程!!
注意,調度進程不代表一定要進行進程切換。
還記得時間片這個概念嗎?
其實就是一個整數count。
我們初始規定count=1000;
那么每一次進行調度,就回讓count--,當count為0時,就會進行進程的切換。
所以我們還有主頻這個概念,就是指 CPU 內部時鐘信號的工作頻率,通常以?赫茲(Hz)?為單位。你的CPU主頻越高,價格越貴,同一個時間進行進程調度越多,性能越好。
死循環
void main(void) /* 這?確實是void,并沒錯。 */{ ????????/* 在startup 程序(head.s)中就是這樣假設的。 */????????...????????/*????????* 注意!! 對于任何其它的任務,'pause()'將意味著我們必須等待收到?個信號才會返????????* 回就緒運?態,但任務0(task0)是唯?的意外情況(參?'schedule()'),因為任????????* 務0 在任何空閑時間?都會被激活(當沒有其它任務在運?時),????????* 因此對于任務0'pause()'僅意味著我們返回來查看是否有其它任務可以運?,如果沒????????* 有的話我們就回到這?,?直循環執?'pause()'。????????*/????????for (;;)????????pause();} // end main
軟中斷?

// sys.h
// 系統調?函數指針表。?于系統調?中斷處理程序(int 0x80),作為跳轉表。
extern int sys_setup (); // 系統啟動初始化設置函數。 (kernel/blk_drv/hd.c,71)
extern int sys_exit (); // 程序退出。 (kernel/exit.c, 137)
extern int sys_fork (); // 創建進程。 (kernel/system_call.s, 208)
extern int sys_read (); // 讀?件。 (fs/read_write.c, 55)
extern int sys_write (); // 寫?件。 (fs/read_write.c, 83)
extern int sys_open (); // 打開?件。 (fs/open.c, 138)
extern int sys_close (); // 關閉?件。 (fs/open.c, 192)
extern int sys_waitpid (); // 等待進程終?。 (kernel/exit.c, 142)
extern int sys_creat (); // 創建?件。 (fs/open.c, 187)
extern int sys_link (); // 創建?個?件的硬連接。 (fs/namei.c, 721)
extern int sys_unlink (); // 刪除?個?件名(或刪除?件)。 (fs/namei.c, 663)
extern int sys_execve (); // 執?程序。 (kernel/system_call.s, 200)
extern int sys_chdir (); // 更改當前?錄。 (fs/open.c, 75)extern int sys_time (); // 取當前時間。 (kernel/sys.c, 102)extern int sys_mknod (); // 建?塊/字符特殊?件。 (fs/namei.c, 412)extern int sys_chmod (); // 修改?件屬性。 (fs/open.c, 105)extern int sys_chown (); // 修改?件宿主和所屬組。 (fs/open.c, 121)extern int sys_break (); // (-kernel/sys.c, 21)extern int sys_stat (); // 使?路徑名取?件的狀態信息。 (fs/stat.c, 36)extern int sys_lseek (); // 重新定位讀/寫?件偏移。 (fs/read_write.c, 25)extern int sys_getpid (); // 取進程id。 (kernel/sched.c, 348)extern int sys_mount (); // 安裝?件系統。 (fs/super.c, 200)extern int sys_umount (); // 卸載?件系統。 (fs/super.c, 167)extern int sys_setuid (); // 設置進程??id。 (kernel/sys.c, 143)extern int sys_getuid (); // 取進程??id。 (kernel/sched.c, 358)extern int sys_stime (); // 設置系統時間?期。 (-kernel/sys.c, 148)extern int sys_ptrace (); // 程序調試。 (-kernel/sys.c, 26)extern int sys_alarm (); // 設置報警。 (kernel/sched.c, 338)extern int sys_fstat (); // 使??件句柄取?件的狀態信息。(fs/stat.c, 47)extern int sys_pause (); // 暫停進程運?。 (kernel/sched.c, 144)extern int sys_utime (); // 改變?件的訪問和修改時間。 (fs/open.c, 24)extern int sys_stty (); // 修改終端?設置。 (-kernel/sys.c, 31)... extern int sys_dup2 (); // 復制?件句柄。 (fs/fcntl.c, 36)extern int sys_getppid (); // 取?進程id。 (kernel/sched.c, 353)extern int sys_getpgrp (); // 取進程組id,等于getpgid(0)。(kernel/sys.c, 201)extern int sys_setsid (); // 在新會話中運?程序。 (kernel/sys.c, 206)extern int sys_sigaction (); // 改變信號處理過程。 (kernel/signal.c, 63)extern int sys_sgetmask (); // 取信號屏蔽碼。 (kernel/signal.c, 15)extern int sys_ssetmask (); // 設置信號屏蔽碼。 (kernel/signal.c, 20)extern int sys_setreuid (); // 設置真實與/或有效??id。 (kernel/sys.c,118)extern int sys_setregid (); // 設置真實與/或有效組id。 (kernel/sys.c, 51)// 系統調?函數指針表。?于系統調?中斷處理程序(int 0x80),作為跳轉表。fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,sys_write, sys_open, sys_close, sys_waitpid, sys_creat, sys_link,sys_unlink, sys_execve, sys_chdir, sys_time, sys_mknod, sys_chmod,sys_chown, sys_break, sys_stat, sys_lseek, sys_getpid, sys_mount,sys_umount, sys_setuid, sys_getuid, sys_stime, sys_ptrace, sys_alarm,sys_fstat, sys_pause, sys_utime, sys_stty, sys_gtty, sys_access,sys_nice, sys_ftime, sys_sync, sys_kill, sys_rename, sys_mkdir,sys_rmdir, sys_dup, sys_pipe, sys_times, sys_prof, sys_brk, sys_setgid,sys_getgid, sys_signal, sys_geteuid, sys_getegid, sys_acct, sys_phys,sys_lock, sys_ioctl, sys_fcntl, sys_mpx, sys_setpgid, sys_ulimit,sys_uname, sys_umask, sys_chroot, sys_ustat, sys_dup2, sys_getppid,sys_getpgrp, sys_setsid, sys_sigaction, sys_sgetmask, sys_ssetmask,sys_setreuid, sys_setregid};
總結:
由于時間關系。
今天的博客就寫到這里,明天我們將會進行最后的結尾,為大家更加具體的說一下什么是內核態與用戶態。
相信今天的知識已經把大家之前所學的內容串聯起來,大家對操作系統也有了更加深刻的理解!
明天我們將會完成信號部分的內容,并給大家講一些信號done的相關內容,之后我們將會開始線程的學習!!