信号
kill -l: 显示系统中的信号
kill -num PID: 给某个进程发送信号
1. 概念
1)信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式
2)信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。
3)如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。
信号的生命周期:
2.信号的响应方式
1)忽略信号:对信号不做任何处理,但是有两个信号不能忽略:即SIGKILL及SIGSTOP。
2)捕捉信号:定义信号处理函数,当信号发生时,执行相应的处理函数。
3)执行缺省操作:Linux对每种信号都规定了默认操作
3.信号种类(常用)
SIGINT(2):中断信号,Ctrl-C 产生,用于中断进程
SIGQUIT(3):退出信号, Ctrl-\ 产生,用于退出进程并生成核心转储文件
SIGKILL(9):终止信号,用于强制终止进程。此信号不能被捕获或忽略。
SIGALRM(14):闹钟信号,当由 alarm() 函数设置的定时器超时时产生。
SIGTERM(15):终止信号,用于请求终止进程。此信号可以被捕获或忽略。termination
SIGCHLD(17):子进程状态改变信号,当子进程停止或终止时产生。
SIGCONT(18):继续执行信号,用于恢复先前停止的进程。
SIGSTOP(19):停止执行信号,用于强制停止进程。此信号不能被捕获或忽略。
SIGTSTP(20):键盘停止信号,通常由用户按下 Ctrl-Z 产生,用于请求停止进程。
4. 函数接口
4.1 信号发送和挂起
#include <signal.h>
int kill(pid_t pid, int sig);
功能:信号发送
参数:pid:指定进程
sig:要发送的信号
返回值:成功 0
失败 -1
int raise(int sig);
功能:进程向自己发送信号
参数:sig:信号
返回值:成功 0
失败 -1
相当于:kill(getpid(), sig);
int pause(void);
功能:用于将调用进程挂起,直到收到被捕获处理的信号为止。
#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
//kill(getpid(), SIGKILL); //给进程发送信号,此例子是给当前进程发送SIGKILL信号
// raise(SIGKILL); //给当前进程发送SIGKILL信号,等同于kill(getpid(), SIGKILL);
// while (1);
pause(); //将进程挂起,作用类似死循环但是不占用CPU
return 0;
}
4.2 定时器
man 2 alarm
unsigned int alarm(unsigned int seconds)
功能:在进程中设置一个定时器。当定时器指定的时间到了时,它就向进程发送SIGALARM信号。
参数:seconds:定时时间,单位为秒
返回值:如果调用此alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0。
注意:一个进程只能有一个闹钟时间。如果在调用alarm时已设置过闹钟时间,则之前的闹钟时间被新值所代替。
常用操作:取消定时器alarm(0),返回旧闹钟余下秒数。
系统默认对SIGALRM(闹钟到点后内核发送的信号)信号的响应: 如果不对SIGALRM信号进行捕捉或采取措施,默认情况下,闹钟响铃时刻会退出进程。
例子:
#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
printf("%d\n", alarm(10)); //设定闹钟5秒后发送闹钟信号
sleep(1); //睡眠1秒,此时闹钟还剩9秒
printf("%d\n", alarm(3)); //打印剩余的9秒,设新闹钟3秒后发送闹钟信号
pause(); //为了不让进程结束,等待SIGALRM信号产生,产生之后结束当前进程
return 0;
}
4.3 信号处理函数signal()
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:信号处理函数
参数:signum:要处理的信号
handler:信号处理方式
SIG_IGN:忽略信号 (忽略 ignore)
SIG_DFL:执行默认操作 (默认 default)
handler:捕捉信号 (handler为函数名,可以自定义)
void handler(int sig){} //函数名可以自定义, 参数为要处理的信号
返回值:成功:设置之前的信号处理方式
失败:-1
补充: typedef 给数据类型重命名
#include <stdio.h>
//给普通数据类型int重命名
typedef int size4;
//给指针类型int* 重命名
typedef int *int_p;
//给数组类型int [10]重命名
typedef int intArr10[10];
//给函数指针void (*)()重命名
typedef void (*fun_p)();
void fun()
{
printf("fun\n");
}
int main(int argc, char const *argv[])
{
size4 a = 10; //相当于int a=10;
int_p p = &a; //相当于int* p=&a;
intArr10 arr = {1, 2, 3}; //相当于int arr[10]={1,2,3};
fun_p fp = fun; //相当于 void (*fp)()=fun;
printf("%d\n", *p);
printf("%d\n", arr[0]);
fp();
return 0;
}
总而言之,定义变量的变量名写在哪里,用typedef给数据类型重命名的新名字就写在哪里。然后使用新名字定义变量的格式直接就可以为:新名字 变量名;
例子:
#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void handler(int sig) //参数sig代表要处理的信号
{
if (sig == SIGINT)
printf("ctrl C: %d\n", sig);
else if (sig == SIGTSTP)
printf("ctrl Z: %d\n", sig);
}
int main(int argc, char const *argv[])
{
signal(SIGINT, SIG_IGN); //对SIGINIT信号设置忽略方式处理
//signal(SIGINT,SIG_DFL); //对SIGINIT信号设置缺省方式处理,也就是默认操作
signal(SIGINT, handler); //对SIGINIT信号设置捕捉方处理,也就是自定义处理方式
signal(SIGTSTP, handler);
//while (1); //为了让进程不要结束,因为等到信号真的来才能验证,不然进程就结束了
pause(); //收到被捕获处理的信号时会结束挂起
return 0;
}
用信号的知识实现司机和售票员问题。
1)售票员捕捉SIGINT(代表开车)信号,向司机发送SIGUSR1信号,司机打印(let's gogogo)
2)售票员捕捉SIGQUIT(代表停车)信号,向司机发送SIGUSR2信号,司机打印(stop the bus)
3)司机捕捉SIGTSTP(代表到达终点站)信号,向售票员发送SIGUSR1信号,售票员打印(please get off the bus)
4)司机等待售票员下车,之后司机再下车。
分析:司机(父进程)、售票员(子进程)
售票员:
捕捉: SIGINT SIGQUIT SIGUSR1
司机:SIGTSTP
捕捉: SIGUSR1 SIGUSR2 SIGTSTP
忽略:SIGINT SIGQUIT
#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
pid_t pid;
void saler(int sig)
{
if (sig == SIGINT)
kill(getppid(), SIGUSR1);
if (sig == SIGQUIT)
kill(getppid(), SIGUSR2);
if (sig == SIGUSR1)
{
printf("pls get off the bus!\n");
exit(0);
}
}
void driver(int sig)
{
if (sig == SIGUSR1)
printf("let's gogogo!!!~~\n");
if (sig == SIGUSR2)
printf("stop the bus!\n");
if (sig == SIGTSTP)
{
kill(pid, SIGUSR1);
wait(NULL);
exit(0); //司机等待售票员下车之后再下车
}
}
int main(int argc, char const *argv[])
{
pid = fork();
if (pid < 0)
{
perror("fork err");
return -1;
}
else if (pid == 0)
{
printf("i am saler!\n");
signal(SIGINT, saler);
signal(SIGQUIT, saler);
signal(SIGUSR1, saler);
signal(SIGTSTP, SIG_IGN);
}
else
{
printf("i am driver!\n");
signal(SIGUSR1, driver);
signal(SIGUSR2, driver);
signal(SIGTSTP, driver);
signal(SIGINT, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
}
while (1)
pause(); //不能发送一个信号就结束进程,所以可以循环挂起不占用CPU
return 0;
}