一.信号捕捉:
1.信号处理的方式:
- 信号忽略:大多数信号都可使用这种方式进行处理,但有两种信号却决不能被忽略。它们是:SIGKILL和SIGSTOP。这两种信号不能被忽略的原因是:它们向超级用户提供一种使用进程或停止的可靠方法。另外,如果忽略某些由硬件异常产生的信号,则进程的行为是未定义的;
- 信号捕捉:为了做到这一点要通知内核在某种信号发生时,调用一个用户函数。在用户函数中,可执行用户希望对这种事件进行的处理。
- 对信号执行默认处理方式:对大多数信号的系统默认动作是终止该进程。
2.信号捕捉函数:
SIGALRM 信号
时钟定时信号,计算实际时间。
alarm 函数
alarm也称为闹钟函数。它可以在进程中设置一个定时器,当定时器指定的时间到时,它向进程发送SIGALRM信号。如果忽略或者不捕获此信号,则其默认动作是终止调用该alarm函数的进程。
#include<unistd.h>
insigned int alarm(insigned int seconds)
//参数seconds表示为设定的秒数,经过seconds后,内核将给调用的该函数的进程发送SIGALRM的信号。如果seconds为0,则不再发送SIGALRM信号。alarm只设定发送一次信号,如果要多次发送,要对alarm多次使用。
pause 函数:
函数使调用进程挂起直至捕捉到一个信号。
#include<unistd.h>
int pause(void)
//pause函数会令目前的进程暂停(睡眠),直到被信号(signal)中断。
sigaction 函数:
用来检查或设置进程在接收到信号时的动作。
#include<signal.h>
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact);
//sigaction会根据参数signum指定的信号编号来设置该信号的处理函数。signum可以是SIGKILL和SIGSTOP以外的任何信号。
二.实现sleep函数:
1.实现思路 :
- main函数调用my_sleep函数,后者调用sigaction组装SIGALRM信号的处理函数handler.
- 调用alarm(seconds)函数设置闹钟.
- 调用pause等待,内核进入其他进程.
- seconds秒后alarm发出信号SIGLARM由内核处理递送到该进程.
- 内核态切换到用户态处理未决信号执行handler函数.
- 切换到用户态执行handler函数,进入handler函数时SIGALRM信号被自动屏蔽, 从handler函数返回时SIGALRM信号自动解除屏蔽。然后自动执行系统调用sigreturn再次进入内核,再返回用户态继续执行进程的主控制流程。
- pause函数返回-1,然后调用alarm(0)取消闹钟,调用sigaction恢复SIGALRM信号以前的处理动作。
2.实现代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
void handler(){
; //可添加任务
}
int my_sleep(int time){
struct sigaction act,oact; //对act里的参数进行修改来改变信号的处理动作,oact则是传出该信号的原来的动作
act.sa_flags = 0;
act.sa_handler = handler;
//sigemptyset(&act.sa_mask); //使屏蔽信号(阻塞)为空
sigaction(SIGALRM,&act,&oact);
alarm(time); //timeout秒后闹钟超时,内核发SIGALRM给这个进程
//while(1);
pause(); //pause函数返回-1,然后调用alarm(0)取消闹钟,调用sigaction恢复信号处理前的动作
int ret = alarm(0);
sigaction(SIGALRM,&oact,NULL);//将信号的处理动作初始化
return ret;
}
int main(){
while(1){
my_sleep(2);
printf("success!\n");
}
}
/* 如果在alarm 与 pause之间有其他函数,则会导致信号异步,pause函数接收不到信号一直挂起 */
在上述的代码中存在者信号异步问题,若在alarm读秒时有程序占用cpu资源导致在设定的time内pause没有接收到信号,之后信号处于递达状态,从而导致pause函数接收不到信号而一直挂起.
3.代码优化
可以调用以下几个函数:
1.sigprocmask 函数
为了防止在调用my_sleep时有其他的信号,它规定了当前阻塞而不能传递给该进程的信号集.可以检测或更改进程的信号屏蔽码.
#include <signal.h>
int sigprocmask( int how, const sigset_t *restrict set, sigset_t *restrict oldset );
//oldset若非空则该进程的信号屏蔽码通过oldset返回;其次,若set是一个非空指针,则参数how指示如何修改当前信号屏蔽字。
下表说明了how可选用的值。不能阻塞SIGKILL和SIGSTOP信号。
how | 说明 |
---|---|
SIG_BLOCK | 该进程新的信号屏蔽字是其当前信号屏蔽字和set指向信号集的并集。set包含了我们希望阻塞的附加信号 |
SIG_UNBLOCK | 该进程新的信号屏蔽字是其当前信号屏蔽字和set所指向信号集补集的交集。set包含了我希望解除阻塞的信号 |
SIG_SETMASK | 该进程新的信号屏蔽字将被set指向的信号集的值代替 |
-
如果set是空指针,则不改变该进程的信号屏蔽字,how的值也无意义。
-
注意:在调用sigprocmask后如果有任何未决的、不再阻塞的信号,则在sigprocmask返回前,至少会将其中一个信号递送给该进程
2.sigsuspend 函数
在对时序要求严格的场合下都应该调用sigsuspend而不是pause,所以我们选用该函数代替pause.
#include <signal.h>
int sigsuspend( const sigset_t *sigmask );
//将进程的信号屏蔽字设置为由sigmask指向的值。在捕捉到一个信号或发生了一个会终止该进程的信号之前,该进程被挂起。如果捕捉到一个信号而且从该信号处理程序返回,则sigsuspend返回,并且将该进程的信号屏蔽字设置为调用sigsuspend之前的值。
3.my_sleepplus
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void handler_sigint(int signo)
{
;
}
int my_sleeppuls(int time)
{
sigset_t newmask, oldmask, mask; //定义信号集
signal(SIGALRM, handler_sigint);
sigemptyset(&newmask); //将其信号集置空
sigemptyset(&oldmask);
sigaddset(&newmask, SIGALRM); //添加信号到newmask中
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) //将本进程的信号屏蔽字改为newmask,将原来的信号屏蔽字保存到oldmask中并返回
{
printf("sigproc failed...\n");
}
alarm(time);
// mask = oldmask;
// sigdelset(&mask, SIGALRM);
// 设置为oldmask,之后醒来再恢复原有的newmask
sigsuspend(&oldmask);
//恢复为oldmask(原来的值)
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
{
printf("sigproc failed\n");
}
return 0;
}
int main()
{
while (1)
{
my_sleeppuls(2);
printf("success!\n");
}
return 0;
}
- 调用sigprocmask(SIG_BLOCK,&newmask, &oldmask)时,屏蔽SIGALRM。
- 调用sigsuspend(&suspmask)时,解除对SIGALRM的屏蔽,然后挂起等待
- SIGALRM递达后suspend返回,自动恢复原来的屏蔽字,也就是再次屏蔽SIGALRM。
- 调用sigprocmask(SIG_SETMASK, &oldmask, NULL)时,再次解除对SIGALRM的屏蔽。