目录
- 1、信号
- 1.1、什么是信号
- 1.2、进程对信号的处理
- 1.3、信号的生命周期
- 1.4、信号处理流程
- 1.5、信号的发送
- 2、kill()、raise()函数 发送信号
- 3、alarm函数 闹钟信号
- 4、pause函数 挂起信号、暂停
- 5、singal 函数 捕获信号
- 5.1、为什么返回值是上一次的处理方式
- 5.2、练习
- 6、sigaction 函数 捕获信号(推荐使用)
- 7、setitimer() 闹钟信号
- 8、sigset_t相关函数 信号集
- 9、sigprocmask 函数 设置信号屏蔽
- 10、sigpending 函数
1、信号
1.1、什么是信号
信号(signal):信号机制是进程之间相互传递消息的一种方法,信号全称为软中断信号,也称作软中断。信号用来通知进程发生了异步事件。
信号事件的发生有两个来源:
硬件来源,比如我们按下了键盘或者其它硬件;
软件来源,最常用发送信号的系统函数是kill(), raise(), alarm()和setitimer()等函数,软件来源还包括一些非法运算等操作。
注意
:信号只是用来通知某进程发生了什么事件,并不给该进程传递任何数据。
kill -l:
1.2、进程对信号的处理
进程可以通过三种方式来响应和处理一个信号:
- 1、忽略信号:忽略某个信号,对该信号不做任何处理,就像未发生过一样。
- 2、捕捉信号:是类似中断的处理程序,对于需要处理的信号,进程可以指定处理函数,由该函数来处理
- 3、执行缺省操作:对该信号的处理保留系统的默认值,这种缺省操作,对大部分的信号的缺省操作是使得进程终止
常见的信号:
处理动作的字母含义如下
A 缺省的动作是终止进程
B 缺省的动作是忽略此信号
C 缺省的动作是终止进程并进行内核映像转储(dump core)
D 缺省的动作是停止进程
E 信号不能被捕获
F 信号不能被忽略
停止进程
:只是暂停了进程的执行,进程仍然存在于内存中,随时可以恢复其执行1。
终止进程
:进程被彻底结束,其资源被释放,不再占用系统内存1
在终端中停止进程
按下 Ctrl+Z 可以停止当前前台进程。
$ sleep 100
^Z
[1]+ Stopped sleep 100
使用 jobs 命令查看停止的进程:
$ jobs
[1]+ Stopped sleep 100
使用 fg 命令恢复进程:
$ fg 1
sleep 100
1.3、信号的生命周期
从信号(Signal)发送到信号处理函数的执行是一个完整的信号生命周期
1.4、信号处理流程
1.5、信号的发送
除了内核和超级用户,并不是每个进程都可以向其他的进程发送信号。
一般的进程只能向具有相同uid和gid的进程发送信号,或向相同进程组中的其他进程发送信号。
常用的发送信号的函数有kill()
、raise ()
、alarm()
、setitimer()
、abort()
等。
2、kill()、raise()函数 发送信号
kill函数同读者熟知的kill系统命令一样,可以发送信号给进程或进程组(实际上,kill系统命令只是kill函数的一个用户接口)。
kill –l
命令查看系统支持的信号列表
命令:man 2 kill
kill
可以向指定的进程发送信号
命令:man 2 raise
raise
只能向本进程发送信号
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>int main()
{int i;for (i = 0; i < 3; i++){printf("i = %d\n",i);sleep(1);if (i == 1){if (kill(getpid(),SIGINT) < 0) //向指定进程发送信号{perror("kill signal error");}// if (raise(SIGINT) < 0) //只能向本进程发送信号// {// perror("raise error");// }}}}
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>void sigfun(int sig)
{printf("parent work start\n");sleep(1);printf("parent work over\n");
}int main()
{pid_t pid = fork();if(pid < 0){perror("fork error");return -1;}else if(pid == 0){sleep(1); //让父进程先执行printf("child process start\n");kill(getppid(),SIGINT);printf("chiild process over\n");}else if(pid > 0){printf("parent process start\n");signal(SIGINT,sigfun);wait(NULL);printf("parent process over\n");}
}
3、alarm函数 闹钟信号
alarm()也称为闹钟函数,它可以在进程中设置一个定时器。当定时器指定的时间到时,内核就向进程发送SIGALRM信号。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>int main()
{int res = alarm(5);printf("res = %d\n",res);sleep(1);res = alarm(7);printf("res = %d\n",res); //返回上一个定时器剩余时间int i = 0;while(1){printf("i = %d\n",i++);sleep(1);}
}
4、pause函数 挂起信号、暂停
pause() 进程挂起直到收到信号为止,并以缺省的方式处理此信号
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>int main()
{pid_t pid = fork();if(pid < 0){perror("fork error");return -1;}else if(pid == 0){printf("child process send signal\n");sleep(3);kill(getppid(),SIGINT); //向父进程发送信号}else if(pid > 0){printf("parent process start\n");pause();//进程挂起 (暂停),等待被信号触发,会以系统默认方式处理信号printf("parent over\n");}
}
5、singal 函数 捕获信号
出错时返回SIG_ERR
函数说明:当指定的信号signum到达时就会跳转到参数handler函数中执行。handler函数的参数接收的是信号值(比如SIGINT
是2)
附加说明
在信号发生跳转到handler处理函数执行后,系统会自动将此处理函数换回原来系统预设的处理方式,如果要改变此操作请改用sigaction()。
比如这个例子
#include <stdio.h>
#include <signal.h>
#include <unistd.h>// 信号处理函数
void handle_signal(int signum) {printf("接收到信号: %d\n", signum);
}int main() {// 注册信号处理函数if (signal(SIGINT, handle_signal) == SIG_ERR) {perror("signal 注册失败");return 1;}printf("程序运行中,按下 Ctrl+C 发送 SIGINT 信号...\n");// 无限循环,等待信号while (1) {sleep(1);}return 0;
}
第二次 按下 ctrl + c
会以系统默认方式中止,
也有例外
如果你的程序在第二次按下 Ctrl+C
时没有中止,是因为 signal() 的行为在 glibc 实现中不会恢复为默认行为。
如果你希望明确控制信号处理行为,建议使用 sigaction()
替代 signal()
。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>void sigfun(int signum)
{if (signum == SIGINT){printf("is SIGINT\n");}elseprintf("is other signal\n");}
int main()
{// signal(SIGINT,SIG_IGN); //以忽略方式处理signal(SIGINT,SIG_DFL); //以缺省方式处理#if 1void (*fun_res)(int) = signal(SIGINT,sigfun); //自定义信号处理方式printf("fun_res = %p\n",fun_res); //第一次返回值为NULLfun_res = signal(SIGINT,sigfun);printf("fun_res = %p sigfun = %p\n",fun_res,sigfun); //第二次返回上一次的处理方式#endifint i = 0;while(i < 5){printf("i = %d\n",i++);sleep(1);}
}
5.1、为什么返回值是上一次的处理方式
#include <stdio.h>
#include <signal.h>
#include <unistd.h>//为什么返回值是上一次signal处理方式
// 链式处理(Chaining)是一种常见的技术,
// 它允许程序员在自定义信号处理函数中调用之前的处理函数。
// 这种技术通常用于在自定义处理逻辑中保留系统默认行为或之前设置的行为。// 保存之前的信号处理函数
void (*old_handler)(int);// 自定义信号处理函数
void custom_handler(int signum)
{printf("Custom handler: Received signal %d\n", signum);// 调用之前的信号处理函数// if (old_handler != SIG_ERR) {// printf("Calling old handler...\n");// old_handler(signum);// }// 调用之前的信号处理函数(如果存在且不是默认行为)if (old_handler != SIG_ERR && old_handler != SIG_DFL) {printf("Calling old handler...\n");old_handler(signum);} else {printf("Old handler is SIG_DFL or invalid, skipping...\n");}
}int main()
{// 保存之前的信号处理函数old_handler = signal(SIGINT, custom_handler);if (old_handler == SIG_ERR) {perror("signal");return 1;}printf("Press Ctrl+C to trigger the custom handler...\n");// 无限循环,等待信号while (1) {sleep(1);}return 0;
}
5.2、练习
创建一个子进程,父进程每隔2秒向子进程发送一个不同的信号(SIGINT,SIGQUIT,SIGALRM), 子进程注册相应的信号处理
函数,处理接收到信号,在信号处理函数里面打印信号名称,子进程接收到3个信号后结束
// 创建一个子进程,父进程每隔2秒向子进程发送一个不同的信号(SIGINT,SIGQUIT,SIGALRM),
// 子进程注册相应的信号处理
// 函数,处理接收到信号,在信号处理函数里面打印信号名称,子进程接收到3个信号后结束#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>void sigfun(int sig)
{printf("is SIGINT\n");
}void sigfun2(int sig)
{printf("is SIGQUIT\n");
}void sigfun3(int sig)
{printf("is SIGALRM\n");
}int main()
{signal(SIGINT,sigfun);signal(SIGQUIT,sigfun2);signal(SIGALRM,sigfun3);pid_t pid = fork();if(pid < 0){perror("fork error");return -1;}else if(pid == 0 ){int num = 0;while (1){num++;pause();if(num == 3)exit(0);}}else if(pid > 0){int sigs[] = {SIGINT,SIGQUIT,SIGALRM};for (int i = 0; i < 3; i++){sleep(2);kill(pid,sigs[i]);}wait(NULL);}}
6、sigaction 函数 捕获信号(推荐使用)
头文件
#include <signal.h>
函数定义
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
参数:
signum
:要捕获的信号编号(例如 SIGINT、SIGTERM 等)。act
:指向struct sigaction
结构体的指针,用于指定新的信号处理行为。oldact
:指向struct sigaction
结构体的指针,用于保存之前的信号处理行为(可以为 NULL)。
返回值:
成功时返回 0,失败时返回 -1 并设置 errno。
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); // 已弃用,通常为 NULL
};
#include <stdio.h>
#include <signal.h>
#include <unistd.h>void sigfun(int sig)
{printf("is SIGINT(signfun)\n");
}void sigfun2(int sig)
{printf("is SIGINT(signfun2)\n");
}int main()
{struct sigaction newact = {0};newact.sa_handler = sigfun; // 设置信号处理函数// sigemptyset(&newact.sa_mask); // 清空信号屏蔽集// newact.sa_flags = 0; // 默认标志sigaction(SIGINT,&newact,NULL);struct sigaction oldact = {0};newact.sa_handler = sigfun2;sigaction(SIGINT,&newact,&oldact);printf("oldact = %p signal = %p\n",oldact.sa_handler,sigfun);int i = 0;while (i <5){printf("i = %d\n",i++);sleep(1);}printf("-----------------------\n");//恢复信号处理方式newact.sa_handler = SIG_DFL;sigaction(SIGINT,&newact,NULL);i = 0;while (i <5){printf("i = %d\n",i++);sleep(1);}}
7、setitimer() 闹钟信号
alarm()只会产生一次闹钟信号
setitimer()循环产生闹钟信号
头文件
#include <sys/time.h>
函数定义
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
参数说明
- which: 指定定时器类型,可以是
ITIMER_REAL
、ITIMER_VIRTUAL
或ITIMER_PROF
。 - new_value: 指向
struct itimerval
结构的指针,用于设置新的定时器值。 - old_value: 指向
struct itimerval
结构的指针,用于存储旧的定时器值。如果不需要旧的定时器值,可以传入
NULL。
ITIMER_REAL
: 真实时间定时器,触发 SIGALRM 信号。
ITIMER_VIRTUAL
: 进程的用户态 CPU 时间定时器,触发 SIGVTALRM 信号。
ITIMER_PROF
: 进程的用户态和内核态 CPU 时间定时器,触发 SIGPROF 信号。
struct itimerval
结构
struct itimerval {struct timeval it_interval; /* 定时器间隔 */struct timeval it_value; /* 当前定时器值 */
};struct timeval {time_t tv_sec; /* 秒 */suseconds_t tv_usec; /* 微秒 */
};
返回值
- 成功时返回 0,失败时返回 -1 并设置 errno。
#include <stdio.h>
#include <sys/time.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>void sigfun(int sig)
{printf("SIGALRM\n");
}int main()
{//用于捕捉信号signal(SIGALRM,sigfun);struct itimerval newtime = {0};newtime.it_interval.tv_sec = 1; //间隔时间newtime.it_value.tv_sec = 3; //第一次发送信号时间setitimer(ITIMER_REAL,&newtime,NULL); int i;for ( i = 0; i < 5; i++){printf("i = %d\n",i);sleep(1);}}
8、sigset_t相关函数 信号集
typedef struct {unsigned long __bits[__SIGSET_NWORDS];
} sigset_t;
头文件
#include <signal.h>
函数定义
//初始化一个空的信号集,即清除所有信号。
int sigemptyset(sigset_t *set);
//初始化一个包含所有信号的信号集
int sigfillset(sigset_t *set);
//将指定信号添加到信号集中
int sigaddset(sigset_t *set, int signum);
//从信号集中删除指定信号。
int sigdelset(sigset_t *set, int signum);
//检查指定信号是否在信号集中。
int sigismember(const sigset_t *set, int signum);
sigemptyset
函数
功能:初始化一个空的信号集,即清除所有信号。
参数:
set:指向信号集的指针。
返回值:
- 成功时返回 0。
- 失败时返回 -1,并设置 errno。
sigfillse
t函数
功能:初始化一个包含所有信号的信号集。
参数:
set:指向信号集的指针。
返回值:
- 成功时返回 0。
- 失败时返回 -1,并设置 errno。
sigaddset
函数
功能:将指定信号添加到信号集中。
参数:
set:指向信号集的指针。
signum:要添加的信号编号(如 SIGINT)。
返回值:
- 成功时返回 0。
- 失败时返回 -1,并设置 errno。
sigdelset
函数
功能:从信号集中删除指定信号。
参数:
set:指向信号集的指针。
signum:要删除的信号编号(如 SIGINT)。
返回值:
成功时返回 0。
失败时返回 -1,并设置 errno。
sigismember
函数
功能:检查指定信号是否在信号集中。
参数:
set:指向信号集的指针。
signum:要检查的信号编号(如 SIGINT)。
返回值:
如果信号在信号集中,返回 1。
如果信号不在信号集中,返回 0。
失败时返回 -1,并设置 errno。
#include <stdio.h>
#include <signal.h>//打印一个字节里每一位的值
void printb(char byte)
{for (int i = 0; i < 8; i++){printf("%d",byte & 1 << 7 - i ? 1 : 0);}printf(" "); //8位空一格}//打印某一块区域所有字节的数据
//dest区域的首地址 size区域的大小
void printmem(void *dest,size_t size)
{//从高字节向低字节输出int i = 0;for ( i = 0; i < size; i++){printb(((char *)dest)[size -1 -i]);//每八个字节换一行if ((i + 1)%8 == 0)printf("\n"); }}int main()
{sigset_t set = {0}; //声明一个信号集结构体//填充整个信号集if(sigfillset(&set) < 0){perror("sigfillset error");return -1;}//清空信号集if (sigemptyset(&set) < 0){perror("sigemptyset error");return -1;}//添加信号if(sigaddset(&set,SIGINT) < 0){perror("sigaddset error");return -1;}if(sigaddset(&set,SIGQUIT) < 0){perror("sigaddset error");return -1;}// printmem(&set,sizeof(set));//删除信号if(sigdelset(&set,SIGQUIT) < 0){perror("sigdelset error");return -1;}printmem(&set,sizeof(set));//查询信号是否存在int res = sigismember(&set,SIGQUIT);if (res == 1){printf("set have SIGINT\n");}else if (res == 0)printf("set not have SIGINT\n");else if (res < 0){perror("sigismember error");return -1;}}
9、sigprocmask 函数 设置信号屏蔽
信号屏蔽 不等于 信号忽略
信号忽略:忽略并丢掉
信号屏蔽:暂时不处理,放入队列中,后面再从队列中取出处理
头文件
#include <signal.h>
函数定义
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
参数说明
-
how
:指定如何修改信号屏蔽字,可以是以下值之一SIG_BLOCK
:将 set 中的信号添加到当前信号屏蔽字中(阻塞这些信号)。SIG_UNBLOCK
:从当前信号屏蔽字中移除 set 中的信号(解除阻塞这些信号)。SIG_SETMASK
:将当前信号屏蔽字替换为 set 中的信号集。
-
set
:指向sigset_t
类型的信号集,表示要修改的信号集。如果为 NULL,则忽略此参数。 -
oldset
:指向sigset_t
类型的信号集,用于保存修改前的信号屏蔽字。如果为 NULL,则不保存。
注意
SIG_BLOCK
:用于临时阻塞某些信号,不影响其他已阻塞的信号。
SIG_SETMASK
:用于完全替换信号屏蔽字,适合需要精确控制信号屏蔽字的场景。
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>void sigfun(int sig)
{printf("is SIGINT\n");
}
int main()
{//注册对SIGINT信号的捕捉signal(SIGINT,sigfun);//初始化信号集sigset_t set = {0};sigemptyset(&set); //先清空sigaddset(&set,SIGINT); //再添加//设置信号屏蔽//这里SIG_if(sigprocmask(SIG_SETMASK,&set,NULL) < 0){perror("sigprocmask error");return -1;}int i;for(i = 0; i < 5; i++){printf("i = %d\n",i);sleep(1);}//解除屏蔽,信号会被触发if(sigprocmask(SIG_UNBLOCK,&set,NULL) < 0){perror("sigprocmask error");return -1;}}
10、sigpending 函数
头文件
#include <signal.h>
函数定义
int sigpending(sigset_t *set);
功能
用于检查当前进程中被阻塞(屏蔽)但尚未处理的信号的函数。
参数
set
:一个指向 sigset_t 类型的指针,用于存储当前被阻塞且未处理的信号集。
返回值
- 成功:返回 0。
- 失败:返回 -1,并设置 errno 以指示错误。
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>void sigfun(int sig)
{printf("is SIGINT\n");
}int main()
{//注册对SIGINT信号的捕捉signal(SIGINT,sigfun);//初始化信号集sigset_t set = {0};sigemptyset(&set); //先清空sigaddset(&set,SIGINT); //再添加//设置信号屏蔽if(sigprocmask(SIG_SETMASK,&set,NULL) < 0){perror("sigprocmask error");return -1;}int i;for(i = 0; i < 5; i++){printf("i = %d\n",i);sleep(1);}//获取屏蔽信号的信号集sigset_t PendSet = {0};if (sigpending(&PendSet) < 0){perror("sigpending error");return -1;}int res = sigismember(&PendSet,SIGINT);if(res == 1)printf("have SIGINT\n");else if(res == 0)printf("not have SIGINT\n");else if(res < 0){perror("sigismember error");return -1;}}