信号:

  信号是UNIX系统响应某些状况而产生的事件,进程在接收到信号时会采取相应的行动。

  信号是因为某些错误条件而产生的,比如内存段冲突、浮点处理器错误或者非法指令等。

  信号是在软件层次上对中断的一种模拟,所以通常把它称为是软中断。

  信号和中断的区别:  

    相似点:

      采用了相同的异步通信方式。

      当检测出有信号或者中断请求时,都暂停正在执行的程序而转去执行相应的处理程序。

      都在处理完毕后返回到原来的断点。

      对信号和中断都可以进行屏蔽。

    区别:

      中断有优先级,而信号没有优先级,所有的信号都是平等的。

      信号处理程序都是在用户态下运行的,而中断处理程序是在核心态下运行。

      中断响应是及时的,而信号响应通常都有较大的延迟。

  常用的信号如下所示:

进程对信号的三种响应如下:

man 7 signal可以查看信号的默认动作和信号的含义。

  signal用于安装一个信号处理函数,原型如下:

  __sighandler_t  signal(int signum, __sighandler_t  handler)

  signal是一个带signum和handler两个参数的函数,准备捕捉或者屏蔽的信号由参数signum给出,接收到指定信号时将要调用的函数由handler给出。

  handler这个函数必须有一个int型参数(即接收到的信号代码),它本身的类型是void。

  handler也可以是下面两个特殊值:

    SIG_IGN  忽略该信号

    SIG_DFL  恢复默认行为

示例程序如下:

 #include <stdio.h>
#include <stdlib.h>
#include <signal.h> void handler(int num)
{
printf("recv signal num = %d\n", num);
} int main()
{
pid_t pid; signal(SIGCHLD, SIG_IGN); pid = fork(); if(pid == -)
{
perror("fork error");
exit();
} if(pid == )
{
printf("child ... \n");
exit();
}
while()
{
pause();
} return ;
}

程序中,我们注册信号时,表示父进程忽略子进程的退出信号,因此,执行结果如下:

默认情况下(当我们不使用signal信号时),父进程会在退出的时候给子进程收尸(前提是子进程先死),可用如下程序证明:

 #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h> void handler(int num)
{
printf("recv signal num = %d\n", num);
} int main()
{
pid_t pid; //signal(SIGCHLD, SIG_IGN); pid = fork(); if(pid == -)
{
perror("fork error");
exit();
} if(pid == )
{
printf("child ... \n");
exit();
} sleep();
//while(1)
//{
// pause();
//} return ;
}

我们让父进程睡眠5秒,保证子进程先死,当父进程还在睡眠时,我们在另一个中断执行ps -ef,结果如下:

可以看到,父进程睡眠期间,子进程已经死了,而且处于僵尸状态,但是当父进程也结束时,子进程的僵尸状态消失了,说明父进程给它收尸了。当把17行的注释打开时,就是告诉内核,子进程死的时候让内核给他收尸,因此,我们看不到子进程处于僵尸状态的现象了(内核收尸太快了)。

  下面我们演示注册一个真正的信号处理函数和恢复默认行为的程序:

 #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h> void handler(int num)
{
printf("recv signal num = %d\n", num);
} int main()
{
pid_t pid;
char c; signal(SIGINT, handler); while( (c = getchar()) != 'a')
{
pause();
} signal(SIGINT, SIG_DFL);
while()
{
pause();
} return ;
}

SIGINT代表ctrl+c信号,18行将这个信号的处理函数注册为handler,当程序停在20行时,我们按下ctrl+c,handler会得到执行。 输入a使程序执行到26行,这时候SIGINT信号的行为恢复到了默认行为(25行中的SIG_DFL表示恢复默认行为),我们再按下ctrl+c,程序直接退出了(这就是默认行为)。执行现象如下:

  signal函数执行成功时,返回默认的处理函数,执行失败时返回SIG_ERR。我们将上述程序改成以下方式:

 #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h> void handler(int num)
{
printf("recv signal num = %d\n", num);
} int main()
{
pid_t pid;
char c; __sighandler_t old = signal(SIGINT, handler); if(SIG_ERR == old)
{
perror("signal error");
} while( (c = getchar()) != 'a')
{
pause();
} signal(SIGINT, old);
printf("huan yuan\n");
while()
{
pause();
} return ;
}

使用signal注册信号处理函数时,将默认的处理函数返回到old中,并在下面进行了错误处理,28行恢复默认行为,执行结果如下:

