Linux---信号
信号对于我们来说已经很熟悉了,走在路上红灯亮起,这时我们就停下脚步,绿灯亮起,又继续前行;当水壶哨声响起的时候,这时我们就过去关掉电源……
那么我们为什么会对那种现象进行识别,并做出一系列的反应呢?
这是因为大脑能记住生活中的这种信号进行识别并做出动作。那么进程也是如此。
信号的产生:
1.用户在终端按下某些键时,终端驱动程序会发送信号给前台进程;
终止程序:在键盘按下ctrl+\ 组合键产生信号SIGQUIT,SIGQUIT默认动作是终止程序并Core Dump(核心转储).
Core Dump:当一个进程在异常终止的时候,操作系统会将内存中进程的有效数据拷贝到磁盘上,文件名通常是core。
查询core文件大小命令:ulimit -a
修改core 文件大小命令:ulimit -c 大小
Core Dump:可以定位程序错误
2.硬件异常产生信号(如:除数为0,无效的内存引用等),硬件检测到并通知内核,内核会向当前进程发送信号;
#include<stdio.h>
#include<stdlib.h>
int main(int argc,char* argv)
{
if(argc!=2){
printf("Usage:%s,symbol",argv[0]);
exit(1);
}
return 0;
}
3.用户调用kill命令将信号发送给其他进程,进程调用kill函数将信号发送给另一个进程或进程组;
函数: int kill(pid_t pid,int signo)
:给指定进程发指定信号
int main(int argc,char* argv[])
{
if(argc!=3){
printf("Error:%s,signo,pid\n",argv[0]);
exit(1);
}
kill(atoi(argv[2]),atoi(argv[1]));
return 0;
}
int raise(int signo)
:给当前进程发送特定信号
void abort(void)
:使当前进程接收到信号异常终止
4.当检测到某些软件条件已经产生,将其通知有关进程时产生信号。
int count=0;
void handler(int signo)
{
printf("signo:%d count:%d\n",signo,count);
exit(1);
}
int main(int argc,char* argv[])
{
signal(14,handler);
alarm(1);//信号没产生前的一秒,将数据累加多少次
while(1){
printf("count=%d\n",count);
count++;
}
return 0;
}
信号在内核中的存在:
递达(Delivery):实际执行信号的处理动作;
阻塞(Block):被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作;
未决(Pending):信号从产生到递达之间的状态。
信号集:
每个信号只有一个bit的未决标志或阻塞标志,不记录该信号产生多少次,因此可以用相同的数据类型sigset_t来存储,sigset_t称为信号集。
使用者只能调用以下函数对sigset_t变量进行操作,不应该对它的内部数据进行解释。
int sigemptyset(sigset_t* set);
初始化set所指向的信号集,使其中所有信号的对应bit位清零,表示该信号集不包含任何有效信号; int sigfillset(sigset_t* set);
初始化set所指向的信号集,使其中所有信号的对应bit置位,表示该信号集的有效信号包含系统支持的所有信号; int sigaddset(sigset* set,int signo);
将一个信号signo添加到set所指向的信号集中; int sigdelset(sigset_t* set,int signo);
将一个信号signo从set所指向的信号集中删除;
以上四个函数都是成功返回0,失败返回-1. int sigismember(sigset_t* set,int signo);
判断一个信号signo是否在set所指向的信号集中。
sigismember函数是一个bool函数,包含则返回1,不包含则返回0,出错返回-1.
读取或更改进程的信号屏蔽字函数(阻塞信号集): int sigprocmask(int how,const sigset_t *set,sigset_t* oset);
参数:how:如何更改当前信号屏蔽字;
读取进程的当前信号屏蔽字由oset参数传出,若set和oset都为非空,则将原来的信号屏蔽字备份到oset里。
读取当前进程的未决信号集: int sigpending(sigset_t* set);
函数返回信号集,其中的各个信号对于调用进程是阻塞的而不能递达,因此也一定是当前未决的。信号集由set参数返回。
成功返回0,失败返回-1.
void Print(sigset_t* set)
{
int i=0;
for(;i<32;++i){
//判断信号是否在信号集中,在的话就将对应比特位置1
if(sigismember(set,i)){
putchar('1');
}
else{
putchar('0');
}
}
printf("\n");
}
int main()
{
sigset_t sig,psig;
//对信号集进行初始化
sigemptyset(&sig);
//将信号SIGINT添加到信号集中
sigaddset(&sig,SIGINT);
//设置进程的信号屏蔽字
sigprocmask(SIG_BLOCK,&sig,NULL);
while(1){
//获取当前进程的未决信号集
sigpending(&psig);
Print(&psig);
sleep(1);
}
return 0;
}
捕捉信号:
什么时候对信号进行捕捉是合适的时机?
从上图可知:捕捉信号的最佳时机在进程从内核态返回到用户态的时候,这个时候会进行一次信号的检测和处理,如果信号的处理动作是用户自定义函数,信号递达时调用这个处理函数,这个过程称为信号的捕捉。
如何去捕捉信号?
读取或修改与指定信号相关联的处理动作函数:
int sigaction(int signo,const struct sigaction* act,struct sigaction* oact);
结构体:struct sigaction
{
void(*)(int) sa_handle;
sigset_t sa_mask;
int sa_flags;
}
act:若非空,则根据act修改该信号的处理动作;
oact:若非空,则根据oact传出原来的信号处理动作。
返回值:成功返回0,失败返回-1.
使调用进程挂起直到有信号递达函数:
int pause(void)
如果信号处理动作是终止进程,进程终止,函数不返回;
如果信号处理动作是忽略,进程还是处于挂起状态,函数不返回;
如果信号处理动作是捕捉,则调用信号处理函数,成功pause返回-1,出错error设置为EINTR。
思考:调用pause()函数之后,信号递达,设置的信号处理函数啥都不做,那么信号处理函数有没有必要存在?
有必要。
调用pause()函数之后,进程处于挂起状态,当信号递达的时候信号自动添加到信号屏蔽字中(当该信号再次到来的时候会被阻塞到当前处理结束),信号处理函数调用完毕之后信号解除屏蔽,pause()函数返回,进程继续执行;如果没有信号处理函数,pause()函数将使进程一直处于挂起状态。
竞态条件:由于程序执行的时序问题导致的错误。
int sigsuspend(const sigset_t* sigmask )
通过指定的sigmask来临时解除对某些信号的屏蔽。没有成功返回值,调用信号处理函数返回-1,error设置为EINTR。
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
void handler(int signo)
{
printf("get a signo:%d\n",signo);
exit(1);
}
void mysleep(int seconds)
{
struct sigaction new, old;
sigset_t newmask,oldmask,midmask;
unsigned int count=0;
new.sa_handler=handler;
sigemptyset(&new.sa_mask);
new.sa_flags=0;
sigaction(SIGALRM,&new,&old);
sigemptyset(&newmask);
sigaddset(&newmask,SIGALRM);
sigprocmask(SIG_BLOCK,&newmask,&oldmask);
alarm(seconds);
midmask=oldmask;
sigdelset(&midmask,SIGALRM);
sigsuspend(&midmask);
count=alarm(0);
sigaction(SIGALRM,&old,NULL);
sigprocmask(SIG_SETMASK,&oldmask,NULL);
}
int main()
{
while(1){
printf("I like eating icecream!\n");
mysleep(1);
}
return 0;
}
上一篇: 如何学会如何写好一篇广告软文?
下一篇: JavaScript原型链详解