不存在编号为0的信号
kill函数对信号编号为0有特殊的应用
一、产生信号的条件
(2) SIGINT ctrl +c 终止信号
(3) ctrl +\ 暂停信号,放入后台
(4) 非法指令
(5) abort 进程异常终止
(7) SIGBUS (虚实关系建立) 总线错误(从写的位置到物理内存,操作系统没有将磁盘的开始位置到物理内存之间建立 联系 mmap(把虚拟内存和磁盘文件的关系映射起来,如果磁盘大小大于0,就建立这种关系
(9) SIGKILL kill - 9 pid 杀死进程
(11) SIGSEGV 段错误
(13) 管道破裂
(14)闹钟
(15) 缺省终止某个进程,终止掉
(17)子进程死的时候会给父进程发送这个信号
(19)进程暂停
(23) SIGURG 紧急数据
(29) 异步 IO
2.信号处理过程
图片
三、 进程收到信号的三种处理方式
默认:如果是系统默认的话,那就会终止这个进程
忽略 :信号来了我们不处理,装作没看到 SIGKILL SIGSTOP 不能忽略
捕获并处理 :当信号来了,执行我们自己写的代码(捕获信号这个动作是需要我们完成的) SIGKILL SIGSTOP 不能捕获
四、signal
#include <signal.h>
void (*signal(int signo, void (*func)(int)))(int);
第一个参数:signo是一个整型数
第二个参数:
可以是信号处理函数
可以是后面两个宏定义。常数SIG_IGN(向内核表示忽略此信号)或是常数SIG_DFL(表示接到此信号后的动作是系统默认动作)
处理函数无返回值,且以参数1位参数
typedef void Sigfunc(int);
然后,可将signal函数原型写成: Sigfunc *signal(int, Sigfunc *);
//案例 :pause函数,它使调用进程睡眠(挂起)
#include<signal.h>
#include<stdio.h>
#include <unistd.h>
static void sig_usr(int);
int main(void)
{
if (signal(SIGUSR1, sig_usr) == SIG_ERR)
perror("can’t catch SIGUSR1");
if (signal(SIGUSR2, sig_usr) == SIG_ERR)
perror("can’t catch SIGUSR2");
for (;;)
pause();
}
static void sig_usr(int signo)
{
if (signo == SIGUSR1)
printf("received SIGUSR1\n");
else if (signo == SIGUSR2)
printf("received SIGUSR2\n");
else
printf("received signal %d\n", signo);
}
kill -USR1 2325:向进程发送SIGUSR1信号
kill -USR2 2325:向进程发送SIGUSR2信号
kill 2325:向进程发送SIGTREM(因为该进程没有设置该信号捕获函数,所以使用默认动作终止程序)
五、SIGCLD、SIGCHLD
1、产生信号条件
子进程结束,
子进程收到SIGSTOP信号
子进程收到SIGSCONT信号
2、信号作用
使用SIGCHLD完成对子进程的回收可以避免父进程阻塞等待而不能执行其他操作,只有当父进程收到SIGCHLD信号才能去调用信号捕捉函数完成对子进程的回收,未收到信号之前可以处理其他操作。
子进程状态改变(终止或停止)后产生此信号(SIGCLD/SIGHCLD),传递给父进程;
如果父进程想要捕捉此信号,可以捕获该信号并进行处理
处理:
SIGCLD信号的捕捉处理必须在子进程fork之前完成
方式一:
如果进程明确地将该信号配置为SIG_IGN,则调用进程的子进程将不产生僵死进程(注意:SIG_IGN与默认处理方式SIG_DFL不同)。子进程在终止时,将其状态丢弃
如果调用进程随后调用一个wait()函数,那么它将阻塞直到所有的子进程都终止,然后该wait返回-1,并将errno设置为ECHILD
方式二:
父进程为SIGCLD设置一个信号捕捉函数,在信号捕捉函数中调用wiat()或waittpid()获取子进程的终止状态与ID
#include <sys/wait.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
static void sig_cld(int);
int main()
{
pid_t pid;
if (signal(SIGCLD, sig_cld) == SIG_ERR)
perror("signal error");
if ((pid = fork()) < 0) {
perror("fork error");
} else if (pid == 0) {
sleep(2);
_exit(0); //子进程终结
}
/* parent */
//pause:使进程挂起,直到接收到一个信号并从信号处理函数中返回才结束挂起状态
pause();
exit(0);
}
static void sig_cld(int signo)
{
pid_t pid;
int status;
printf("SIGCLD received\n");
if (signal(SIGCLD, sig_cld) == SIG_ERR)
perror("signal error");
if ((pid = wait(&status)) < 0) //阻塞等待子进程,获得其进程号和终止状态
perror("wait error");
printf("pid = %d\n", pid);
}
//配合SIGCHLD信号,父进程完成对子进程的回收
六、 KILL
向进程或进程组发送指定的信号
#include <signal.h>
int kill(pid_t pid, int signo);
pid > 0:将信号发送给进程ID为pid的进程
pid== 0:将信号发送给调用此kill函数的进程所在进程组的其它所有进程
pid < 0:将信号发送给其进程组ID等于pid绝对值,而且发送进程有许可权向其发送信号的所有进程。
pid== -1: 将该信号发送给除init进程外的所有进程,但发送者必须拥有对目标进程发送信号的权限(很危险,可能bash会被杀死)
返回值:
成功:返回0
失败:返回-1,并设置errno。常见的几种errno如下
EINVAL:无效的信号
EPERM:该进程没有权限发送信号给任何一个目标进程
ESRCH:目标进程或进程组不存在
编号0定义为空信号。如果signo参数是0,则kill仍执行正常的错误检查,但不发送信号。这常被用来确定一个特定进程是否仍旧存在;
七、raise函数
进程向自身发送信号
#include <signal.h>
int raise(int signo);
参数:
要发送给自己的信号
返回值:
成功:返回0
失败:返回-1
raise(signo); 等价于 kill(getpid(), signo);