Linux高级编程--11.信号

基本概念

信号在Linux中是一个比较常见的概念,例如我们按Ctrl+C中断前台进程,通过Kill命令结束进程都是通过信号实现的。下面就以Ctrl+C为例简单的说明信号的处理流程:

  • 用户按下Ctrl-C,这个键盘输入产生一个硬件中断。
  • 该进程的用户空间代码暂停执行,CPU从用户态切换到内核态处理硬件中断。
  • 终端驱动程序将Ctrl-C解释成一个SIGINT信号,记在该进程的PCB中(也可以说发送了一个SIGINT信号给该进程)。
  • 当内核返回到该进程的用户空间代码继续执行之前,首先处理PCB中记录的信号,发现有一个SIGINT信号待处理,而这个信号的默认处理动作是终止进程,所以直接终止进程而不再返回它的用户空间代码执行。

用kill -l命令可以察看系统定义的信号列表:

$ kill –l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL
 5) SIGTRAP      6) SIGABRT      7) SIGBUS       8) SIGFPE
 9) SIGKILL     10) SIGUSR1     11) SIGSEGV     12) SIGUSR2
13) SIGPIPE     14) SIGALRM     15) SIGTERM     16) SIGSTKFLT
17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU
25) SIGXFSZ     26) SIGVTALRM   27) SIGPROF     28) SIGWINCH
29) SIGIO       30) SIGPWR      31) SIGSYS      34) SIGRTMIN
35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3  38) SIGRTMIN+4
39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12
47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14
51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10
55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7  58) SIGRTMAX-6
59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h中找到,可以通过man signal(7)查看详细说明:

Signal Value Action Comment
-------------------------------------------------------------------------
SIGHUP 1 Term Hangup detected on controlling terminal
                             or death of controlling process
SIGINT 2 Term Interrupt from keyboard
SIGQUIT 3 Core Quit from keyboard
SIGILL 4 Core Illegal Instruction
...

产生信号的条件主要有:

用户在终端按下某些键时,终端驱动程序会发送信号给前台进程。
例如常见的Ctrl-C产生SIGINT信号,Ctrl-\产生SIGQUIT信号,Ctrl-Z产生SIGTSTP信号。
硬件异常产生信号,这些条件由硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号发送给进程。
一个进程调用kill函数可以发送信号给另一个进程。
当内核检测到某种软件条件发生时也可以通过信号通知进程,例如闹钟超时产生SIGALRM信号,向读端已关闭的管道写数据时产生SIGPIPE信号。

捕捉信号

如果不想按默认动作处理信号,用户程序可以调用sigaction函数接管该信号的处理流程。由于信号处理函数的代码是在用户空间的,处理过程比较复杂,举例如下:

  • 用户程序注册了SIGQUIT信号的处理函数sighandler。
  • 当前正在执行main函数,这时发生中断或异常切换到内核态。
  • 在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。
  • 内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函数,sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程。
  • ighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。
  • 如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了。

sigaction函数可以读取和修改与指定信号相关联的处理动作,它的声明如下:

#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

其中关键就是第二个参数act,他是一个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这个参数,它通常可以有如下几种赋值:

  • 常数SIG_IGN表示忽略信号
  • 常数SIG_DFL表示执行系统默认动作,一般用于恢复信号处理
  • 赋值为一个函数指针表示用自定义函数捕捉信号

另外,如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。

下面就以一个简单的例子演示下如何实现捕捉信号的过程,该函数的功能比较简单,就是在Ctrl+C的时候并不直接退出,而是先输出一条华丽的分割线后才退出。

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>

void show_and_exit(int sig)
{
    printf("\n----------------------------\n");
    exit(0);
}

int main(void)
{
    struct sigaction act = {0}, oldact = {0};

    act.sa_handler = show_and_exit;
    //act.sa_flags = SA_RESETHAND | SA_NODEFER;
    //sigaddset(&act.sa_mask, SIGQUIT);

    sigaction(SIGINT, &act, &oldact);

    int count = 0;
    while(1)
    {
        sleep(1);
        printf("sleeping %d\n", count++);
    }
}

执行该函数结果如下:

$./sign
sleeping 0
sleeping 1
sleeping 2
sleeping 3
sleeping 4

----------------------------
$

来自为知笔记(Wiz)

时间: 2024-08-08 18:41:23

Linux高级编程--11.信号的相关文章

linux系统编程之信号(四)

