Linux:信号处理原理与实现

本文详细介绍了Linux信号处理的原理,包括信号的来源、类型,以及处理机制。通过示例展示了如何响应Ctrl+C信号,并探讨了Linux内核中信号的发送、接收和处理流程,包括信号队列、信号处理函数的触发和执行过程。
摘要由CSDN通过智能技术生成

什么是信号

信号本质上是在软件层次上对中断机制的一种模拟,其主要有以下几种来源:

  • 程序错误:除零,非法内存访问等。
  • 外部信号:终端 Ctrl-C 产生 SGINT 信号,定时器到期产生SIGALRM等。
  • 显式请求:kill函数允许进程发送任何信号给其他进程或进程组。

目前 Linux 支持64种信号。信号分为非实时信号(不可靠信号)和实时信号(可靠信号)两种类型,对应于 Linux 的信号值为 1-31 和 34-64。

信号是异步的,一个进程不必通过任何操作来等待信号的到达。事实上,进程也不知道信号到底什么时候到达。一般来说,我们只需要在进程中设置信号相应的处理函数,当有信号到达的时候,由系统异步触发相应的处理函数即可。如下代码:

#include <signal.h> 
#include <unistd.h> 
#include <stdio.h> 
 
void sigcb(int signo) { 
    switch (signo) { 
    case SIGHUP: 
        printf("Get a signal -- SIGHUP\n"); 
        break; 
    case SIGINT: 
        printf("Get a signal -- SIGINT\n"); 
        break; 
    case SIGQUIT: 
        printf("Get a signal -- SIGQUIT\n"); 
        break; 
    } 
    return; 
} 
 
int main() { 
    signal(SIGHUP, sigcb); 
    signal(SIGINT, sigcb); 
    signal(SIGQUIT, sigcb); 
    for (;;) { 
        sleep(1); 
    } 
} 

运行程序后,当我们按下 Ctrl+C 后,屏幕上将会打印 Get a signal -- SIGINT。当然我们可以使用 kill -s SIGINT pid 命令来发送一个信号给进程,屏幕同样打印出 Get a signal -- SIGINT 的信息。

信号实现原理

接下来我们分析一下Linux对信号处理机制的实现原理。

信号处理相关的数据结构

在进程管理结构 task_struct 中有几个与信号处理相关的字段,如下:

struct task_struct { 
    ... 
    int sigpending; 
    ... 
    struct signal_struct *sig; 
    sigset_t blocked; 
    struct sigpending pending; 
    ... 
} 

成员 sigpending 表示进程是否有信号需要处理(1表示有,0表示没有)。成员 blocked 表示被屏蔽的信息,每个位代表一个被屏蔽的信号。成员 sig 表示信号相应的处理方法,其类型是 struct signal_struct,定义如下:

#define  _NSIG  64 
 
struct signal_struct { 
 atomic_t  count; 
 struct k_sigaction action[_NSIG]; 
 spinlock_t  siglock; 
}; 
 
typedef void (*__sighandler_t)(int); 
 
struct sigaction { 
 __sighandler_t sa_handler; 
 unsigned long sa_flags; 
 void (*sa_restorer)(void); 
 sigset_t sa_mask; 
}; 
 
struct k_sigaction { 
 struct sigaction sa; 
}; 

可以看出,struct signal_struct 是个比较复杂的结构,其 action 成员是个 struct k_sigaction 结构的数组,数组中的每个成员代表着相应信号的处理信息,而 struct k_sigaction 结构其实是 struct sigaction 的简单封装。

我们再来看看 struct sigaction 这个结构,其中 sa_handler 成员是类型为 __sighandler_t 的函数指针,代表着信号处理的方法。

最后我们来看看 struct task_struct 结构的 pending 成员,其类型为 struct sigpending,存储着进程接收到的信号队列,struct sigpending 的定义如下:

struct sigqueue { 
 struct sigqueue *next; 
 siginfo_t info; 
}; 
 
struct sigpending { 
 struct sigqueue *head, **tail; 
 sigset_t signal; 
}; 

当进程接收到一个信号时,就需要把接收到的信号添加 pending 这个队列中。

发送信号

可以通过 kill() 系统调用发送一个信号给指定的进程,其原型如下:

int kill(pid_t pid, int sig); 

参数 pid 指定要接收信号进程的ID,而参数 sig 是要发送的信号。kill() 系统调用最终会进入内核态,并且调用内核函数 sys_kill(),代码如下:

asmlinkage long 
sys_kill(int pid, int sig) 
{ 
 struct siginfo info; 
 
 info.si_signo = sig; 
 info.si_errno = 0; 
 info.si_code = SI_USER; 
 info.si_pid = current->pid; 
 info.si_uid = current->uid; 
 
 return kill_something_info(sig, &info, pid); 
} 

sys_kill() 的代码比较简单,首先初始化 info 变量的成员,接着调用 kill_something_info() 函数来处理发送信号的操作。kill_something_info() 函数的代码如下:

static int kill_something_info(int sig, struct siginfo *info, int pid) 
{ 
 if (!pid) { 
  return kill_pg_info(sig, info, current->pgrp); 
 } else if (pid == -1) { 
  int retval = 0, count = 0; 
  struct task_struct * p; 
 
  read_lock(&tasklist_lock); 
  for_each_task(p) { 
   if (p->pid > 1 && p != current) { 
    int err =
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值