当一个进程跟另外一个进程发送信号后,接收信号的进程有以下几种行为:
1.执行默认处理 2.忽略 3.捕捉后特定处理
1.kill函数
发送信号的函数
#include <sys/types.h> #include <signal.h> int kill(pid_t pid, int sig);参数说明
pid:指定接收信号的进程 ID(进程标识符),有几种特殊情况:pid > 0:向 PID 为pid的单个进程发送信号。pid = 0:向当前进程所在的进程组中所有进程发送信号。pid = -1:向系统中所有有权限发送的进程发送信号(一般用于管理员操作)。pid < -1:向 PID 为|pid|的进程组中所有进程发送信号。
sig:指定要发送的信号(如SIGKILL、SIGTERM、SIGALRM等):- 常见信号:
SIGKILL(强制终止进程,无法被捕获或忽略)、SIGTERM(请求终止进程,可被捕获处理)、SIGSTOP(暂停进程)等。 - 若
sig = 0:不发送任何信号,仅用于检查目标进程是否存在(返回值可判断进程是否存活)。
- 常见信号:
返回值
- 成功:返回
0。 - 失败:返回
-1,并设置errno表示错误原因(如目标进程不存在、无权限发送信号等)。 - 这里必须在父进程中找到对应的子进程id
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <signal.h> #include <unistd.h> void sys_err(const char *str) { perror(str); exit(1); } int main() { pid_t pid; pid_t res; int i; for(i=0;i<5;i++){ pid = fork(); if(i==2){ //这里必须在父进程中获取子进程的id才行 res = pid; } if(pid==0) { break; } // 子进程跳出循环 } if (pid > 0) { sleep(5); // 让子进程先运行一会 // 向子进程发送SIGTERM信号 if (kill(res, SIGTERM) == -1) { sys_err("kill error"); } printf("parent process send SIGTERM to child process\n"); } else if (pid == 0) { while (1) { printf("child process is running, my = %dth\n", i+1); sleep(1); } } else { sys_err("fork error"); } return 0; }2.alarm函数
alarm函数是 Unix/Linux 系统中用于设置一次性定时器的系统调用,作用是在指定时间(秒数)后,向当前进程发送SIGALRM信号(与之前提到的定时器信号相同)。
函数原型
#include <unistd.h> unsigned int alarm(unsigned int seconds);参数与返回值
参数
seconds:指定定时器的秒数。- 若
seconds = 0:取消当前已设置的闹钟(如果存在)。 - 若
seconds > 0:设置一个新的闹钟,seconds秒后发送SIGALRM信号。
- 若
返回值:
- 如果之前没有设置过未到期的闹钟,返回
0。 - 如果之前已设置过闹钟且尚未到期,返回剩余的秒数(即距离上一个闹钟到期还剩多少秒),同时新的闹钟会覆盖旧的设置。
- 如果之前没有设置过未到期的闹钟,返回
核心功能
alarm函数会让系统在seconds秒后,自动向当前进程发送SIGALRM信号。默认情况下,SIGALRM信号的处理行为是终止进程,但可以通过signal函数注册自定义处理函数(如之前代码中的myfunc),从而实现 “定时执行某个操作” 的效果。
需要注意的是:alarm是一次性定时器,信号触发后定时器自动失效,若需周期性触发,需在信号处理函数中再次调用alarm重置。
#include <stdio.h> #include <unistd.h> #include <signal.h> #include <sys/time.h> int main(void) { int i; //自然时间 alarm(1); for(i = 0; ; i++) printf("%d\n", i); return 0; }3.setitimer函数
等待的时间和定时器的时间内,是不是都在运行主程序,只是时间到了会触发中断,运行一次处理函数,然后继续执行主程序?
豆包答:是的,你的理解完全正确
函数原型
#include <sys/time.h> int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);参数说明
which:指定定时器类型,有 3 种可选:ITIMER_REAL:实时时钟(以系统真实时间计时),到期后发送SIGALRM信号(与alarm一致)。ITIMER_VIRTUAL:虚拟时钟(仅计算进程在用户态运行的时间),到期后发送SIGVTALRM信号。ITIMER_PROF:统计时钟(计算进程在用户态 + 内核态的总运行时间),到期后发送SIGPROF信号。(最常用的是ITIMER_REAL,对应真实时间的定时)
new_value:指向struct itimerval结构体的指针,用于设置定时器的参数(首次触发时间和周期),结构体定义如下:c
运行
struct itimerval { struct timeval it_interval; // 周期性触发的时间间隔(0表示仅触发一次) struct timeval it_value; // 首次触发的延迟时间(0表示关闭定时器) }; struct timeval { time_t tv_sec; // 秒数 suseconds_t tv_usec; // 微秒数(0-999999) };old_value:指向struct itimerval结构体的指针,用于存储上一次定时器的设置(若不需要可传NULL)。
返回值
- 成功:返回
0。 - 失败:返回
-1,并设置errno表示错误原因(如参数无效)。
#include <stdio.h> #include <signal.h> #include <sys/time.h> #include <stdlib.h> int count=0; void myfunc(int signo) { printf("count = %d\n",count); count=0; } int main(void) { struct itimerval it, oldit; signal(SIGALRM, myfunc); //itvalue:第一次定时器到期的时间间隔 it.it_value.tv_sec = 1; it.it_value.tv_usec = 0; //itinterval:后续定时器到期的时间间隔 sec是秒,usec是微秒两个之和是最后的时间 it.it_interval.tv_sec = 2; it.it_interval.tv_usec = 0; if (setitimer(ITIMER_REAL, &it, &oldit) == -1) { perror("setitimer error"); return -1; } while (1){ count++; } return 0; }4.sigaction函数
函数原型
#include <signal.h> int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);参数说明
signum:- 类型为
int,表示需要处理的信号编号。在 Linux 系统中,常见的信号有SIGINT(通常由Ctrl + C触发,编号为 2 )、SIGQUIT(通常由Ctrl + \触发,编号为 3 )、SIGTERM(程序终止信号,编号为 15 )等。不同的信号有不同的默认行为,通过sigaction函数可以改变这些默认行为。
- 类型为
act:- 类型为
const struct sigaction *,是一个指向sigaction结构体的指针,用于指定信号signum的新处理方式。sigaction结构体通常包含以下几个重要成员:sa_handler:这是一个函数指针,指向自定义的信号处理函数。当接收到指定信号时,系统就会调用这个函数来处理信号。例如在前面的代码中act.sa_handler = sig_catch;就是将sig_catch函数设置为对应信号的处理函数。sa_mask:是一个信号集,用于指定在执行信号处理函数时,需要额外屏蔽的信号。也就是说,在处理某个信号期间,如果又收到了sa_mask中包含的信号,这些信号会被暂时阻塞,直到当前信号处理函数执行完毕。可以使用sigemptyset函数来清空信号集,用sigaddset函数向信号集中添加信号。sa_flags:是一些标志位,用于指定信号处理的一些选项,常见取值有:0:表示使用默认行为。⬇️SA_RESTART:若信号中断了某些系统调用(如read、write等),在信号处理完成后,会自动重启这些系统调用,而不是让系统调用返回错误。
- 类型为
oldact:- 类型为
struct sigaction *,也是一个指向sigaction结构体的指针。如果它不为NULL,sigaction函数会将信号signum原来的处理方式存储到oldact指向的结构体中。这样后续如果需要恢复信号的原来处理方式,可以再次调用sigaction函数,并传入保存的oldact信息。
- 类型为
返回值
sigaction函数调用成功时返回0。- 调用失败时返回
-1,并且会设置errno来指示错误原因,常见的错误原因有无效的信号编号等。
#include <stdio.h> #include <stdlib.h> #include <signal.h> #include <string.h> #include <unistd.h> // 包含 sleep 函数的声明 void sys_err(const char *str) { perror(str); exit(1); } //为了避免死循环,默认情况下是自动屏蔽自己的信号的 void sig_catch(int signo) // 回调函数 { if (signo == SIGINT) { sleep(10); printf("catch you!! %d\n", signo); } else if (signo == SIGQUIT) printf("catch SIGQUIT!! %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; // 设置信号处理的方式 默认情况下为0 int ret = sigaction(SIGINT, &act, &oldact); // 注册SIGINT信号捕捉函数 if (ret == -1) sys_err("sigaction error for SIGINT"); ret = sigaction(SIGQUIT, &act, &oldact); // 注册SIGQUIT信号捕捉函数 if (ret == -1) sys_err("sigaction error for SIGQUIT"); while (1); return 0; }通过sigaction函数对子进程进行回调回收
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <sys/types.h> #include <sys/wait.h> // 信号处理函数,用于回收子进程 void catch_child(int signo) { pid_t pid; int status; // 循环回收所有已终止的子进程,防止僵尸进程 while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { if (WIFEXITED(status)) { printf("子进程 %d 正常退出,退出码:%d\n", pid, WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { printf("子进程 %d 被信号 %d 终止\n", pid, WTERMSIG(status)); } } } int main() { pid_t pid; sigset_t set; // 阻塞 SIGCHLD 信号 sigemptyset(&set); sigaddset(&set, SIGCHLD); sigprocmask(SIG_BLOCK, &set, NULL); int i; for (i = 0; i < 15; i++) { if ((pid = fork()) == 0) { // 子进程直接退出 break; } } if (i == 15) { // 父进程逻辑 struct sigaction act; act.sa_handler = catch_child; // 设置信号处理函数 sigemptyset(&act.sa_mask); // 清空信号屏蔽集 act.sa_flags = 0; // 默认属性 // 注册 SIGCHLD 信号的处理函数 sigaction(SIGCHLD, &act, NULL); // 解除对 SIGCHLD 信号的阻塞 sigprocmask(SIG_UNBLOCK, &set, NULL); printf("I'm parent, pid = %d\n", getpid()); // 模拟父进程后续逻辑,持续运行 while (1) { sleep(1); } } else { // 子进程逻辑,直接打印后退出 printf("I'm child pid = %d\n", getpid()); exit(0); } return 0; }