20191227甘宁第六章学习笔记
第6章 信号和信号处理
6.1信号和中断
信号:发送给进程的请求,将进程从正常执行转移到中断处理。
中断:从输入输出设备或协处理器发送到中央处理器的外部请求,将中央处理器从正常执行转移到中断处理。
“中断”是发送给进程的事件,它将进程从正常活动转移到其他活动,称为“中断处理”。“进程”在完成“中断”处理后可以恢复正常活动。
有以下类型的终端:
1.人员中断
2.过程中断
3.硬件中断
4.进程的陷阱错误
Unix/Linux的信号处理
(1)按Ctrl C通常会导致当前正在运行的进程终止。原因如下:Ctrl C组合键会产生键盘硬件中断。键盘中断处理程序将Ctrl C组合键转换为SIGINT(2)信号,发送给终端上的所有进程,唤醒等待键盘输入的进程。在内核模式下,每个进程都必须检查和处理未完成的信号。对于大多数信号,进程的默认操作是调用内核的kexit(exitValue)函数来终止。在Linux中,exitValue的低位字节是导致进程终止的信号号。
(2)用户可以使用命令nohup a.out在后台运行程序。即使在用户退出后,该进程也将继续运行。
(3)用户再次登录时可能会发现(通过ps-u LTD)后台进程仍在运行。用户可以使用sh命令kill pid(或kill -s 9 pid)来终止进程。
信号处理函数
每个处理模块都有一个信号处理阵列intsig [32]。sig[32]数组的每个条目指定如何处理相应的信号,其中0表示DEFault,1表示IGNore。下图显示了信号位向量、屏蔽位向量和信号处理功能。
如果信号位向量中的位I为1,将生成信号I或将其发送给进程。如果屏蔽位向量的位1为1,信号将被阻塞或屏蔽。否则,信号不会被阻断。只有当信号存在且未被阻塞时,信号才会生效或被传递给进程。
信号的发送
发送信号的主要功能有kill()、raise()、sigqueue()、alarm()、setitimer()和abort()。1、kill()
#包含系统/类型. h
#包含信号. h
int kill(pid_t pid,int signo)
参数pid值信号的接收过程
0 pid进程id为pid进程。
Pid=0同一进程组的进程
pid0 pid!=-1进程组标识为-pid的所有进程
Pid=-1进程标识大于1的所有进程,发送进程本身除外
Sinno是信号值。当它为0(即零信号)时,它实际上不发送任何信号,但照常进行错误检查。因此,它可以用来检查目标进程是否存在,以及当前进程是否有向目标发送信号的权限(具有root权限的进程可以向任何进程发送信号,而没有root权限的进程只能向属于同一会话或同一用户的进程发送信号)。
Kill()最常用于pid0被调用时的信令,调用成功返回0;否则,返回-1。注意:在pid0的情况下,不同的版本对于哪些进程会接收信号有不同的看法,其实很简单。只需参考内核源代码/signal.c .上表中的规则参考红帽7.2。
2、raise()
#包含信号. h
int raise(int signo)
向进程本身发送信号,参数是要发送的信号值。呼叫成功返回0;否则,返回-1。
3、sigqueue()
#包含系统/类型. h
#包含信号. h
int sigqueue(pid_t pid,int sig,const union sigval)
呼叫成功返回0;否则,返回-1。
Sigqueue()是一个比较新的信令系统调用,主要是针对实时信号提出的(当然也支持前32种)。支持信号有参数,与函数sigaction()一起使用。
sigqueue的第一个参数指定了接收信号的进程ID,第二个参数决定了要发送的信号,第三个参数是联合数据结构union sigval,它指定了信号传输的参数,也就是俗称的4字节值。
typedef union sigval { int sival _ int;void * sival _ ptr} sigval _ t;
Sigqueue()提供的附加信息比kill()多,但是sigqueue()只能向一个进程发送信号,而不能向一个进程组发送信号。如果signo=0,它将被执行。
错误检查,但实际上不发送任何信号,0值信号可用于检查pid的有效性以及当前进程是否有权限向目标进程发送信号。
在调用sigqueue时,sigval_t指定的信息会拷贝到3参数信号处理函数(3参数信号处理函数指的是信号处理函数由sigaction安装,并设定了sa_sigaction指针,稍后将阐述)的siginfo_t结构中,这样信号处理函数就可以处理这些信息了。由于sigqueue系统调用支持发送带参数信号,所以比kill()系统调用的功能要灵活和强大得多。
注:sigqueue()发送非实时信号时,第三个参数包含的信息仍然能够传递给信号处理函数; sigqueue()发送非实时信号时,仍然不支持排队,即在信号处理函数执行过程中到来的所有相同信号,都被合并为一个信号。
4、alarm()
#include unistd.h
unsigned int alarm(unsigned int seconds)
专门为SIGALRM信号而设,在指定的时间seconds秒后,将向进程本身发送SIGALRM信号,又称为闹钟时间。进程调用alarm后,任何以前的alarm()调用都将无效。如果参数seconds为零,那么进程内将不再包含任何闹钟时间。
返回值,如果调用alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0。
5、setitimer()
#include sys/time.h
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue));
setitimer()比alarm功能强大,支持3种类型的定时器:
ITIMER_REAL: 设定绝对时间;经过指定的时间后,内核将发送SIGALRM信号给本进程;
ITIMER_VIRTUAL 设定程序执行时间;经过指定的时间后,内核将发送SIGVTALRM信号给本进程;
ITIMER_PROF 设定进程执行以及内核因本进程而消耗的时间和,经过指定的时间后,内核将发送ITIMER_VIRTUAL信号给本进程;
Setitimer()第一个参数which指定定时器类型(上面三种之一);第二个参数是结构itimerval的一个实例,结构itimerval形式见附录1。第三个参数可不做处理。
Setitimer()调用成功返回0,否则返回-1。
6、abort()
#include stdlib.h
void abort(void);
向进程发送SIGABORT信号,默认情况下进程会异常退出,当然可定义自己的信号处理函数。即使SIGABORT被进程设置为阻塞信号,调用abort()后,SIGABORT仍然能被进程接收。该函数无返回值。
信号捕捉函数
进程可使用系统调用:int r =signal(int signal_numberr void *handler);来修改选定信号编号的处理函数,SIGKILL(19)除外,它们不能修改。signal()系统调用在所有类Unix系统中均可用,但它有一些缺点:
- 在执行已安装的信号捕捉函数之前,通常将信号处理函数重置为DEFault。为捕捉下一次出现相同的信号,必须重新安装捕捉函数。
- signal()不能阻塞其他信号
- 不同Unix版本的signal。可能会有所不通不同。
所以现在signal()已经被sigaciton()所代替,它的原型是int sigaction (int signum, const struct sigaction *act, struct sigaction *oldact);,sigaction结构体的定义为
其中重要的字段如下:
sa_handler :该字段是指向处理函数的指针,该函数与signal()的处理函数有相同的原型。
sa_sigaction:该字段是运行信号处理函数的另一种方法。它的信号编号旁边有两个额外参数,其中siginfo t *提供关于所接收信号的更多信息。
sa_mask:可在处理函数执行期间设置要阻塞的信号。
sa_flags :可修改信号处理进程的行为。若要使用sa_sigaction处理函数,必须将sa_flags设置为SA_SIGINFO。
实践内容
/* sigset.c */
#include sys/types.h
#include unistd.h
#include signal.h
#include stdio.h
#include stdlib.h
/* 自定义的信号处理函数 */
void my_func(int signum)
{
printf("If you want to quit,please try SIGQUIT\n");
}
int main()
{
sigset_t set,pendset;
struct sigaction action1,action2;
/* 初始化信号集为空 */
if (sigemptyset(set) 0)
{
perror("sigemptyset");
exit(1);
}
/* 将相应的信号加入信号集 */
if (sigaddset(set, SIGQUIT) 0)
{
perror("sigaddset");
exit(1);
}
if (sigaddset(set, SIGINT) 0)
{
perror("sigaddset");
exit(1);
}
if (sigismember(set, SIGINT))
{
sigemptyset(action1.sa_mask);
action1.sa_handler = my_func;
action1.sa_flags = 0;
sigaction(SIGINT, action1, NULL);
}
if (sigismember(set, SIGQUIT))
{
sigemptyset(action2.sa_mask);
action2.sa_handler = SIG_DFL;
action2.sa_flags = 0;
sigaction(SIGQUIT, action2,NULL);
}
/* 设置信号集屏蔽字,此时set中的信号不会被传递给进程,暂时进入待处理状态 */
if (sigprocmask(SIG_BLOCK, set, NULL) 0)
{
perror("sigprocmask");
exit(1);
}
else
{
printf("Signal set was blocked, Press any key!");
getchar();
}
/* 在信号屏蔽字中删除set中的信号 */
if (sigprocmask(SIG_UNBLOCK, set, NULL) 0)
{
perror("sigprocmask");
exit(1);
}
else
{
printf("Signal set is in unblock state\n");
}
while(1);
exit(0);
}
该程序的运行结果如下,可以看见,在信号处于阻塞状态时,所发出的信号对进程不起作用,并且该信号进入待处理状态。读者按任意键,并且信号脱离了阻塞状态后,用户发出的信号才能正常运行。这里SIGINT已按照用户自定义的函数运行,请读者注意阻塞状态下SIGINT的处理和非阻塞状态下SIGINT的处理有何不同。
问题与解决
内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/89921.html