linux之信号产生

产生信号的条件主要有:

A. 通过终端按键产生信号

用户在终端按下某些键时,终端驱动程序会发送信号给前台进程,例如Ctrl-C产生SIGINT信 号,Ctrl-\产生SIGQUIT信号,Ctrl-Z产生SIGTSTP信号。

SIGINT的默认处理动作是终止进程,
SIGQUIT的默认处理动作是终止进程并且Core Dump,

首先解释什么是Core Dump(核心转储)。当一个进程要异常终止时,可以选择把进程的用户空间内存数据全部保存到磁盘上,文件名通常是core,这叫做Core Dump。

进程异常终止通常是因为有 Bug,比如非法内存访问导致段错误,事后可以用调试器检查core文件以查清错误原因,这叫做 Post-mortem Debug(事后调试)。一个进程允许产生多大的core⽂文件取决于进程的 Resource Limit(这个信息保存 在PCB中)。默认是不允许产⽣生core文件的,因为core文件中 可能包含用户密码等敏感信息,不安全。

在开发调试阶段可以用ulimit命令改变这个限制, 允许产生core文件。
首先用ulimit命令改变Shell进程的Resource Limit,允许core文件最大为1024K:

这里写图片描述

test.c源代码:

#include 

int main()
{ 

    while(1);

    return 0;
}

因为ulimit命令改变了Shell进程的Resource Limit, test进程的PCB由Shell进程复制⽽而来,所以 也具有和Shell进程相同的Resource Limit值,这样就可以产生Core文件了。
这里写图片描述

进行调试test:
这里写图片描述

B. 硬件异常产生信号

这些条件由硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释为 SIGFPE信号发送给进程。再比如当前进程访问了非法内存地址,,MMU会产生异常,内核 将 这个异常解释为SIGSEGV信号发送给进程。

file.c 源代码:

#include 

void handler(int data)
{ 
    printf("sig is %d\n", data);
}

int main()
{ 
    signal(11, handler);
    int ret = 1/0;

    return 0;
}

这里写图片描述

C. 调用系统函数向进程发信号

首先在后台执行死循环程序,然后⽤用kill命令给它发SIGSEGV信号。

源代码:

 #include 

int main()
{ 

    while(1);

    return 0;
}

这里写图片描述

2829是test进程的id。之所以要再次回车才显示Segmentation fault,是因为在2829进程终止掉 之前已经回到了Shell提示符等待用户输入下一条命令,Shell不希望 Segmentation fault信息和用 户的输入交错在一起,所以等用户输入命令之后才显示。 指定某种信号的kill命令可以有多种写法,上面的命令还可以写成kill -SIGSEGV 4568 或kill -11 4568, 11是信号SIGSEGV的编号。以往遇 到的段错误都是由非法内存访问产 生的,而这个程序本身没错,给它发SIGSEGV也能产生段错误。

kill命令是调用kill函数实现的。
kill函数可以给一个指定的进程发送指定的信号。
raise函数可 以给当前进程发送指定的信号(自己给自己发信号)。

#include 
 int kill(pid_t pid, int signo); 
int raise(int signo); 
//这两个函数都是成功返回0,错误返回-1。

源代码:

#include 
#include

void handler(int data)
{ 
    printf("sig is %d\n", data);    
}

int main()
{ 
    signal(2, handler);
    sleep(3);

    while(1)
    { 
        raise(2);
        sleep(1);
    }
}

运行结果图:

这里写图片描述

abort函数使当前进程接收到 信号而异常终止。

#include  
void abort(void); 
//就像exit函数一样,abort函数总是会成功的,所以没有返回值。

运行结果图:

pause()函数使该进程暂停让出CPU

#include 

int pause(void);

源代码:

#include 
#include 
#include 
#include 

void sig_handler(int num)
{ 

    printf("receive the signal %d.\n", num);
    alarm(2);
}

int main()
{ 

    signal(SIGALRM, sig_handler);
    alarm(2);
    while(1){ 

        pause();
        printf("pause is over.\n");
    }
    exit(0);

}

运行结果图:
这里写图片描述

可以看出程序每隔2秒就会收到信号14,也就是SIGALRM信号;并且当处理完该信号之后,直接执行pause()函数下面的语句;说明pause()是可被中断的暂停;

alarm函数 和SIGALRM信号

 #include 
  unsigned int alarm(unsigned int seconds);

调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发 SIGALRM信号, 该信号的默认处理动作是终止当前进程。这个函数的返回值是0或者是以前 设定的闹钟时间还余下 的秒数。

源代码:

#include 


int main()
{ 
    alarm(1);
    int count = 0;

    while(1)
    { 
        printf("count is %d\n", count);
        count++;
    }
}
//这个程序的作用是1秒钟之内不停地数数,1秒钟到了就被SIGALRM信号终⽌止

运行结果图:
这里写图片描述

源代码:

#include 
#include 

int count = 0;
void handler(int data)
{ 
    printf("count is %d\n", count);
}

int main()
{ 
    signal(14, handler);
    alarm(1);

    while(1)
    { 
        count++;
    }

    return 0;
}
//这个函数遇上个函数作用相同,不同的是省去了I/O的输出的切换。

这里写图片描述

可以看出,一秒钟比上一个代码计数多了好多倍,说明有输出的会延缓计数。

你可能感兴趣的:(linux)