信号的分类:可靠与不可靠信号,实时与非实时信号。实时信号都是可靠信号,非实时信号都是不可靠信号。1-31号都是不可靠信号。不可靠信号就是向应用程序发送了多次信号,应用程序可能只接收到了一次。可靠信号就是向应用程序发几次信号都能保证全接收到。早期unix系统每接收到一个信号就将处理程序恢复到默认行为。现在的linux中的不可靠信号主要指信号可能会丢失。

信号发送函数kill和raise,如下所示:

kill可以向指定进程发送信号,raise向自身发送信号。 kill是示例程序如下:

 #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h> void my_handler(int num)
{
if(SIGINT == num)
{
printf("recv signal SIGINT\n");
}
else if(SIGUSR1 == num)
{
printf("recv signal SIGUSR1\n");
}
else
{
printf("recv signal num = %d\n", num);
}
} int main()
{
pid_t pid; if(signal(SIGINT, my_handler) == SIG_ERR)
{
perror("signal error");
} if(signal(SIGUSR1, my_handler) == SIG_ERR)
{
perror("signal error");
} pid = fork(); if(pid == -)
{
perror("fork error");
exit();
} if(pid == )
{
pid = getppid();
kill(pid, SIGUSR1);
exit();
} int nsleep = ; do
{
printf("parent process begin sleep\n");
nsleep = sleep(nsleep);
printf("parent process end sleep\n");
}while(nsleep > ); return ;
}

子进程通过kill向父进程发信号,父进程在睡眠,当收到信号时就去执行信号处理函数,执行完之后,发现nsleep不为0,也即没有睡够指定的时间,因此再次进入睡眠,直到睡完为止。执行结果如下:

小知识:

  getpgrp()获取进程组id, kill(pid, SIGINT)向进程组发送SIGINT。父进程注册的信号处理函数也会复制给子进程(在fork之前注册信号)。sleep函数返回值是剩余的秒数。sleep能被信号打断,是可中断睡眠,处理信号函数返回以后就不再睡眠了,继续向下执行。wait使进程进入的睡眠也是可中断睡眠。

pause函数:

  将进程置为可中断睡眠状态,然后它调用内核函数schedule(),使linux进程调度器找到另一个进程来运行。

  pause使调用者进程挂起,直到一个信号被捕获。

alarm函数:

  设置一个闹钟,延迟发送信号,告诉linux内核n秒钟以后,给本进程发送SIGALRM信号。示例程序如下:

 #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h> void my_handler(int num)
{
printf("recv signal num = %d\n", num);
} int main()
{
if(signal(SIGALRM, my_handler) == SIG_ERR)
{
perror("signal error");
exit();
} alarm(); pause();
printf("return from pause \n"); return ;
}

执行结果如下所示:

可重入与不可重入:

示例程序如下:

 #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h> typedef struct _Teacher
{
int age;
int num;
}Teacher; Teacher g_t; void printGlobalTeacher()
{
printf("g_t.age = %d\n", g_t.age);
printf("g_t.num = %d\n", g_t.num);
} void my_handler(int num)
{
printf("recv signal num = %d\n", num);
printGlobalTeacher();
alarm();
} int main()
{
Teacher t1,t2;
t1.age = ;
t1.num = ;
t2.age = ;
t2.num = ; if(signal(SIGALRM, my_handler) == SIG_ERR)
{
perror("signal error");
exit();
} alarm(); while()
{
g_t = t1;
g_t = t2;
//printf("return from pause \n");
}
return ;
}

上述程序中在信号处理函数中调用printGlobalTeacher函数,这个函数是不可重入的函数,因为函数内部访问了全局变量g_t, 因此打印结果有可能出错。执行结果如下:

可以看到出现了age和num不相等的情况,这就是出错了。

