参考引用
- UNIX 环境高级编程 (第3版)
- 黑马程序员-Linux 系统编程
概念
信号是信息的载体,Linux/UNIX 环境下古老、经典的通信方式,现下依然是主要的通信手段
机制
信号的特质
每个进程收到的所有信号,都是由内核负责发送并由内核处理
Linux 内核的进程控制块 PCB 是一个结构体 task_struct,除了包含进程 id、状态、工作目录、用户 id、组 id 和文件描述符表,还包含了信号相关的信息,主要指:阻塞信号集和未决信号集
$ 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
$ man 7 signal
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
SIGABRT 6 Core Abort signal from abort(3)
SIGFPE 8 Core Floating-point exception
SIGKILL 9 Term Kill signal
SIGSEGV 11 Core Invalid memory reference
SIGPIPE 13 Term Broken pipe: write to pipe with no readers; see pipe(7)
SIGALRM 14 Term Timer signal from alarm(2)
SIGTERM 15 Term Termination signal
SIGUSR1 30,10,16 Term User-defined signal 1
SIGUSR2 31,12,17 Term User-defined signal 2
SIGCHLD 20,17,18 Ign Child stopped or terminated
SIGCONT 19,18,25 Cont Continue if stopped
SIGSTOP 17,19,23 Stop Stop process
SIGTSTP 18,20,24 Stop Stop typed at terminal
SIGTTIN 21,21,26 Stop Terminal input for background process
SIGTTOU 22,22,27 Stop Terminal output for background process
SIGBUS 10,7,10 Core Bus error (bad memory access)
SIGPOLL Term Pollable event (Sys V).
SIGPROF 27,27,29 Term Profiling timer expired
SIGSYS 12,31,12 Core Bad system call (SVr4);
SIGTRAP 5 Core Trace/breakpoint trap
SIGURG 16,23,21 Ign Urgent condition on socket (4.2BSD)
SIGVTALRM 26,26,28 Term Virtual alarm clock (4.2BSD)
SIGXCPU 24,24,30 Core CPU time limit exceeded (4.2BSD);
SIGXFSZ 25,25,31 Core File size limit exceeded (4.2BSD);
SIGIOT 6 Core IOT trap. A synonym for SIGABRT
SIGEMT 7,-,7 Term Emulator trap
SIGSTKFLT -,16,- Term Stack fault on coprocessor (unused)
SIGIO 23,29,22 Term I/O now possible (4.2BSD)
SIGCLD -,-,18 Ign A synonym for SIGCHLD
SIGPWR 29,30,19 Term Power failure (System V)
SIGINFO 29,-,- A synonym for SIGPWR
SIGLOST -,-,- Term File lock lost (unused)
SIGWINCH 28,28,20 Ign Window resize signal (4.3BSD, Sun)
SIGUNUSED -,31,- Core Synonymous with SIGSYS
只有每个信号所对应的事件发生了,该信号才会被递送 (但不一定递达),不应乱发信号
$ kill -SIGKILL pid
kill 函数
#include
#include
// 成功:0;失败:-1 (ID 非法,信号非法,普通用户杀 init 进程等权级问题),设置 errno
int kill(pid_t pid, int sig);
进程组
权限保护
普通用户基本规则
#include
#include
#include
#include
#include
#include
#include
void sys_err(const char *str) {
perror(str);
exit(1);
}
int main(int argc, char *argv[]) {
pid_t pid = fork();
// 处于父进程中,进入了一个无限循环,每 1 秒打印输出当前父进程的 PID,并调用 sleep 函数进行延时
if (pid > 0) {
while (1) {
printf("parent, pid = %d\n", getpid());
sleep(1);
}
} else if (pid == 0) {
printf("child pid = %d, ppid = %d\n", getpid(), getppid());
sleep(5);
// 发送信号的进程 id 是 0,表示给与自己所在进程组的成员发送信号
kill(0, SIGKILL);
}
return 0;
}
$ gcc kill.c -o kill
$ ./kill
parent, pid = 3882
child pid = 3883, ppid = 3882
parent, pid = 3882
parent, pid = 3882
parent, pid = 3882
parent, pid = 3882
Killed
#include
// 返回值:返回 0 或剩余的秒数,无失败
unsigned int alarm(unsigned int seconds);
#include
#include
int main(void) {
int i;
alarm(1);
for(i = 0; ; i++) {
printf("%d\n", i);
}
return 0;
}
$ gcc alarm.c -o alarm
$ ./alarm
1
2
3
...
574794
Alarm clock
1、使用 time 命令查看程序执行的时间
2、程序运行的瓶颈在于 IO,优化程序首选优化 IO
3、实际执行时间 = 系统时间 + 用户时间 + 等待时间
#include
// 返回值:成功 0,失败 -1
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
#include
#include
#include
/* struct itimerval {
struct timeval{
it_value.tv_sec;
it_value.tv_usec;
} it_interval;
struct timeval {
it_value.tv_sec;
it_value.tv_usec;
} it_value;
} it, oldit;
*/
unsigned int my_alarm(unsigned int sec) {
struct itimerval it, oldit;
int ret;
it.it_value.tv_sec = sec;
it.it_value.tv_usec = 0;
it.it_interval.tv_sec = 0;
it.it_interval.tv_usec = 0;
ret = setitimer(ITIMER_REAL, &it, &oldit);
if (ret == -1) {
perror("setitimer");
exit(1);
}
return oldit.it_value.tv_sec;
}
int main(void) {
int i;
my_alarm(1); //alarm(sec);
for(i = 0; ; i++)
printf("%d\n", i);
return 0;
}
nclude <stdio.h>
#include
#include
#include
void myfunc(int signo) {
printf("hello world\n");
}
int main(void) {
struct itimerval it, oldit;
signal(SIGALRM, myfunc);
it.it_value.tv_sec = 2;
it.it_value.tv_usec = 0;
it.it_interval.tv_sec = 5;
it.it_interval.tv_usec = 0;
if (setitimer(ITIMER_REAL, &it, &oldit) == -1) {
perror("setitimer error");
exit(1);
}
while(1);
return 0;
}
#include
// 将某个信号集清 0 成功:0;失败:-1
int sigemptyset(sigset_t *set);
// 将某个信号集置 1 成功:0;失败:-1
int sigfillset(sigset_t *set);
// 将某个信号加入信号集 成功:0;失败:-1
int sigaddset(sigset_t *set, int signum);
// 将某个信号清出信号集 成功:0;失败:-1
int sigdelset(sigset_t *set, int signum);
// 判断某个信号是否在信号集中 返回值:在集合:1、不在:0;出错:-1
int sigismember(const sigset_t *set, int signum);
#include
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
#include
// 返回值:成功:0;失败:-1,并设置相应的 errno
// set 传出参数
int sigpending(sigset_t *set);
#include
#include
#include
#include
#include
#include
void sys_err(const char *str) {
perror(str);
exit(1);
}
// 用于打印信号集中的信号状态
void print_set(sigset_t *set) {
int i;
for (i = 1; i < 32; i++) {
if (sigismember(set, i)) {
putchar('1');
} else {
putchar('0');
}
}
printf("\n");
}
int main(int argc, char* argv[]) {
// 分别用于存储要阻塞的信号集、原来的信号集和未决的信号集
sigset_t set, oldset, pedset;
int ret = 0;
sigemptyset(&set); // 将 set 信号集清空
// 向 set 信号集中添加了四个信号
sigaddset(&set, SIGINT);
sigaddset(&set, SIGQUIT);
sigaddset(&set, SIGBUS);
sigaddset(&set, SIGKILL);
// 将 set 信号集中的信号阻塞,并将原来的信号集保存到 oldset 中
// SIG_BLOCK 表示需要屏蔽的信号
ret = sigprocmask(SIG_BLOCK, &set, &oldset);
if (ret == -1) {
sys_err("sigprocmask error");
exit(1);
}
// 进入一个无限循环,在循环中使用 sigpending 函数获取未决的信号集
// 并使用 print_set 函数打印出未决信号集中的信号状态
while (1) {
ret = sigpending(&pedset);
print_set(&pedset);
sleep(1);
}
return 0;
}
$ gcc sigsfunc.c -o sigsfunc
$ ./sigsfunc
0000000000000000000000000000000
0000000000000000000000000000000
^C0100000000000000000000000000000
0100000000000000000000000000000
^\0110000000000000000000000000000
0110000000000000000000000000000
0110000000000000000000000000000
... # 对应另一个终端执行命令 kill -7 2122
0110001000000000000000000000000
0110001000000000000000000000000
... # 对应另一个终端执行命令 kill -9 2122
Killed
$ ps aux
yue 2122 0.0 0.0 4516 768 pts/0 S+ 15:25 0:00 ./sigsfunc
$ kill -7 2122
$ kill -9 2122
9 号 SIGKILL 和 19 号 SIGSTOP 信号比较特殊,只能执行默认动作,不能忽略捕捉,不能设置阻塞
#include
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
#include
#include
#include
#include
#include
#include
void sys_err(const char *str) {
perror(str);
exit(1);
}
void sig_catch(int signo) {
printf("I catch you! %d\n", signo);
return ;
}
int main(int argc, char* argv[]) {
// 注册一个捕捉 SIGINT(Ctrl + c) 信号的函数
signal(SIGINT, sig_catch);
while (1);
return 0;
}
$ gcc signal.c -o signal
$ ./signal
^CI catch you! 2
^CI catch you! 2
^CI catch you! 2
^\Quit (core dumped)
修改信号处理动作(通常在 Linux 用来注册一个信号的捕捉函数)
#include
// 返回值 成功:0;失败:-1,设置 errno
// act 传入参数,新的处理方式
// oldact 传出参数,旧的处理方式
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
struct 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);
};
#include
#include
#include
#include
#include
#include
#include
void sys_err(const char *str) {
perror(str);
exit(1);
}
void sig_catch(int signo) { // 回调函数
if (signo == SIGINT) {
printf("I catch you! %d\n", signo);
sleep(5);
} else if (signo == SIGQUIT) {
printf("------I catch you! %d\n", signo);
}
return ;
}
int main(int argc, char* argv[]) {
struct sigaction act, oldact;
act.sa_handler = sig_catch; // 设置回调函数
sigemptyset(&(act.sa_mask)); // 清空 sa_mask 屏蔽字, 只在 sig_catch 工作时有效
//sigaddset(&(act.sa_mask), SIGQUIT);
act.sa_flags = 0; // 默认值
int ret = sigaction(SIGINT, &act, &oldact); // 注册信号捕捉函数
if (ret == -1) {
sys_err("sigaction error");
}
ret = sigaction(SIGQUIT, &act, &oldact); // 注册信号捕捉函数
while (1);
return 0;
}
$ gcc sigaction.c -o sigaction
$ ./sigaction
^CI catch you! 2
^\------I catch you! 3
# 在另一个终端输入 kill xxx 后
Terminated
#include
#include
#include
#include
#include
#include
#include
void sys_err(const char *str) {
perror(str);
exit(1);
}
// 有子进程终止,发送 SIGCHLD 信号时,该函数会被内核回调
void catch_child(int signo) {
pid_t wpid;
int status;
while ((wpid = waitpid(-1, &status, 0)) != -1) { // 循环回收,防止僵尸进程出现
if (WIFEXITED(status)) {
printf("------catch child id %d, ret = %d\n", wpid, WEXITSTATUS(status));
}
}
return;
}
int main(int argc, char* argv[]) {
pid_t pid;
// 阻塞:防止注册函数还没注册完子进程就结束了
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGCHLD);
sigprocmask(SIG_BLOCK, &set, NULL);
int i;
// 循环创建多个子进程
for (i = 0; i < 15; i++) {
if ((pid = fork()) == 0) {
break;
}
}
if (15 == i) {
struct sigaction act;
act.sa_handler = catch_child; // 设置回调函数
sigemptyset(&(act.sa_mask)); // 设置捕捉函数执行期间屏蔽字
act.sa_flags = 0; // 设置默认属性, 本信号自动屏蔽
sigaction(SIGCHLD, &act, NULL); // 注册信号捕捉函数
// 解除阻塞
sigprocmask(SIG_UNBLOCK, &set, NULL);
printf("I'm parent, pid = %d\n", getpid());
while (1); // 模拟父进程后续逻辑
} else {
printf("I'm child pid = %d\n", getpid());
return i;
}
return 0;
}
$ gcc catch_child.c -o catch_child
$ ./catch_child
I'm child pid = 3170
I'm child pid = 3171
I'm child pid = 3173
I'm child pid = 3172
I'm child pid = 3175
I'm child pid = 3176
I'm child pid = 3177
I'm child pid = 3178
I'm child pid = 3179
I'm child pid = 3181
I'm child pid = 3182
I'm child pid = 3180
------catch child id 3170, ret = 0
------catch child id 3171, ret = 1
------catch child id 3172, ret = 2
------catch child id 3173, ret = 3
------catch child id 3175, ret = 5
------catch child id 3176, ret = 6
------catch child id 3177, ret = 7
------catch child id 3178, ret = 8
------catch child id 3179, ret = 9
------catch child id 3181, ret = 11
------catch child id 3182, ret = 12
------catch child id 3180, ret = 10
I'm child pid = 3183
I'm child pid = 3184
------catch child id 3183, ret = 13
------catch child id 3184, ret = 14
I'm child pid = 3174
------catch child id 3174, ret = 4
I'm parent, pid = 3169
统调用可分为两类:慢速系统调用和其他系统调用
可修改 sa_flags 参数来设置被信号中断后系统调用是否重启
sa_flags 还有很多可选参数,适用于不同情况