信号在内核中的表示
执行信号的处理动作称为信号递达(Delivery),信号从产生到递达之间的状态,称为信号未决(Pending)。进程可以选择阻塞(Block)某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
注意,阻塞和忽略是不同,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。信号在内核中的表示可以看作是这样的:
图-信号的发送过程
解释说明:
1)PCB进程控制块(task_struct)中函数有信号屏蔽状态字(block)和信号未决状态字(pending)还有是否忽略标志;
2)信号屏蔽状态字(block),1代表阻塞、0代表不阻塞;
信号未决状态字(pending)的1代表未决,,0代表信号可以抵达了;
3)向进程发送SIGINT,内核首先判断信号屏蔽状态字是否阻塞,若阻塞,信号未决状态字(pending)相应位制成1;若阻塞解除,信号未决状态字(pending)相应位制成0;表示信号可以抵达了。
4)block状态字、pending状态字均64位(bit);
5)block状态字用户可以读写,pending状态字用户只能读;这是信号设计机制。
思考1:状态字都64bit,编程时,如何表示状态字那?
思考2:block状态字信息如何获取或者操作那?哪些api?
思考3:pending状态字信息如何获取或者操作那?哪些api?
答案见下;
信号集操作函数(状态字表示)#include <signal.h>int sigemptyset(sigset_t *set);//把信号集清零;(64bit/8=8字节)int sigfillset(sigset_t *set); //把信号集64bit全部置为1int sigaddset(sigset_t *set, int signo); //根据signo,把信号集中的对应位置成1int sigdelset(sigset_t *set, int signo); //根据signo,把信号集中的对应位置成0int sigismember(const sigset_t *set, int signo);//判断signo是否在信号集中
sigprocmask:读取/更改信号屏蔽状态字(Block)
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功则为0,若出错则为-1
读取:如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。
更改:如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。
How:
sigpending获取信号未决状态字(pending)信息
#include <signal.h>int sigpending(sigset_t *set);
DESCRIPTION
sigpending()returnsthesetofsignalsthatarependingfordeliverytothecallingthread
(i.e.,thesignalswhichhavebeenraisedwhileblocked).
Themaskofpendingsignalsisreturnedinset.
/**示例1:添加信号SIGINT到信号屏蔽字此时再按下Ctrl+C键, 进程也接收不到SIGINT信号了**/inline void err_exit(std::string message);void sigHandler(int signo);void printSigSet(const sigset_t &sigset);int main(){//虽然此处安装了信号处理函数, 但是该进程还是接收不到SIGINT信号if (signal(SIGINT, sigHandler) == SIG_ERR)err_exit("signal error");//添加信号屏蔽字: 屏蔽SIGINT信号sigset_t addset;sigemptyset(&addset);sigaddset(&addset, SIGINT);if (sigprocmask(SIG_BLOCK, &addset, NULL) == -1)err_exit("sigprocmask error");// 不断打印当前的信号屏蔽字sigset_t sigset;while (true){sigpending(&sigset);printSigSet(sigset);sleep(1);}}inline void err_exit(std::string message){perror(message.c_str());exit(EXIT_FAILURE);}void sigHandler(int signo){cout << "catch a signal, number = " << signo << endl;}void printSigSet(const sigset_t &sigset){for (unsigned i = 1; i < NSIG; ++i){if (sigismember(&sigset, i))putchar(‘1’);elseputchar(‘0’);}putchar(‘\n’);}/** 示例2:在示例1的基础上继续屏蔽SIGINT信号, 但是如果该进程接收到了SIGQUIT信号, 则将对SIGINT信号的屏蔽解除, 需要1.在main函数中再安装一个SIGQUIT捕捉函数2.对sigHandler函数进行改造**/int main(){if (signal(SIGINT, sigHandler) == SIG_ERR)err_exit("signal SIGINT error");if (signal(SIGQUIT, sigHandler) == SIG_ERR)err_exit("signal SIGQUIT error");//添加信号屏蔽字: 屏蔽SIGINT信号sigset_t addset;sigemptyset(&addset);sigaddset(&addset, SIGINT);if (sigprocmask(SIG_BLOCK, &addset, NULL) == -1)err_exit("sigprocmask error");// 不断打印当前的信号屏蔽字sigset_t sigset;while (true){sigpending(&sigset);printSigSet(sigset);sleep(1);}}void sigHandler(int signo){switch (signo){case SIGINT:cout << "catch a signal SIGINT" << endl;break;case SIGQUIT://如果是SIGQUIT信号, 则将SIGINT信号的屏蔽进行解除sigset_t unblockSet;sigemptyset(&unblockSet);sigaddset(&unblockSet, SIGINT);if (sigprocmask(SIG_UNBLOCK, &unblockSet, NULL) == -1)err_exit("sigprocmask unblock error");elsecout << "sigprocmask success" << endl;break;default:cerr << "unknown signal" << endl;break;}}/**连续的按Ctrl+c键盘,虽然发送了多个SIGINT信号,但是因为信号是不稳定的(不支持排队),所以只保留了一个,如下图*/
再怎么风光明媚的自家山川,