2.2 linux中的信号分析的更多相关文章

  1. 2.3 linux中的信号分析 阻塞、未达

    信号的阻塞.未达: linux中进程1向进程2发送信号,要经过内核,内核会维护一个进程对某个信号的状态,如下图所示: 当进程1向进程2发送信号时,信号的传递过程在内核中是有状态的,内核首先要检查这个信 ...

  2. 25 Linux中的信号

    Linux中的信号 信号是进程在运行过程中,由自身产生或由进程外部发过来的消息(事件).每个信号用一个整型常量宏表示,以SIG开头,比如SIGCHLD.SIGINT等,它们在系统头文件中定义,也可以通 ...

  3. Linux中多线程信号的处理

    1. 博文:Linux多线程中使用信号-1  http://blog.csdn.net/qq276592716/article/details/7325250 2. 博文:Linux多线程信号总结  ...

  4. Linux中的日志分析及管理

    日志文件对于诊断和解决系统中的问题很有帮助,因为在Linux系统中运行的程序通常会把系统消息和错误消息写入相应的日志文件,这样系统一旦出现问题就会“有据可查”.此外,当主机遭受攻击时,日志文件还可以帮 ...

  5. linux中的信号机制

    概述 Linux信号机制是在应用软件层次上对中断机制的一种模拟,信号提供了一种处理异步事件的方法,例如,终端用户输入中断键(ctrl+c),则会通过信号机制停止一个程序[1]. 这其实就是向那个程序( ...

  6. linux中的信号简介和trap命令

    1.信号 linux通过信号来在运行在系统上的进程之间通信,也可以通过信号来控制shell脚本的运行 主要有一下信号 1 ##进程重新加载配置 2 ##删除进程在内存中的数据 3 ##删除鼠标在内存中 ...

  7. 转:linux中select()函数分析

    源地址:http://blog.csdn.net/zi_jin/article/details/4214359 Select在Socket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱 ...

  8. [转帖]Linux教程(8)-Linux中的进程和日志㐇、

    Linux教程(8)-Linux中的进程和日志 2018-08-20 23:42:23 钱婷婷 阅读数 3554更多 分类专栏: Linux教程与操作 Linux教程与使用   版权声明:本文为博主原 ...

  9. Java基础(一):I/O多路复用模型及Linux中的应用

    IO多路复用模型广泛的应用于各种高并发的中间件中,那么区别于其他模式他的优势是什么.其核心设计思想又是什么.其在Linux中是如何实现的? I/O模型 I/O模型主要有以下五种: 同步阻塞I/O:I/ ...

随机推荐

  1. hdu 1014 Uniform Generator 数论

    摘取于http://blog.csdn.net/kenden23/article/details/37519883: 找到规律之后本题就是水题了,不过找规律也不太容易的,证明这个规律成立更加不容易. ...

  2. C++ Boost在VS2015中的使用

    1.下载包 目录结构: 切换到上面的目录,然后运行 bootstrap.bat 执行完毕后会生成两个exe文件 继续执行 bjam.exe 结束后,目录如下 2.设置路径 测试 #include &q ...

  3. 常见dos命令汇总

    常用的内部命令有md.cd.rd.dir.path.copy.type.edit.ren.del.cls.ver.date.time.prompt.常用的外部命令有deltree.format.dis ...

  4. SQLServer 大小写转换

    SQLServer中小转大也是同样的默认快捷键,大转小换成L了 1.小写转换大写  Ctrl + shift + U 2.大写转换小写  Ctrl + shift + L

  5. OpenGL入门程序四:颜色模式

    1.OpenGL支持两种颜色模式: 1>RGBA颜色模式 ,用 glClearColor 指定清空屏幕后的颜色,即“空颜色” . 2>索引颜色模式,用 glClearIndex 指定清空屏 ...

  6. [可能没有默认的字体]Warning: imagettfbbox() [function.imagettfbbox]: Invalid font filename...

    Warning: imagettfbbox() [function.imagettfbbox]: Invalid font filename... [可能没有默认的字体] 例: //putenv('G ...

  7. LeetCode--070--爬楼梯

    problem description: 假设你正在爬楼梯.需要 n 阶你才能到达楼顶. 每次你可以爬 1 或 2 个台阶.你有多少种不同的方法可以爬到楼顶呢? 注意:给定 n 是一个正整数. 示例 ...

  8. (转载)-关于sg函数的理解

    最近学习了nim博弈,但是始终无法理解sg函数为什么sg[S]=mex(sg[S'] | S->S'),看到一篇博文解释的不错,截取了需要的几章节. 四.Sprague-Grundy数的提出 我 ...

  9. springboot activiti关闭验证自动部署

    # spring-activiti # 自动部署验证设置:true-开启(默认).false-关闭 spring.activiti.check-process-definitions=false # ...

  10. 在Windows下为PHP安装redis扩展

    1.使用phpinfo()函数查看PHP的版本信息,这会决定扩展文件版本 2.选择http://windows.php.net/downloads/pecl/snaps/redis/2.2.5/ ht ...