linux内核中的信号机制--信号机制的管理结构
Kernel version:2.6.14
CPU architecture:ARM920T
Author:ce123(http://blog.csdn.net/ce123)
信号只是一个数字,数字为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结构中,其主要结构如下图所示。
实时信号引入了信号队列,为了处理上的方便,普通信号也使用了信号队列,仅仅从数字上无法区分实时信号和普通信号。由于在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平台上,k_sigaction(include/asm-arm/signal.h)是死噶长提哦你的一个包装,定义如下:
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;
本文详细介绍了Linux内核2.6.14版本中ARM920T架构的信号机制。信号作为进程间通信的一种方式,包括默认动作和自定义处理。内核通过signal、blocked变量和sigpending结构管理信号,处理信号的默认动作如进程终止和核心转储。信号队列用于存储待处理的信号,sigqueue结构包含信号的相关信息。sighand_struct结构保存信号处理函数,而blocked用于屏蔽信号。文章还探讨了信号的发送、接收和处理流程,并详细阐述了信号回调函数的类型和作用。
214

被折叠的 条评论
为什么被折叠?



