linux通过信号回调函数,信号机制的管理结构 - Linux内核中的信号机制_Linux编程_Linux公社-Linux系统门户网站...

信号只是一个数字,数字为0-31表示不同的信号,如下表所示。

编号

信号名

默认动作

说明

1

SIGHUP

进程终止

终端断开连接

2

SIGINT

进程终止

用户在键盘上按下CTRL+C

3

SIGQUIT

进程意外结束(Dump)

用户在键盘上按下CTRL+\

4

SIGILL

进程意外结束(Dump)

遇到非法指令

5

SIGTRAP

进程意外结束(Dump)

遇到断电,用于调试

6

SIGABRT/SIGIOT

进程意外结束(Dump)

7

SIGBUS

进程意外结束(Dump)

总线错误

8

SIGFPE

进程意外结束(Dump)

浮点异常

9

SIGKILL

进程终止

其他进程发送SIGKILL将导致目标进程终止

10

SIGUSR1

进程终止

应用程序可自定义使用

11

SIGSEGV

进程意外结束(Dump)

非法的内存访问

12

SIGUSR2

进程终止

应用程序可自定义使用

13

SIGPIPE

进程终止

管道读取端已经关闭,写入端进程会收到该信号

14

SIGALRM

进程终止

定时器到时

15

SIGTERM

进程终止

发送该信号使目标进程终止

16

SIGSTKFLT

进程终止

堆线错误

17

SIGCHLD

忽略

子进程退出时会向父进程发送该信号

18

SIGCONT

忽略

进程继续执行

19

SIGSTOP

进程暂停

发送该信号会使目标进程进入TASK_STOPPED状态

20

SIGTSTP

进程暂停

在终端上按下CTRL+Z

21

SIGTTIN

进程暂停

后台进程从控制终端读取数据

22

SIGTTOU

进程暂停

后台进程从控制终端读取数据

23

SIGURG

忽略

socket收到设置紧急指针标志的网络数据包

24

SIGXCPU

进程意外结束(Dump)

进程使用CPU已经超过限制

25

SIGXFSZ

进程意外结束(Dump)

进程使用CPU已经超过限制

26

SIGVTALRM

进程终止

进程虚拟定时器到期

27

SIGPROF

进程终止

进程Profile定时器到期

28

SIGMNCH

忽略

进程终端窗口大小改变

29

SIGIO

进程暂停

用于异步IO

29

SIGPOLL

进程暂停

用于异步IO

30

SIGPWR

进程暂停

电源失效

31

SIGUNUSED

进程暂停

保留未使用

注意在上标中的默认动作是指,在没有任何程序为相应的信号设置信号处理函数的情况下,内核接收到该信号的默认处理方式,但在实际中,有可能不是这样的。另外,在这里,进程终止一般是指进程通过do_exit()退出,进程意外结束(Dump)则表示进程遇到了一个异常。默认情况下,内核会根据进程当时的内存情况,在进程的当前目录中生成一个Core Dump文件,以后用户可以通过这个文件分析进程异常的原因。这个工作主要通过do_coredump()来完成。

由于早期只有31个信号,内核仅仅使用一个32位的变量signal来表示进程接收到的信号,因此如果要向一个进程发送一个信号,就把signal的第n位设置为1,这非常类似中断请求寄存器SRCPND寄存器,同时,还有一个blocked的变量,用来屏蔽信号,这类似中断屏蔽寄存器INTMSK。这样做的好处是可以“很快”判断出一个进程收到了哪些信号,如果采用链表或者数组,则需要扫描整个队列,但这也带来了新的问题,如果向一个进程发送了SIGINT信号,在这个信号处理之前,再次发送SIGINT,当这个进程开始处理信号时,它只知道收到了SIGINT信号,而无法判断出有几个SIGINT需要处理。此后加入了信号队列,把收到的信号保存在这个队列中,就可以很好的解决这个问题了。但是为了兼容的目的,仍能保留了旧的信号处理方式,因此1-32还是按原有的方式进行处理,而33-64则使用新的机制,为了区别对待,编号为33-64的信号又称为实时信号。需要注意的是:这里的“实时”和实时操作系统中的“实时”没有任何联系,实时信号在处理速度上并不会比普通信号快,它们之间的区别就是:普通信号会对多次的同一个信号进行“合并”处理,而实时信号会一一处理。因此我们这里仅讨论普通信号。

信号机制的相关管理结构位于task_struct结构中,其主要结构如下图所示。

c4357de690bc0dfe0c6cfd600ea0ab3d.png

