目录
1 信号机制
2 信号的产生
3 常用信号
4 相关命令
4.1 信号相关命令 kill / killall
4.2 信号发送 – kill / raise
4.3 定时器函数相关函数 – alarm /ualarm/ pause
4.4 信号捕捉:设置信号响应方式 – signal /sigaction,闹钟实现
4.5 子进程结束信号(使用SIGCHLD信号实现回收子进程)
4.6 练习
掌握:信号机制、常用信号、信号相关命令、信号发送、定时器、信号捕捉、信号集和信号屏蔽
1 信号机制
信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式。
linux内核通过信号通知用户进程,不同的信号类型代表不同的事件
Linux对早期的unix信号机制进行了扩展
进程对信号有不同的响应方式:
- 缺省方式
- 忽略信号
- 捕捉信号
2 信号的产生
- 按键产生
- 系统调用函数产生(比如raise, kill)
- 硬件异常
- 命令行产生 (kill)
- 软件条件(比如被0除,访问非法内存等)
3 常用信号
SIGCHLD 子进程 状态改变发给父进程的
4 相关命令
4.1 信号相关命令 kill / killall
kill [-signal] pid
默认发送SIGTERM
-sig 可指定信号
pid 指定发送对象
示例:
killall [-u user | prog]
prog 指定进程名
user 指定用户名
4.2 信号发送 – kill / raise
#include <unistd.h>
#include <signal.h>
int kill(pid_t pid, int sig);
功能:发送信号
参数:
pid: > 0:发送信号给指定进程
= 0:发送信号给跟调用kill函数的那个进程处于同一进程组的进程。
< -1: 取绝对值,发送信号给该绝对值所对应的进程组的所有组员。
= -1:发送信号给,有权限发送的所有进程。
signum:待发送的信号
int raise(int sig); //给自己发信号
给自己发信号,等价于kill(getpid(), signo);
示例
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
int main(){
// kill(24149,11); //对进程24149,发送一个段错误信号,进程24149接收到信号报错结束
raise(11); //对自己的进程,发送一个段错误信号
}
4.3 定时器函数相关函数 – alarm /ualarm/ pause
int alarm(unsigned int seconds);
功能:定时发送SIGALRM给当前进程(一次性)
返回值:成功时返回上个定时器的剩余时间,失败时返回EOF
参数:seconds 定时器的时间
一个进程中只能设定一个定时器,时间到时产生SIGALRM
示例
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
int main(){
alarm(3);
while(1);
}
//运行结果
linux@linux:~/Desktop$ ./alarm
Alarm clock
重复性闹钟函数
useconds_t ualarm(useconds_t usecs, useconds_t interval); (循环发送)
以useconds为单位,第一个参数为第一次产生时间,第二个参数为间隔产生
发送alarm信号函数
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
struct itimerval {
struct timeval it_interval; // 闹钟触发周期
struct timeval it_value; // 闹钟触发时间
};
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
功能:定时的发送alarm信号
参数:
-which:
ITIMER_REAL:以逝去时间递减。发送SIGALRM信号
ITIMER_VIRTUAL: 计算进程(用户模式)执行的时间。 发送SIGVTALRM信号
ITIMER_PROF: 进程在用户模式(即程序执行时)和核心模式(即进程调度用时)均计算时
间。 发送SIGPROF信号
-new_value: 负责设定 timout 时间
-old_value: 存放旧的timeout值,一般指定为NULL
int pause(void);
进程一直阻塞,直到被信号中断
被信号中断后返回-1,errno为EINTR
示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
alarm(3);
pause();
printf(“I have been waken up!\n”);
return 0;
}
$ ./a.out
Alarm clock
重要:alarm经常用于实现超时检测
4.4 信号捕捉:设置信号响应方式 – signal /sigaction,闹钟实现
信号捕捉过程:
- 定义新的信号的执行函数handle。
- 使用signal/sigaction 函数,把自定义的handle和指定的信号相关联。
signal函数
typedef void (*sighandler_t)(int);
//它使用了typedef关键字定义了一个名为sighandler_t的新类型,这个类型是一个指向函数的指针,该函数以一个整型参数作为信号值,返回值类型为void(即不返回任何值)。通过这个定义,可以方便地声明和使用信号处理函数,以对接收到的信号做出相应的处理。
sighandler_t signal(int signum, sighandler_t handler);
功能:捕捉信号执行自定义函数
返回值:成功时返回原先的信号处理函数,失败时返回SIG_ERR
参数:
- signo 要设置的信号类型
- handler 指定的信号处理函数: SIG_DFL代表缺省方式;SIG_IGN 代表忽略信号;
示例
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <linux/posix_types.h>
typedef void (*sighandler_t)(int);
sighandler_t oldact;
void handle(int sig){
printf("I cath the SIGINT \n");
signal(SIGINT,oldact); //改回原先的信号处理,如按ctrl+c结束进程
}
int main(){
oldact = signal(SIGINT,handle);
while(1){
sleep(1);
}
}
sigaction函数
系统建议使用sigaction函数,因为signal在不同类unix系统的行为不完全一样。
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
参数说明:
signum
:要设置或获取处理方式的信号编号。act
:指向struct sigaction
结构体的指针,用于设置新的处理方式。如果为NULL
,则不会改变原有的处理方式。oldact
:指向struct sigaction
结构体的指针,在函数返回时用于存储旧的处理方式。如果为NULL
,则不会保存旧的处理方式。
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);
}
sigaction
结构体的字段解释如下:
sa_handler
:是一个函数指针,用于指定信号处理函数,表示简单的信号处理。sa_sigaction
:是一个函数指针,用于指定信号处理函数,表示复杂的信号处理。
它有三个参数,可以获得关于信号的更详细的信息。
sa_flags参考值如下:
SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数
SA_RESTART:使被信号打断的系统调用自动重新发起。
SA_RESETHAND:信号处理之后重新设置为默认的处理方式。
SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。
sa_mask
:用于设置在处理当前信号时要阻塞的信号集。sa_flags
:用于设置一些标志位,例如SA_RESTART
表示在被信号中断的系统调用重新启动。/通过将其设置为0,表示不使用任何额外的标志位,即默认行为。sa_restorer
:保留字段,已不再使用。
函数返回值:
- 成功:返回0,并将旧的信号处理方式保存到
oldact
中(如果oldact
不为NULL
)。 - 失败:返回-1,并设置
errno
来指示错误的原因。
通过调用 sigaction
函数,可以为指定的信号设置新的处理方式,或者获取当前的处理方式。这在编写信号处理程序时非常有用,以便对信号进行正确的处理和响应。
示例:定时器的实现(使用alarm)
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <linux/posix_types.h>
typedef void (*sighandler_t)(int);
sighandler_t oldact;
void handle(int sig){
if(sig == SIGINT){
printf("I cath the SIGINT \n");
}else if (sig==SIGALRM){
printf("second timer \n");
alarm(1);
}
// signal(SIGINT,oldact);
}
int main(){
struct sigaction act;
act.sa_handler = handle;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(SIGINT,&act,NULL);
alarm(1);
sigaction(SIGALRM,&act,NULL);
// oldact = signal(SIGINT,handle);
while(1){
sleep(1);
}
}
示例:定时器的实现(settimer实现)
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <linux/posix_types.h>
#include <sys/time.h>
typedef void (*sighandler_t)(int);
sighandler_t oldact;
void handle(int sig){
if(sig == SIGINT){
printf("I cath the SIGINT \n");
}else if (sig==SIGALRM){
printf("second timer \n");
// alarm(1);
}
// signal(SIGINT,oldact);
}
int main(){
struct sigaction act;
act.sa_handler = handle;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
// sigaction(SIGINT,&act,NULL);
// alarm(1);
struct itimerval timevalue;
timevalue.it_interval.tv_sec = 1;
timevalue.it_interval.tv_usec = 0;
timevalue.it_value.tv_sec = 5;
timevalue.it_value.tv_usec = 0;
setitimer(ITIMER_REAL,&timevalue, NULL);
sigaction(SIGALRM,&act,NULL);
// oldact = signal(SIGINT,handle);
while(1){
// sleep(1); //sleep也是用alarm实现的
}
}
示例
// 头文件省略
void handler (int signo) {
if (signo == SIGINT) {
printf(“I have got SIGINT!\n”); }
if (signo == SIGQUIT) {
printf(“I have got SIGQUIT\n”); }
}
int main() {
signal(SIGINT, handler);
signal(SIGQUIT, handler);
while ( 1 ) pause();
return 0;
}
4.5 子进程结束信号(使用SIGCHLD信号实现回收子进程)
SIGCHLD的产生条件
1子进程终止时
2子进程接收到SIGSTOP信号停止时
3子进程处在停止态,接受到SIGCONT后唤醒时
SIGCHLD
可以用来处理子进程退出的僵尸
之前是使用wait来回收,现在使用信号来配合wait回收,解决父进程阻塞的问题
示例:
#include <stdio.h>
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
void handle(int sig){
wait(NULL);
printf("Get sig =%d\n",sig);
}
int main(){
pid_t pid;
struct sigaction act;
act.sa_handler = handle;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
pid = fork();
if(pid>0){
//wait(NULL); //利用信号处理,可以让父进程异步操作,接着干自己的事情而不阻塞
sigaction(SIGCHLD,&act,NULL);
while(1){
printf("this is father process\n");
sleep(1);
}
}else if(pid==0){
sleep(5);
exit(0);
}
}
执行完通过ps 命令查看,没有僵尸进程。
4.6 练习
实现捕捉SIGINT信号,在屏幕上打印 "Ctrl + c"
#include <stdlib.h>
#include <string.h>
#include <linux/posix_types.h>
typedef void (*sighandler_t)(int);
sighandler_t oldact;
void handle(int sig){
printf("Ctrl+C\n");
//signal(SIGINT,oldact); //改回原先的信号处理,按ctrl+c结束进程
}
int main(){
oldact = signal(SIGINT,handle);
while(1){
sleep(1);
}
}