信号以SIG开头
eg:
SIGABRT——夭折信号,进程调用abort时产生这种信号
SIGLARM——闹钟信号,alarm设置的定时器超时后产生
信号在头文件 signal.h中定义
信号产生的条件:
1.用户按某些终端键
eg:Ctrl+C产生中断信号(SIGINT)
2.硬件异常产生信号:除数为0、无效的内存引用等。
3.进程调用kill函数将任意信号发送给另一个进程或进程组
4.用户用kill命令(shell)将信号发送给其他进程
5.当软件条件发生时,产生信号
eg:
在网络上传来带外的数据——SIGURG
对于信号的处理:
1.忽略
两种信号不能被忽略:SIGKILL、SIGSTOP,它们为内核和超级用户提供了使进程终止或停止的可靠方法 ,硬件信号也不行
2.捕捉
SIGKILL和SIGSTOP不能捕捉
3.执行系统默认动作
终止+core 这个core是什么意思???
表示在当前工作目录的core文件中复制了该进程的内存映像。
函数signal
signp参数是图10-1中的信号名;
func值有三种选择:
SIG_IGN——表示忽略此信号(SIGKILL和SIGSTOP信号不能忽略)
SIG_DFL——表示收到信号后执行系统默认动作
函数地址——当收到信号后执行此函数
代码示例:
#include"apue.h"
static void sig_usr(int);/*信号处理函数*/
int main()
{
if(signal(SIGUSR1,sig_usr)==SIG_ERR)
err_sys("can't catch SIGUSR1");
if(signal(SIGUSR2,sig_usr)==SIG_ERR)
err_sys("can't catch SIGUSR2");
for(;;)
pause();
}
static void sig_usr(int signo)
{
if(signo==SIGUSR1)
printf("received SIGUSR1\n");
else if (signo==SIGUSR2)
printf("received SIGUSR2\n");
else
err_dump("received signal %d\n",signo);
}
输出结果:
可以调用kill命令将信号发送给进程
kill -USR1 5106 表示给5106发送USR1信号
kill 5106向该进程发送SIGTERM(用于请求进程正常终止)
进程用exec启动程序文件,新的程序文件会将所有信号都设置为它们的默认动作,除非新的程序忽略这些信号。
一个进程调用fork时,其子进程继承父进程的信号处理方式
不可靠的信号(远古时代的宝贝)
可重入函数
在信号处理程序中要保证调用安全的函数。
如果你不确定一个函数是不是可重入的可以问问chatGPT
可靠信号术语和语义
信号递送:
当一个信号产生时,内核通常在进程表中以某种形式设置一个标志。
在信号产生和递送之间的时间间隔内,称信号是未决的。
函数kill和raise
pid对应4种情况:
pid>0——发送给进程ID为pid的进程
pid==o——发送给属于同一进程组的所有进程(有权限的前提下)
pid<0——发送给进程组ID等于pid绝对值的所有进程
pid==-1 发送给发送进程有权限发送的所有进程
上述四种情况的所有进程不包括系统进程集(不包括现定义的系统进程集,包括内核进程和init进程)中的进程。
权限问题:
1.超级用户可将信号发送给任一进程
2.非超级用户,发送者的实际用户ID或有效用户ID必须等于接收者的实际用户ID或有效用户ID
3.若发送的信号为SIGCONT,则可将它发送给属于同一会话的任一其他进程
kill(pid,0);
signo为0表示空信号,用来判断进程ID为pid的进程是否存在。
如果向一个并不存在的进程发送空信号,则kill返回-1,errno被设置为ESRCH
注意:kill函数不是原子操作,等函数返回时,原本存在的进程可能已经终止,所以并无多大价值.
函数alarm和pause
注意:可以用signal函数捕捉闹钟到时的SIGALRM信号,但是signal函数一定要在闹钟到时之前调用,否者程序会直接终止。
注意:如果没有设置信号处理程序,则进程会执行信号的默认动作,然后pause函数返回。
用alarm、pause函数实现有缺陷的sleep函数,代码如下:
#include"apue.h"
#include<signal.h>
#include<unistd.h>
static void sig_alarm(int signo)
{
/*nothing to do,just return to wake up the pause*/
}
unsigned int sleep1(unsigned int seconds)
{
if(signal(SIGALRM,sig_alarm) == SIG_ERR)
return seconds;
alarm(seconds);
pause();
return alarm(0);/*turn off timer*/
}
int main()
{
sleep1(5);
return 0;
}
存在以下3个问题:
1.在调用sleep1之前已经设置了闹钟,则它会被sleep1中第一次调用alarm时移除
2.修改了对SIGALRM的配置,sleep1函数对SIGALRM信号进行捕获并执行响应程序
3.第一次执行alarm和pause有竞争条件,如果执行alarm(seconds)后进程进行了切换,且在pause()执行之前时间结束,则进程会结束。
对sleep1的改良这里不作赘述
信号集
定义:
一个能表示多个信号的数据类型。
sigismember测试一个指定的位
函数sigprocmask
作用:可以检测或更改,或同时进行检测和更改进程的信号屏蔽字。
oset保存旧的信号屏蔽字
eg:
sigprocmask(SIG_BLOCK,&newmask,&oldmask);
将newmask和旧信号屏蔽字的并集作为进程的新信号屏蔽字,将旧信号屏蔽字存入oldmask
可以用sigprocmask函数来获取进程的屏蔽字集合
sigset_t sigset;
sigprocmask(0,NULL,&sigset);
进程的屏蔽字集合存入sigset
打印调用进程信号屏蔽字中的信号名 代码示例:
#include"apue.h"
#include<errno.h>
void pr_mask(const char * str)
{
sigset_t sigset;
int errno_save;/*用来保存errno值*/
errno_save = errno;
if(sigprocmask(0,NULL,&sigset)<0)
err_ret("sigprocmask error");
else
{
printf("%s",str);
if(sigismember(&sigset,SIGINT))
printf("SIGINT");
if(sigismember(&sigset,SIGQUIT))
printf("SIGQUIT");
if(sigismember(&sigset,SIGUSR1))
printf("SIGUSR1");
if(sigismember(&sigset,SIGALRM))
printf("SIGALRM");
printf("\n");
}
errno=errno_save;
}
函数sigpending
作用:
用于获取当前被阻塞(pending)的信号集合,即已经发送给进程但尚未被处理的信号集合。
阻塞信号是指暂时阻止信号递送给进程,而未决信号是指已经发送给进程但尚未处理的信号。
对之前很多信号功能的代码示例:
#include"apue.h"
static void sig_quit(int);
int main()
{
sigset_t newmask,oldmask,pendmask;
if(signal(SIGQUIT,sig_quit)) /*为SIGQUIT信号设置信号执行程序*/
err_sys("can't catch SIGQUIT");
sigemptyset(&newmask);
sigaddset(&newmask,SIGQUIT);/*阻塞信号SIGQUIT*/
if(sigprocmask(SIG_BLOCK,&newmask,&oldmask)<0)/*将新信号屏蔽集赋予进程*/
err_sys("SIG_BLOCK error");
sleep(5);/*屏蔽SIGQUIT睡眠*/
if(sigpending(&pendmask)<0)
err_sys("sigpending error");
if(sigismember(&pendmask,SIGQUIT))
printf("\nSIGQUIT pending\n");
if(sigprocmask(SIG_SETMASK,&oldmask,NULL)<0)
err_sys("SIG_SETMASK error");
printf("SIGQUIT unblocked\n");
sleep(5);
exit(0);
}
static void sig_quit(int signo)
{
printf("caught SIGQUIT\n");
if(signal(SIGQUIT,SIG_DFL)<0)
err_sys("can't reset SIGQUIT");
}
流程分析:
1.
signal(SIGQUIT,sig_quit)
为SIGQUIT信号设置信号执行程序
2.
sigemptyset(&newmask);
sigaddset(&newmask,SIGQUIT);/*阻塞信号SIGQUIT*/
if(sigprocmask(SIG_BLOCK,&newmask,&oldmask)<0)/*将新信号屏蔽集赋予进程*/
err_sys("SIG_BLOCK error");
为进程设置新的信号屏蔽字,对SIGQUIT信号进行屏蔽,并将旧信号屏蔽字进行保存,方便后面回复状态
3.
sleep(5);/*屏蔽SIGQUIT睡眠*/
进程进入第一次睡眠,在此期间屏蔽SIGQUIT信号
4.
if(sigpending(&pendmask)<0)
err_sys("sigpending error");
if(sigismember(&pendmask,SIGQUIT))
printf("\nSIGQUIT pending");
获取未决信号和屏蔽信号,并判断SIGQUIT是否是屏蔽信号或者未决信号
这里SIGQUIT是屏蔽信号,终端输出SIGQUIT pending
5.
if(sigprocmask(SIG_SETMASK,&oldmask,NULL)<0)
err_sys("SIG_SETMASK error");
printf("SIGQUIT unblocked\n");
sleep(5);
回复进程的信号屏蔽字(即取消对SIGQUIT信号的屏蔽)
则屏蔽队列中的SIGQUIT立马变成了未决信号,信号执行程序sig_quit立马调用,输出
caught SIGQUIT
然后才输出SIGQUIT unblocked
解除屏蔽的瞬间屏蔽信号就变成了未决信号,并调用信号执行程序
输出结果:
注意到caught SIGQUIT的输出是在SIGQUIT unblocked之前的
信号处理程序在解除屏蔽的瞬间就执行了
观察到第二次运行代码,产生了10次SIGQUIT信号,只调用了一次信号处理程序。
当解除对信号的阻塞时,操作系统会检查是否有相同类型的多个未决信号等待处理。如果有多个相同类型的未决信号,通常只会调用一次信号处理程序来处理这些信号。这样可以确保信号处理程序不会被重复调用,避免出现重复处理相同信号的情况。
函数sigaction(我感觉十分复杂,究极套娃)
功能:检查或修改(或检查并修改)与指定信号相关联的处理动作。
signo变量表示要处理的信号编号,比如SIGINT、SIGTERM等。
act参数用于指定信号处理函数以及一些其他选项
oact(old act)参数用于保存旧的act值
eg:
sigaction(SIGINT,act,oact);
将对于SIGINT的信号处理函数和一些其他选项(后面详述)设置成act指定的一些值,并把旧的处理函数和一些其他选项保存记录在oact中
sigaction结构体定义如下:
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
sa_handler字段包含信号处理函数的地址
当sa_handler设置为函数地址时,sa_mask用来设置执行函数时的屏蔽字
sa_mask保证了信号处理函数在执行过程中不被其他信号打断
sa_flags对应信号进行处理的各个选项,可选如下:
sa_sigaction字段
1.是一个替代的信号处理程序
2.sa_sigaction和sa_handler二选一
3.当sa_flags为SA_SIGINFO时,选用sa_sigation字段,按下列方式调用信号处理程序:
void handler(int signo,siginfo_t * info,void *context)
下面解释info、context两个参数:
info参数
siginfo结构体参数如下:
struct siginfo{
int si_signo;/*信号编号*/
int si_errno;/*如果信号是由于某个系统调用失败而产生的,这里将包含对应的错误号。*/
int si_code;/*额外信息(取决于信号)*/
pid_t si_pid;/*发送进程的进程ID*/
uid_t si_uid;/*发送进程的实际用户ID*/
void *si_addr;/*造成故障的地址*/
int si_status;/*返回值或信号编码*/
union sigval si_value;/*特殊应用值*/
};
si_code根据信号的不同有不同的值,具体如下:
context参数
ucontext_t *uc_link; /*该指针指向一个将在当前上下文返回后被恢复的上下文*/
sigset_t uc_sigmask; /*当前上下文活跃时被阻塞的信号集*/
stack_t uc_stack;/*当前上下文使用的栈*/
mcontext_t uc_mcontext;/*以特定于某台机器或某种平台的方式保存的上下文信息*/
私了个募这个sigaction函数,东西一大堆,真的看不明白,人绕晕了!!!!
下面介绍几个sigaction应用示例来熟悉一下
sigaction应用示例:实现signal函数
#include"apue.h"
#include<signal.h>
Sigfunc *signal(int signo,Sigfunc * func)
{
struct sigaction act,oact;/*用来存放从sigaction函数返回的信息*/
act.sa_handler=func;
sigemptyset(&act.sa_mask);
act.sa_flags=0;
if(signo == SIGALRM)
{
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT;
#endif
}
else
act.sa_flags |= SA_RESTART;
if(sigaction(signo,&act,&oact)<0)
return SIG_ERR;
return oact.sa_handler;
}
static void sig_quit(int signo)
{
printf("\ncaught SIGQUIT\n");
}
int main()
{
signal(SIGQUIT,sig_quit);
pause();
return 0;
}
函数sigsetjmp和siglongjmp
作用:在信号处理程序中进行非局部转移。
代码示例:
#include"apue.h"
#include<setjmp.h>
#include<time.h>
#include"test_sigprocmask.c"
static void sig_usr1(int);
static void sig_alrm(int);
static sigjmp_buf jmpbuf;
static volatile sig_atomic_t canjmp;
int main()
{
if(signal(SIGUSR1,sig_usr1) == SIG_ERR)
err_sys("signal (SIGUSR1) error");
if(signal(SIGALRM,sig_alrm) == SIG_ERR)
err_sys("signal (SIGALRM) error");
pr_mask("starting main: ");
if(sigsetjmp(jmpbuf,1))
{
pr_mask("ending main: ");
exit(0);
}
canjmp=1; /*执行了sigsetjmp之后才将canjmp设置成1*/
for(;;)
pause();
}
static void sig_usr1(int signo)
{
time_t starttime;
if(canjmp == 0)/*如果canjmp为0则返回*/
return ;
pr_mask("starting sig_usr1");
alarm(3);/*计时3s⏰*/
starttime = time(NULL);
for(;;)
if( time(NULL) > starttime+5 )
break;
pr_mask("finishing sig_usr1");
canjmp = 0;/*在一次longjmp之前把canjmp清0*/
siglongjmp(jmpbuf,1);
}
static void sig_alrm(int signo)
{
pr_mask("in sig_alrm: ");
}
代码分析:
1.创建了易失变量canjmp来限制siglongjmp只能在sigsetjmp之后调用,别切在执行一次longjmp之前将canjmp清0(保证了执行sigsetjmp之后才能longjmp)
2.并且canjmp的类型为sig_atomic_t,保证了对canjmp的写为原子操作
输出结果:
main函数中调用的sigsetjmp参数为jmpbuf和1,savemask值非0,会保存当前屏蔽字,所以当从sig_usr1或者sig_alrm函数longjmp回来时,信号屏蔽字为空,输出ending main:
另外执行信号处理程序会屏蔽相应的信号:
左边没有执行信号处理程序时,屏蔽字为空,中间进入SIGUSR1的信号处理程序,屏蔽字为SIGUSR1,右边在执行sig_usr1时被SIGALRM信号中断,转而执行sig_alrm,此时中断屏蔽字为SIGUSR1和SIGALRM
函数sigsuspend
目的:
为了解决在解除信号屏蔽和pause()之间产生的信号带来的问题。
功能:
sigsuspend
函数的作用是挂起进程,直到收到指定信号为止。当调用sigsuspend
函数时,进程会将自身的信号屏蔽字设置为一个指定的信号集,然后进入睡眠状态,直到收到信号集中的信号为止。收到信号后,进程会恢复原来的信号屏蔽字,然后继续执行。这个函数通常用于实现信号的同步处理。
suspend可以保护代码临界区,使其不被特定信号中断
下面的代码就保护临界区不受SIGINT信号中断
代码示例:
#include"apue.h"
#include"test_sigprocmask.c"
static void sig_int(int);
int main()
{
sigset_t newmask,oldmask,waitmask;
pr_mask("program start:");
if(signal(SIGINT,sig_int) == SIG_ERR)
err_sys("signal(SIGINT) error");
sigemptyset(&waitmask);
sigaddset(&waitmask,SIGUSR1);
sigemptyset(&newmask);
sigaddset(&newmask,SIGINT);
if(sigprocmask(SIG_BLOCK,&newmask,&oldmask)<0)
err_sys("SIG_BLOCK error");
/*临界区代码在这*/
pr_mask("in critical region:");
if(sigsuspend(&waitmask)!=-1)
err_sys("sigsuspend error");
pr_mask("after return from sigsuspend: ");
if(sigprocmask(SIG_SETMASK,&oldmask,NULL)<0)
err_sys("SIG_SETMASK error");
pr_mask("program exit: ");
exit(0);
}
static void sig_int(int signo)
{
pr_mask("\nin sig_int: ");
}
输出结果:
分析:程序一开始屏蔽字为空
1.if(sigprocmask(SIG_BLOCK,&newmask,&oldmask)<0)
err_sys("SIG_BLOCK error");
这里newmask中屏蔽信号为SIGINT,然后进入临界区域(critical region)
所以临界区的屏蔽字屏蔽了SIGINT信号,输出in critical region:SIGINT
2.然后调用suspend函数
if(sigsuspend(&waitmask)!=-1)
err_sys("sigsuspend error");
waitmask集合中只包含SIGUSR1信号,进程进入等待除SIGUSR1外的信号的状态
终端键入Ctrl+C 发送SIGINT信号,进程接收SIGINT信号,进入信号处理函数sig_int
由于sigsuspend函数屏蔽了SIGUSR1信号,且SIGINT的信号处理函数自动屏蔽SIGINT信号,所以sig_int函数输出
in sig_int:SIGINT SIGUSR1
3.从sigsuspend函数返回,屏蔽字集合恢复到调用sigsuspend函数之前,即只屏蔽了SIGINT信号
输出
after return from sigsuspend: SIGINT
4.
if(sigprocmask(SIG_SETMASK,&oldmask,NULL)<0)
err_sys("SIG_SETMASK error");
将屏蔽字恢复为程序开始时的状态,即屏蔽字为空
输出:
program exit:
suspend函数的另一种应用:等待一个信号处理程序设置一个全局变量
下面的代码用于捕捉中断信号和退出信号,希望仅当捕捉到退出信号时,才唤醒主例程。
代码示例如下:
#include"apue.h"
volatile sig_atomic_t quitflag;
static void sig_int(int signo)
{
if(signo == SIGINT)
printf("\ninterrupt\n");
else if(signo == SIGQUIT)
quitflag=1;
printf("quitflag的值为%d\n",quitflag);
}
int main()
{
sigset_t newmask,oldmask,zeromask;
if(signal(SIGINT,sig_int) == SIG_ERR)/*为SIGINT和SIGQUIT信号设置同一处理函数*/
err_sys("signal (SIGNINT) error");
if(signal(SIGQUIT,sig_int) == SIG_ERR)
err_sys("signal (SIGQUIT) error");
sigemptyset(&zeromask);
sigemptyset(&newmask);
sigaddset(&newmask,SIGQUIT);
if(sigprocmask(SIG_BLOCK,&newmask,&oldmask)<0)
err_sys("SIG_BLOCK error");
while(quitflag == 0) /*只有在sig_int将quitflag设置成1后,主例程才能继续*/
{
sigsuspend(&zeromask);
printf("在while循环中,quitflag的值为%d\n",quitflag);
}
printf("跳出了循环!");
quitflag = 0;
if(sigprocmask(SIG_SETMASK,&oldmask,NULL)<0)
err_sys("SIG_SETMASK error");
exit(0);
}
程序会卡在主函数while循环处,zeromask为空,进程会接收所有信号
while(quitflag == 0) /*只有在sig_int将quitflag设置成1后,主例程才能继续*/
{
sigsuspend(&zeromask);
printf("在while循环中,quitflag的值为%d\n",quitflag);
}
但是只有SIGQUIT信号能使得quitflag的值变为1,使得程序继续
PS.我自己运行的时候第二次键入Ctrl+C程序就退出了,我不知道为什么。
sigsuspend函数可以实现父、子进程间的同步
TELlWAIT、TELL_PARENT、TELL_CHILD、WAIT_PARENT和WAIT_CHILD实现如下:
#include "apue.h"
static volatile sig_atomic_t sigflag;
static sigset_t newmask,oldmask,zeromask;
static void sig_usr(int signo)
{
sigflag = 1;
}
/*完成两件任务:
1.给SIGUSR1、SIGUSR2设置信号处理函数
2.屏蔽SIGUSR1、SIGUSR2信号,并存储原屏蔽字
*/
void TELL_WAIT(void)
{
if(signal(SIGUSR1,sig_usr) == SIG_ERR)
err_sys("signal SIGUSR1 error");
if(signal(SIGUSR2,sig_usr) == SIG_ERR)
err_sys("signal SIGUSR2 error");
sigemptyset(&zeromask);
sigemptyset(&newmask);
sigaddset(&newmask,SIGUSR1);
sigaddset(&oldmask,SIGUSR2);
if(sigprocmask(SIG_BLOCK,&newmask,&oldmask)<0)
err_sys("SIG_BLOCK error");
}
/*
向父进程发送USR2信号,告诉父进程自己准备完毕
*/
void TELL_PARENT(pid_t pid)
{
kill(pid,SIGUSR2);
}
void WAIT_PARENT(void)
{
while(sigflag == 0) /*等待父进程将sigflag设置为0*/
sigsuspend(&zeromask);
sigflag=0;
/*还原信号屏蔽字*/
if(sigprocmask(SIG_SETMASK,&oldmask,NULL)<0)
err_sys("SIG_SETMASK error");
}
/*向子进程发送SIGUSR1信号,告诉自己已经完成*/
void TELL_CHILD(pid_t pid)
{
kill(pid,SIGUSR1);
}
void WAIT_CHILD(void)
{
/*等待子进程将sigflag设置为0*/
while(sigflag == 0)
sigsuspend(&zeromask);
sigflag=0;
if(sigprocmask(SIG_SETMASK,&oldmask,NULL)<0)
err_sys("SIG_SETMASK error");
}
代码解析:
1.代码中用SIGUSR1表示父进程发送给子进程的信号,SIGUSR2表示子进程发送给父进程的信号
2.TELL_WAIT函数相当于一个初始化函数
3.父子进程之间的TELL通过发送USR信号来触发信号处理函数的执行从而修改sigflag的值来进行
函数abort——使程序异常终止
原理:向调用进程发送SIGABRT信号
一些细节:
1.调用abort会向主机发送一个未成功终止的通知,具体是调用raise函数
2.abort将SIGABRT信号发送给进程,进程执行信号处理程序返回后,不会返回调用abort的进程
3.进程在收到SIGABRT信号后,要在终止之前由其执行所需的清理操作
按POSIX.1实现的abort代码如下:
#include<signal.c>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
void abort(void)
{
sigset_t mask;
struct sigaction action;
/*调用abort的进程不能忽略SIGABRT信号,如果该进程忽略了,则取消屏蔽*/
sigaction(SIGABRT,NULL,&action);/*把对于SIGABRT信号的相关处理设置保存在action中*/
if(action.__sigaction_handler.sa_handler == SIG_IGN)/*如果对于SIGABRT的处理为忽略则改成默认*/
{
action.__sigaction_handler.sa_handler = SIG_DFL;
sigaction(SIGABRT,&action,NULL);
}
if(action.__sigaction_handler.sa_handler == SIG_DFL)
fflush(NULL);/*冲洗所有的标准输入输出流*/
sigfillset(&mask);/*将所有信号屏蔽字设为1*/
sigdelset(&mask,SIGABRT);/*不屏蔽SIGABRT信号*/
sigprocmask(SIG_SETMASK,&mask,NULL);/*屏蔽除了SIGABRT之外的所有信号*/
kill(getpid(),SIGABRT);
fflush(NULL);/*冲洗所有打开的标准输入输出流*/
action.__sigaction_handler.sa_handler = SIG_DFL;
sigaction(SIGABRT,&action,NULL);
sigprocmask(SIG_SETMASK,&mask,NULL);
kill(getpid(),SIGABRT);
exit(1);
}
解析:
sigaction(SIGABRT,NULL,&action);/*把对于SIGABRT信号的相关处理设置保存在action中*/
if(action.__sigaction_handler.sa_handler == SIG_IGN)/*如果对于SIGABRT的处理为忽略则改成默认*/
{
action.__sigaction_handler.sa_handler = SIG_DFL;
sigaction(SIGABRT,&action,NULL);
}
这段代码判断进程对于SIGABRT信号是否是忽略的,若是忽略的则设置成默认状态
为什么代码中对于标准输入输出流进行了两次冲洗?
第一次冲洗是在给进程发送SIGABRT信号(也就是执行信号处理程序)之前,
在信号处理程序中输入输出缓存中可能产生新数据
所以在从信号处理程序返回后要进行再次冲洗
函数system
函数sleep、nanosleep和clock_nanosleep
reqtp指向的timespec结构体用秒和纳秒指定需要休眠的长度
remtp指向的timespec结构体存放未休眠完的时间长度
clock_id可选如下:
CLOCK_REALTIME
:- 这是基于系统实时时间的时钟。它反映了墙上时钟的时间,并受到系统时间调整(如用户手动设置时间或NTP调整)的影响。
- 当使用
CLOCK_REALTIME
与clock_nanosleep
时,休眠的结束时间将基于系统的实时时间。
CLOCK_MONOTONIC
:- 这是一个从某个固定点(通常是系统启动)开始计算的时钟,它不受系统时间调整的影响。
CLOCK_MONOTONIC
通常用于测量经过的时间,因为它不会由于系统时间的更改而受到影响。- 使用
CLOCK_MONOTONIC
与clock_nanosleep
时,休眠的结束时间将基于从某个固定点开始的单调时间。
CLOCK_PROCESS_CPUTIME_ID
:- 这个时钟测量当前进程所消耗的CPU时间。
- 它不包括其他进程或系统活动所使用的时间,只计算当前进程在用户模式和内核模式下实际使用的时间。
CLOCK_THREAD_CPUTIME_ID
:- 类似于
CLOCK_PROCESS_CPUTIME_ID
,但它测量的是当前线程所消耗的CPU时间。
- 类似于
函数sigqueue——对信号进行排队
使用排队信号前置条件:
sigqueue函数与kill函数类似,但是sigqueue函数可以使用value参数向信号处理程序传递整数和指针值。
注意:
1.信号不能无限排队,收到SIGQUEUE_MAX限制,达到相应限制后,sigqueue会失败,将errno设置为EAGAIN
2.独立信号集,编号在SIGRTMIN~SIGRTMAX之间,默认行为是终止进程
作业控制信号
作业控制信号之间有某些交互:
1.一个进程产生4种停止信号(SIGTSTP、SIGSTOP、SIGttin或SIGTTOU)中的任意一种时,未决SIGCONT会被丢弃
2.对一个进程产生SIGCONT信号时,未决停止信号(SIGTSTP、SIGSTOP、SIGttin或SIGTTOU)将会被丢弃
SIGCONT只对于停止进程有效(使得进程继续),否则直接忽略。
尽管有的停止进程忽略或者阻塞SIGCONT信号,但是收到SIGCONT信号的停止进程还是会继续。
示例代码如下,改程序将标准输入复制到标准输出,并在SIGTSTP信号处理函数中管理屏幕刷新:
#include"apue.h"
#define BUFFSIZE 1024
/*SIGTSTP信号的处理函数*/
static void sig_tstp(int signo)
{
sigset_t mask;
/*....将光标移动至左下角,重置tty模式*/
/*解除对于SIGTSTP信号的阻塞,因为在我们处理该信号的时候,它被阻塞了*/
sigemptyset(&mask);
sigaddset(&mask,SIGTSTP);
sigprocmask(SIG_UNBLOCK,&mask,NULL);
signal(SIGTSTP,SIG_DFL);/*对于SIGTSTP信号的处理设置为默认*/
kill(getpid(),SIGTSTP);
/*直到进程继续之前不会从kill函数返回*/
/*.. 重新设置tty模式,重新绘制屏幕..*/
}
int main()
{
int n;
char buf[BUFFSIZE];
/*只有在我们用用作业控制shell运行时,才能捕捉SIGTSTP信号*/
if(signal(SIGTSTP,SIG_IGN) == SIG_DFL)
signal(SIGTSTP,sig_tstp);
while((n = read(STDIN_FILENO,buf,BUFFSIZE))>0)
if(write(STDOUT_FILENO,buf,n)!=n)
err_sys("write error");
if(n<0)
err_sys("read error");
return 0;
}
对于信号处理函数sig_tstp:
static void sig_tstp(int signo)
{
sigset_t mask;
/*....将光标移动至左下角,重置tty模式*/
/*解除对于SIGTSTP信号的阻塞,因为在我们处理该信号的时候,它被阻塞了*/
sigemptyset(&mask);
sigaddset(&mask,SIGTSTP);
sigprocmask(SIG_UNBLOCK,&mask,NULL);
signal(SIGTSTP,SIG_DFL);/*对于SIGTSTP信号的处理设置为默认*/
kill(getpid(),SIGTSTP);
/*直到进程继续之前不会从kill函数返回*/
/*.. 重新设置tty模式,重新绘制屏幕..*/
}
1.该函数使得进程收到SIGTSTP信号之后,将光标移动至左下角,并重置tty模式,方便用户重新输入
2.然后会取消对于SIGTSTP信号的屏蔽(进入SIGTSTP信号的处理函数后系统会自动屏蔽SIGTSTP信号),并将对于SIGSTSTP信号的处理程序设置为默认(只将进程挂起)
3.然后调用kill函数对该进程发送SIGTSTP信号,使得进程挂起
4.进程收到SIGCONT信号后才会从kill函数返回,重新设置tty模式,绘制屏幕
信号名和编号之间的映射
psignal函数
使用psignal函数可移植地打印与信号编号对应的字符串。
psiginfo函数
用来获取sigaction函数信号处理程序中siginfo的信息(除信号编号以外的更多信息)
习题
10.1
删除图 10-2 程序中的 for(;;)
语句,结果会怎样?为什么?
图10-2代码如下:
修改后的代码如下:
#include"apue.h"
static void sig_usr(int);/*信号处理函数*/
int main()
{
if(signal(SIGUSR1,sig_usr)==SIG_ERR)
err_sys("can't catch SIGUSR1");
if(signal(SIGUSR2,sig_usr)==SIG_ERR)
err_sys("can't catch SIGUSR2");
pause();
}
static void sig_usr(int signo)
{
if(signo==SIGUSR1)
printf("received SIGUSR1\n");
else if (signo==SIGUSR2)
printf("received SIGUSR2\n");
else
err_dump("eceived signal %d\n",signo);
}
收到USR1信号后,进程就结束了
没有那个for循环,pause的时候收到USR1信号,进程跳出pause去执行USR1的信号处理程序,然后进程就结束了
10.2
实现 10.22 节中说明的 sig2str
函数
我的原来的思路是用psignal函数来实现sig2str函数,这是不可取的,我对于psignal函数的作用还不够清晰
psignal
函数是一个在Unix和类Unix系统(包括Solaris)中用于将信号编号转换为描述性字符串并打印到标准错误输出(stderr)的函数。
eg:
注意输出的为Interrupt而不是SIGINT,输出的为该信号的作用
而sig2str(SIGINT,str)
则str只存放的SIGINT,表明将一个int型的信号变为对应的信号名存入str
参考Github上的答案:
#include <signal.h>
#include <stdio.h>
#include <string.h>
#define s2s(signo, str, signal) \
{ \
if (signo == signal) strcpy(str, #signal); \
}
void sig2str(int signo, char *str) {
strcpy(str, "UNKNOWN");
s2s(signo, str, SIGKILL);
s2s(signo, str, SIGHUP);
s2s(signo, str, SIGINT);
s2s(signo, str, SIGQUIT);
s2s(signo, str, SIGILL);
s2s(signo, str, SIGTRAP);
s2s(signo, str, SIGABRT);
s2s(signo, str, SIGIOT);
s2s(signo, str, SIGBUS);
s2s(signo, str, SIGFPE);
s2s(signo, str, SIGKILL);
s2s(signo, str, SIGUSR1);
s2s(signo, str, SIGSEGV);
s2s(signo, str, SIGUSR2);
s2s(signo, str, SIGPIPE);
s2s(signo, str, SIGALRM);
s2s(signo, str, SIGTERM);
s2s(signo, str, SIGSTKFLT);
s2s(signo, str, SIGCHLD);
s2s(signo, str, SIGCONT);
s2s(signo, str, SIGSTOP);
s2s(signo, str, SIGTSTP);
s2s(signo, str, SIGTTIN);
s2s(signo, str, SIGTTOU);
s2s(signo, str, SIGURG);
s2s(signo, str, SIGXCPU);
s2s(signo, str, SIGXFSZ);
s2s(signo, str, SIGVTALRM);
s2s(signo, str, SIGPROF);
s2s(signo, str, SIGWINCH);
s2s(signo, str, SIGIO);
s2s(signo, str, SIGPWR);
s2s(signo, str, SIGSYS);
s2s(signo, str, SIGRTMIN);
}
int main() {
int n;
char str[1024];
while (1) {
scanf("%d", &n);
sig2str(n, str);
printf("%s\n", str);
}
}
重点是这里的宏定义,感觉自己对宏定义还是不太熟悉:
#define s2s(signo, str, signal) \
{ \
if (signo == signal) strcpy(str, #signal); \
}
我只知道基本的宏定义,参考了
进行更深入的了解。
10.3
画出运行图 10-9 程序时的栈帧情况
见 p745 ,图 C-11
10.4
图 10-11 程序中利用 setjmp
和 longjmp
设置 I/O 操作的超时,下面的代码也常见用于侧重目的:
signal(SIGALRM, sig_alrm); alarm(60); if (setjmp(env_alrm) != 0) { /* handle timeout */ }
这段代码有什么错误?
可能会导致longjmp在setjmp前调用
eg:
1.signal函数设置完信号处理程序
2.alarm函数设置闹钟
3.发生进程切换
4.60s后进程收到SIGALRM信号执行信号处理函数sig_alrm,longjmp在setjmp调用之前调用,发生错误
Github:
在第一次调用 alarm
和 setjmp
之间有一次竞争条件。如果进程在调用 alarm
和 setjmp
之间被内核阻塞了,闹钟时间超过后就调用信号处理程序,然后调用 longjmp
。但是由于没有调用过 setjmp
,所以没有设置 env_alrm
缓冲区。如果 longjmp
的跳转缓冲区没有被 setjmp
初始化,则说明 longjmp
的操作是未定义的。
10.5
仅适用一个定时器(alarm 或较高精度的 setitimer),构造一组函数,使得进程在该单一定时器基础上可以设置任意数量的定时器。
先复习一下alarm函数:
这里只说说我的思路(只说思路,C语言实现小根堆太麻烦了):
1.创建一个小根堆用于存放各计时器的剩余时间
2.只对剩余时间最小的那个计时器进行计时,将未进行计时的计时值存入小根堆中
3.每次my_alarm函数和sig_alrm函数都对小根堆中的计时器剩余时间值进行更新
#include"unistd.h"
/*用来改造alarm函数,来实现多个定时闹钟*/
unsigned int my_alarm(unsigned int secs)
{
int res_secs = alarm(secs);/*获取剩余的秒数*/
int pas_time = 堆顶元素值 - res_secs;/*得到自从上一次更新堆流逝时间*/
堆中所有元素值-=pas_time;/*更新堆中时间*/
if(res_secs == 0)/*没有正在计时的闹钟*/
{
将secs插入堆中;
return 0;
}
if(res_secs < secs)
{
alarm(res_secs);
}
else
{
alarm(secs);
}
将secs插入堆中;
}
static void sig_alrm(int signo)
{
if(signo != SIGALRM)
{
printf("发生了错误!!!\n");
exit(0);
}
int past_time = 堆顶元素;/*计算自从上一次更新堆的流逝时间*/
删除堆顶元素;
堆中所有元素值-=pas_time;/*更新堆中时间*/
alarm(新的堆顶元素);
}
int main()
{
return 0;
}
缺陷:感觉精度不够,没有考虑my_alarm和sig_alrm函数在执行时的时间流逝,有误差。
Github答案:
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
struct timer_t {
unsigned int started_at; // 设置定时器时的 UNIX 时间戳
unsigned int ended_at; // 设置的定时器应该触发的 UNIX 时间戳
void (*proc)(); // 超时执行的函数
struct timer_t *next; // 指向下一个定时器
};
struct timer_t *head = NULL;
/**
* 向队列中添加一个定时器
* 返回最近一个要触发的闹钟时间的余留秒数
*/
unsigned int push_to_timer_list(struct timer_t *node) {
unsigned int cur_time;
time((time_t *)&cur_time);
struct timer_t *cur = head;
/* 若当前没有待触发的定时器 */
if (cur == NULL) {
head = node;
alarm(node->ended_at - cur_time);
return (head->ended_at - cur_time);
} else {
/* 如果添加的定时器先于之前的第一个定时器 */
if (head->ended_at > node->ended_at) {
node->next = head;
head = node;
alarm(node->ended_at - cur_time);
return (node->ended_at - cur_time);
} else {
/* 移动游标 */
while (cur->next && cur->next->ended_at < node->ended_at) {
cur = cur->next;
}
node->next = cur->next;
cur->next = node;
return (head->ended_at - cur_time);
}
}
}
/**
* 触发指定的事件并启动下一个定时器
*/
void alarm_proc(int signo) {
struct timer_t *cur;
cur = head;
cur->proc();
if (cur->next) {
alarm(cur->next->ended_at - cur->ended_at);
free(head);
head = cur->next;
} else {
free(head);
head = NULL;
}
}
/**
* 类似于 JavsScript 的 settimeout 函数
* 参数是定时器的时间以及超时后执行的函数
*/
void settimeout(unsigned int seconds, void (*proc)()) {
/* 如果时间为 0 ,则直接执行 */
if (seconds == 0) {
proc();
return;
}
struct timer_t *t = (struct timer_t *)malloc(sizeof(struct timer_t));
time((time_t *)&(t->started_at));
t->ended_at = t->started_at + seconds;
t->next = NULL;
t->proc = proc;
if (signal(SIGALRM, alarm_proc) == SIG_ERR) {
printf("signal (SIGALRM) error\n");
exit(0);
}
push_to_timer_list(t);
}
void print() { printf("Hello World\n"); }
void print2() { printf("Hello MeiK\n"); }
int main() {
settimeout(2, print);
settimeout(1, print);
settimeout(5, print);
settimeout(3, print2);
printf("Hello main\n");
for (;;) {
pause();
}
return 0;
}
.......未完待续