今天继续探讨信号相关的东东,话不多说,正入正题: 信号在内核中的表示: 下面用图来进一步描述这种信号从产生到递达之间的状态(信号阻塞与未诀): 那是怎么来决定的呢?下面慢慢来举例分解: 所以,通过这些图,可以描述信号从产生到递达的一个过程,上面的理解起来可能有点难,下面会用代码来进一步阐述,在进行实验之前,还需了解一些函数的使用,这些函数在实验中都会被用到,也就是信号集操作函数. 信号集操作函数: 其中解释一下sigset_t,百度百科解释为: 而这个函数的意义就是将这64位清0 这个函数的意义

UC高级编程--利用信号实现进程间通信

/********************************************************************** *Copyright (c) 2014,TianYuan *All rights reserved. * * 文件名称: sigin.c * 文件标识:无 * 内容摘要:利用信号实现进程间通信,测试代码. 把要发送的数据存放到文件中,并发送信号. * 其它说明:无 * 当前版本: V1.0 * 作 者: wuyq * 完成日期: 20140711 * *

Linux环境编程之信号(一):信号基本概述

引言 假如在后台运行一个可执行程序./a.out,如果想终止该程序,通常会按下Ctrl-C,从而产生一个中断,其实这个过程的实现就是通过信号完成的.信号是软件中断,它提供了一种处理异步事件的方法. (一) 每个信号都有一个名字,这些名字都以三个字符SIG开头.例如SIGALARM是闹钟信号,当由alarm函数设置的计时器超时后产生此信号.Linux除支持31种不同信号外,还支持应用程序额外定义信号.信号定义在<bits/signum.h>中,也可以通过命令kill -l查看. (二)信号的产生

Linux环境编程之信号(三):一些信号函数

(一)kill和raise函数 kill函数将信号发送给进程或进程组.raise函数则允许进程自身发送信号. #include <sys/types.h> #include <signal.h> int kill(pid_t pid, int sig); int raise(int  signo);  //返回值:若成功则返回0,若出错则返回-1. 参数:pid参数有4种情况:1.pid > 0 将信号发送给进程为pid的进程.2.pid == 0 将该信号发送给与发送进程属

Linux环境编程之信号(二):不可靠信号、中断的系统调用、可重入函数

(一)不可靠信号 对前面说的信号,是不可靠的,不可靠指的是信号可能会丢失:一个信号发生了,但进程却可能一直不知道这一点.另外,进程对信号的控制能力有限,只能捕捉信号或忽略它.有时用户希望通知内核阻塞一个信号:不要忽略它,在其发生时记住它,然后在进程做好准备时再通知它.这种阻塞信号的能力并不具备. 之前的版本中村咋一个问题:在进程每次接到信号对其进行处理时,随即将该信号动作复位为默认值.另一个问题是,在进程不希望某种信号发生时,它不能关闭该信号.进程能做的一切就是忽略该信号. (二)中断的系统调用

Linux——高级编程之概要

1.为什么要学习Linux下的高级编程 应用课程的学习,不知道Linux内核的强大功能 Linux下的高级编程课程学习:感知到内核的存在,内核的强大功能 文件管理 进程管理 设备管理 内存管理 网络管理 2.怎么样学习Linux下的高级编程 Linux下高级编程的特点:涉及到内核向用户空间提供的接口(函数) 3.为什么内核要提供这些接口呢 主要原因: A:内核要为应用程序服务,应用程序如果没有内核服务,则功能非常单一 B:内核是一个稳定的代码,同时也要为多个用户空间的程序服务,为了防止用户空间的

UNIX环境高级编程之-----信号signal

参考书籍:unxi环境高级编程 信号函数: <span style="font-family:Microsoft YaHei;font-size:18px;">typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); </span> 其原型为: <span style="font-family:Microsoft YaHei;

Linux系统编程_10_信号

信号处理机制在linux编程中非常重要,它类似于单片机中的中断系统:我们在编写中断函数时,需要设置中断函数的地址,设置其相应的寄存器,以便于发生中断事件时可以正确的跳到中断函数去执行: linux中信号和这个类似,一般的编程模型是定义中断函数,然后把中断函数注册,使得进程收到特定的信号时,可以跳到信号处理函数去执行: 1.kill函数与raise函数 kill函数用来将信号发送给进程或进程组: --int kill(pid_t pid,  int signo); raise函数则允许进程向自身发

Linux高级编程--04.GDB调试程序(查看数据)

查看栈信息 当程序被停住了,你需要做的第一件事就是查看程序是在哪里停住的.当你的程序调用了一个函数,函数的地址,函数参数,函数内的局部变量都会被压入"栈"(Stack)中.你可以用GDB命令来查看当前的栈中的信息. 下面是一些查看函数调用栈信息的GDB命令: backtrace / bt :打印当前的函数调用栈的所有信息.如: (gdb) bt #0 func (n=250) at tst.c:6 #1 0x08048524 in main (argc=1, argv=0xbffff6