何为信号
信号(signal)用于通知进程发生了某种情况。进程有以下3种处理信号的方式:
- 忽略信号。有些信号表示硬件异常,例如,除以0或访问进程地址空间以外的存储单元等,因为这些异常产生的后果不确定,所以不推荐使用这种处理>方式。
- 按系统默认方式处理。
- 提供一个函数,信号发生时调用该函数,这被称为捕抓该信号。通过提供的信号处理函数,我们就能知道什么时候产生了信号,并按期望的方式处理它。
以上摘自《APUE》中文版14页
信号类型
[root@test ~]# 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
每个信号有其默认的处理方式,参考manpage,分为以下几种类型
Term Default action is to terminate the process.
Ign Default action is to ignore the signal.
Core Default action is to terminate the process and dump core (see
core(5)).Stop Default action is to stop the process.
Cont Default action is to continue the process if it is currently
stopped.
例如我们常用的kill的默认信号SIGTERM(15)
,以及强制结束信号SIGKILL(9)
,其对应的处理方式都是Term
,我们经常使用SIGABRT(6)
对应处理方式是Core
,可以用来产生core-dump文件
core文件详解
core文件是ELF-formatted文件,可以使用readelf
查看core的elf信息。它包含了程序运行时的内存,寄存器状态,堆栈指针,内存管理信息等
配置coredump
- 开启coredump
# 开启coredump
ulimit -c unlimited
# 持久化方式
echo "ulimit -c unlimited" >> /etc/profile
- 设置coredump的命名规则
# 在/etc/sysctl.conf文件中加入
# 例如下面这个命令,可以通过在%e前面增加/home/之类的路径使其保存到特定路径
kernel.core_pattern=%e.core.%s_%t
并保存退出,执行下面指令使其生效
sysctl -p
# 参数含义
%% output one '%'
%p pid
%u uid
%g gid
%s signal number
%t UNIX time of dump
%h hostname
%e executable filename
# 默认是这样的 |/usr/share/apport/apport %p %s %c %P
If the first character of the pattern is a '|', the kernel will treat
the rest of the pattern as a command to run. The core dump will be
written to the standard input of that program instead of to a file.
- 通过
cat /proc/sys/kernel/core_pattern
验证设置的pattern - 每个进程也可以通过setrlimit的
RLIMIT_CORE
配置进程级别的core大小
控制coredump的mapping
/proc/[pid]/coredump_filter
可以指定怎样的进程空间内存可以保存到core文件里,它是由下面的bitmask组成
- (bit 0) anonymous private memory(匿名私有内存段,例如:动态变了)
- (bit 1) anonymous shared memory(匿名共享内存段)
- (bit 2) file-backed private memory(file-backed 私有内存段)
- (bit 3) file-backed shared memory(file-bakced 共享内存段,例如:动态链接库)
- (bit 4) ELF header pages in file-backed private memory areas (it is effective only if the bit 2 is cleared)(ELF 文件映射,只有在bit 2 复位的时候才起作用)
- (bit 5) hugetlb private memory(大页私有内存)
- (bit 6) hugetlb shared memory(大页共享内存)
- bit 7 (since Linux 4.4) Dump private DAX pages.
- bit 8 (since Linux 4.4) Dump shared DAX pages.
默认配置是0x33,也就是说bits 0 (anonymous private mappings), 1 (anonymous shared mappings), 4 (ELF headers) and 5 (private huge pages) 都会被dump出。如果想改变bitmask,可以使用如下方法:
echo 0x00000001 > /proc/[pid]/coredump_filter
也可以配置在当前shell生效的coredump_filter
# 如果没有下面的文件,请检查内核参数CONFIG_ELF_CORE是否配置
$ echo 0x7 > /proc/self/coredump_filter
$ ./some_program
注意:
- 如果你想设置全局的
coredump_filter
,可以参考这篇文章 - MMIO的页永远不会被dump出来
产生core-dump的方法
- 发信号
SIGABRT
,会终止进程 -
gcore
,gcore会调用gdb产生coredump,不会终止进程,但是产生core的时候进程会处于stopped
状态 - 设置信号处理函数,信号处理函数
fork()
后abort()
- google-coredumper上述的方法1终止进程,方法2会暂停进程,所以谷歌就做了个coredump库,支持在运行时coredump
gcore
-
gcore
实际上就是gdb
里面的一个命令,它的作用是把进程的memory全部dump出来,和系统调用abort()
等方法的实现方式是不同的。最直观的感受是gcore会把进程全部的VIRT内存dump出来。产生的core文件有可能会很大,并且它的大小不受ulimit -c
限制。 - 使用
gcore
dump出的core文件,在gdb中使用info files
查看每段内存的大小,比使用abort()
dump出的要大的多,原因应该是gcore把没有用到的虚拟内存也dump出来了 - gcore执行过程中程序处于
stopped
状态 - 命令行中的
gore
命令其实是RedHat linux制作的一个shell script
用来调用gdb,所以如果你调用了命令行gcore [pid]
,它实际上会执行如下的gdb命令,如下,以进程40923为例:
gdb --nx --batch -ex set pagination off -ex set height 0 -ex set width 0 -ex attach 40923 -ex gcore core.40923 -ex detach -ex quit
所以,如果你没有gcore命令,也可以使用如下命令产生gcore
gdb --pid=40923 --batch -ex gcore
signal 函数
- 信号处理函数
signal
规定了在某种信号signo
情况下调用func
处理 - 正在处理的时候,其他信号是不能递交的,必须等当前信号处理完毕,其他信号才能递交
#include
typedef void Sigfunc(int);
Sigfunc *signal(int signo, Sigfunc *func)
SIGCHLD
- 子进程终止时,会给父进程发一个
SIGCHLD
信号(其实这个信号是由内核在进城终止时发给父进程的(《UNIX网络编程》P103)),如果父进程不处理的话,子进程机会变成僵尸进程 - 父进程只需要在信号处理函数里面调用
wait
即可。 - 如果不想子进程产生僵尸进程,可以设置
SIG_IGN
signal(SIGCHLD, SIG_IGN);
signal(SIGCHLD, sig_chld);
void
sig_chld(int signo)
{
pid_t pid;
int stat;
pid = wait(&stat);
printf("child %d terminated\n", pid);
return;
}
僵尸进程的危害
所谓僵尸进程,形象来说,进程已死,但其尸体还在,没人收尸啊,冤魂不散,仍然占用一个进程号,如果主进程不妥善处理,当僵尸进程数量巨大之后,就没法再次fork了,所以对于大型并发服务器来说,当建立了进程池,一定要想办法处理掉所有僵尸进程。
SIGPIPE
《UNP》中文版113页
- A与B连接, B异常终止了,这是B会终止socket连接并且给A发一个fin,
- 这时如果A再给B发一个写, B的套接字会回一个RST
- 如果A再再给B发一个写, 会引发
SIGPIPE
信号, 这个信号试图终止进程A, 如果A不情愿被终止,那么必须signal捕获该信号并处理,但是无论如何,写操作都会fail并且errno=EPIPE
-
signal
函数最简单的方法就是把SIGPIPE
置成SIG_IGN
,这样进程就会忽略SIGPIPE
信号。
signal(SIGPIPE, SIG_IGN);
SIGIO
信号驱动式I/O模型, 利用信号,让内核在描述符就绪时发送SIGIO信号通知进程
其他相关函数
sigation
- 在一些较早的系统上(《UNP》P105),signal设置的信号句柄只能起一次作用,信号被捕获一次后,信号句柄就会被还原成默认值了。我们现在用的linux系统应该没有关系的
- sigaction设置的信号句柄,可以一直有效,直到你再次改变它的设置。
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigaction( SIGPIPE, &sa, 0 );
sigation的定义:
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags; // 一般置0, 有一些特殊的标志位会用到这个, 如SA_NOCLDSTOP等
void (*sa_restorer)(void);
};
wait和waitpid函数
wait 函数可以用来处理已终止的子进程
#include
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *statloc, int options); // 成功返回进程ID, 出错返回0或-1
- 通过返回,返回已终止子进程的ID
- 通过statloc指针返回子进程终止状态
waitpid()
《UNIX网络编程》 P110
通过waitpid设置WNOHANG选项,可以告知waitpid在有尚未终止的子进程在运行时不要阻塞
参考链接
- Linux信号(一):信号类型
- What's the easiest way to detect what signals are being sent to a process?
- signal(7) — Linux manual page信号的不同用处
- core(5) — Linux manual page
- 线上内存泄漏的正确查找姿势