实时信号引入了信号队列,为了处理上的方便,普通信号也使用了信号队列,仅仅从数字上无法区分实时信号和普通信号。由于在linux中,进程对象和线程对象都是task_struct,因此需要区别对待线程的信号和进程的信号。在上图中,Private Signal Queue是线程(在linux中称为轻权进程)信号队列,而Shared Signal Queue是进程(在linux中被称为进程组)信号队列。对于进程信号,则由进程组中的每一个线程共享。例如,在上图中,pending和shared_pending分别是Private Signal Queue和Shared Signal Queue其类型都是sigpending(/include/linux/signal.h),定义如下:

struct sigpending {

struct list_head list;

sigset_t signal;

};

list用于连接信号队列,signal是一个位图,每一位表示一个对应的信号,用于指示信号队列中有哪些信号等待处理,其类型为sigset_t(include/asm-arm/signal.h),其定义如下:

#define _NSIG  64

#define _NSIG_BPW 32

#define _NSIG_WORDS (_NSIG / _NSIG_BPW)

typedef struct {

unsigned long sig[_NSIG_WORDS];

} sigset_t;

sigset_t是一个数组,总共有64位,对应64个信号位图(32个普通信号和32个实时信号)。在以后的信号发送的分析中,我们会看到,对于普通信号,只需要把sigset_t中对应的位置1就可以了,而对于实时信号,还需要把相关信息添加到list的信号队列,信号队列类型为sigqueue(include/linux/signal.h),定义如下:

/*

* Real Time signals may be queued.

*/

struct sigqueue {

struct list_head list;

spinlock_t *lock;

int flags;

siginfo_t info;

struct user_struct *user;

};

sigqueue中的list是队列链表指针,info为这个信号的相关信息,其定义如下(include/asm-generic/siginfo.h):

typedef struct siginfo {

int si_signo;

int si_errno;

int si_code;

union {

int _pad[SI_PAD_SIZE];

/* kill() */

struct {

pid_t _pid;  /* sender's pid */

__ARCH_SI_UID_T _uid; /* sender's uid */

} _kill;

/* POSIX.1b timers */

struct {

timer_t _tid;  /* timer id */

int _overrun;  /* overrun count */

char _pad[sizeof( __ARCH_SI_UID_T) - sizeof(int)];

sigval_t _sigval; /* same as below */

int _sys_private;      /* not to be passed to user */

} _timer;

/* POSIX.1b signals */

struct {

pid_t _pid;  /* sender's pid */

__ARCH_SI_UID_T _uid; /* sender's uid */

sigval_t _sigval;

} _rt;

/* SIGCHLD */

struct {

pid_t _pid;  /* which child */

__ARCH_SI_UID_T _uid; /* sender's uid */

int _status;  /* exit code */

clock_t _utime;

clock_t _stime;

} _sigchld;

/* SIGILL, SIGFPE, SIGSEGV, SIGBUS */

struct {

void __user *_addr; /* faulting insn/memory ref. */

#ifdef __ARCH_SI_TRAPNO

int _trapno; /* TRAP # which caused the signal */

#endif

} _sigfault;

/* SIGPOLL */

struct {

__ARCH_SI_BAND_T _band; /* POLL_IN, POLL_OUT, POLL_MSG */

int _fd;

} _sigpoll;

} _sifields;

} siginfo_t;

上图中的sighand保存信号的处理函数指针,其作用类似于中断向量表,类型为sighand_struct(include/linux/sched.h),定义为:

struct sighand_struct {

atomic_t  count;

struct k_sigaction action[_NSIG];

spinlock_t  siglock;

};

_NSIG定义在asm-arm/signal.h中,为64,数组action,对应64个信号处理函数的相关信息,烈性为k_sigaction,在arm平台上,

struct k_sigaction {

struct sigaction sa;

};

sigantion(include/asm-arm/signal.h)的定义如下:

struct sigaction {

__sighandler_t sa_handler;

unsigned long sa_flags;

__sigrestore_t sa_restorer;

sigset_t sa_mask;  /* mask last for extensibility */

};

sa_handler就是信号处理函数指针。另外在task_struct结构中还有一个blocked可以用来屏蔽信号。明白了上面的主要数据结构的作用之后,很容易想到信号的处理主要有以下几方面。

设置信号回调函数:内核吧函数的相关信息保存到对应的sigantion结构中。

信号的发送:通过相关系统调用吧一个指定的信号发送到目标进程,如果该信号没有被屏蔽,就把信号的相关信息添加到信号队列中,如果有必要就唤醒目标进程。

信号响应:进程被唤醒后,根据信号队列中的信息,调用信号回调函数。

我们再来看一下信号回调函数,sa_handler的类型是__sihandler_t(include/asm-generic/singal.h),其定义为:

typedef void __signalfn_t(int);

typedef __signalfn_t __user *__sighandler_t;0b1331709591d260c1c78e86d0c51c18.png

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值