当前位置: 首页 > news >正文

【C语言】信号

【C语言】信号

  • 信号
    • 1. 信号状态
    • 2. 信号处理方式
    • 3. 信号注册相关函数
    • 4. 信号集相关函数
  • 最后

信号

1. 信号状态

信号有三种状态:产生、未决和递达

信号产生方式:

  • 按键产生,ctrl+c 产生 中断信号SIGINT,ctrl + \ 产生退出信号 SIGQUIT并生成core文件,ctrl +z产生停止信号SIGSTOP
  • 系统调用,例如kill、raise、abort函数
  • 定时器
  • 发生异常

信号未决状态:

​ 是指信号在阻塞信号集中被设置为阻塞,那么接收到这个信号后,会被保存到进程的未决信号集中,当阻塞解除才能执行对应信号处理函数。

信号递达状态:

​ 进程接收到信号并执行了对应操作。

2. 信号处理方式

  • 执行默认的操作
  • 忽略
  • 捕获,执行用户定义的处理函数

信号无法排队,所以如果同时来了很多信号,那么需要用循环进行处理

查看当前系统有哪些信号 kill -l ,其中 SIGKILL 和 SIGSTOP 不能被捕捉,阻塞,或忽略。

 1) SIGHUP	 	2) SIGINT	 3) SIGQUIT	 	4) SIGILL	 5) SIGTRAP6) SIGABRT	 	7) SIGBUS	 8) SIGFPE	 	9) SIGKILL	10) SIGUSR1
11) SIGSEGV		12) SIGUSR2	13) SIGPIPE		14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT		19) SIGSTOP	20) SIGTSTP
21) SIGTTIN		22) SIGTTOU	23) SIGURG		24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	

3. 信号注册相关函数

//signal()
//用于注册信号捕捉函数,当捕获对应信号时,执行相应函数。
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
参数signum: 信号handler: 信号处理函数//sigaction()
//用于注册信号捕捉函数,执行相应函数,并且添加处理方式,返回0成功,返回-1失败
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
struct sigaction {void     (*sa_handler)(int);void     (*sa_sigaction)(int, siginfo_t *, void *);sigset_t   sa_mask;int        sa_flags;void     (*sa_restorer)(void);
};参数signum: 捕捉的信号act: 	传入参数,新的信号处理方式sa_handler:信号处理函数,还可赋值为SIG_IGN表示忽略信号到来 或 SIG_DFL表执行默认动作sa_sigaction:如果在 sa_flags 中指定了 SA_SIGINFO ,则 sa_sigaction(而不是SA_handler)作为signum的信号处理函数。此函数接收的信号号作为其第一个参数,一个指向 siginfo_t 类型的指针作为其第二个参数,这个类型结构体包含了很多和进程相关的信息,以及一个指向ucontext_t类型(强制转换为void*)的指针作为第三个参数。(通常,处理程序函数不使用第三个参数。),通常设置为NULLsa_mask:信号处理时需要阻塞的信号掩码sa_flags:通常为0,表示使用默认标识sa_restorer:不应用于应用程序,已舍弃oldact :传出参数,旧的信号处理方式同上//kill()
//用于向某个进程发信号。
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
参数pid 进程idsig 信号//raise()
//用于自己给自己发送信号
#include <signal.h>
int raise(int sig);
返回值成功返回0失败返回非0//abort()
//用于自己给自己发送 SIGABRT 信号,等价于 abort() == kill(getpid(), SIGABRT);
#include <stdlib.h>
void abort(void);//alarm()
//设置时钟seconds秒数,内核会给当前进程发送 SIGALRM 信号,每个进程只有一个定时器
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
返回值返回0或者剩余秒数
//取消定时器使用alarm(0),返回旧闹钟余下秒数,不管进程处于什么状态,计时器一直计时//setitimer()
//也是设置计时器,比alarm函数计时得更精确,可以精确到微秒。子进程不会继承父进程的计时器
#include <sys/time.h>
int getitimer(int which, struct itimerval *curr_value);
int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);struct itimerval {struct timeval it_interval; /* Interval for periodic timer */计时周期时间struct timeval it_value;    /* Time until next expiration */第一次计时时间,可通过getitimer函数获取该值得到剩余计时时间
};struct timeval {time_t      tv_sec;         /* seconds */suseconds_t tv_usec;        /* microseconds */
};
//成功返回0,失败返回-1
参数which: 指定定时方式ITIMER_REAL		计算实时时间,给当前进程发送 SIGALRM 信号ITIMER_VIRTUAL	计算进程运行时间,发送 SIGVTALRM 信号ITIMER_PROF		计算进程运行时间和系统调用时间,这个通常和ITIMER_VIRTUAL组合进行评测进程运行时间和系统调用时间,发送 SIGPROF 信号。getitimer 中 curr_value 是传出参数,表示剩余时间。
setitimer 中 new_value 设置计时时间old_value 通常设置为NULL,如果非NULL,那么是一个传出参数,返回的是原来的计时器。

示例:

创建三个子进程,子进程结束时会给父进程发送 SIGCHLD 信号,父进程执行对应函数回收子进程。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>void sighandle(int signo)
{pid_t pid;while (1){pid = waitpid(-1, NULL, WNOHANG);if (pid > 0){printf("child [%d] is over\n", pid);}else if (pid == 0){printf("no more child need clean");break;}else if (pid == -1){printf("no child is living, pid==[%d]\n", pid);break;}else{perror("waitpid error");break;}}
}int main()
{int i = 0;pid_t pid;signal(SIGCHLD, sighandle);for (i = 0; i < 3; i++){pid = fork();if (pid < 0){perror("fork error");return -1;}else if (pid > 0){// father}else{// childprintf("here is child [%d],father is [%d]\n", getpid(), getppid());break;}}if (i==3){while (1){sleep(1);}}return 0;
}

进程实际执行时间 = 系统时间 + 用户时间 + 损耗时间

损耗的时间主要来来自文件IO操作,IO操作会有用户区到内核区的切换,切换的次数越多越耗时。

4. 信号集相关函数

信号集有阻塞信号集和未决信号集,当某些信号在阻塞信号集中被设置为阻塞,那么当这个信号来时,该进程的未决信号集会将该信号对应设置为未决信号。只有解除该信号为非阻塞,才会执行对应的信号处理函数。

#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);
//上面都是返回0成功,返回-1失败
//判断信号是否在这个信号集中,返回 1在,0不在,-1错误
int sigismember(const sigset_t *set, int signum);//sigpending()
//获取当前进程的未决信号集,set为传出参数,
int sigpending(sigset_t *set);//sigprocmask()
//用来修改进程pcb中阻塞信号集中的内容,成功返回0,失败返回-1
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
参数how:将后面的set进行某种操作SIG_BLOCK	:将set添加到阻塞信号集SIG_UNBLOCK	:将set从阻塞信号集移除SIG_SETMASK	:直接将set作为阻塞信号集set: 信号集,可以使用上面的一系列函数得到oldset: 通常设为NULL,否则返回之前的阻塞信号集

示例:

创建三个子进程,并且屏蔽SIGINT中断信号,然后注册SIGCHLD信号处理函数,注册时阻塞SIGCHLD信号

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>void sighandle(int signo)
{pid_t pid;while (1){pid = waitpid(-1, NULL, WNOHANG);if (pid > 0){printf("child [%d] is over\n", pid);}else if (pid == 0){printf("no more child need clean\n");break;}else if (pid == -1){printf("no child is living, pid==[%d]\n", pid);break;}else{perror("waitpid error");break;}}
}int main()
{int i = 0;pid_t pid;char usig = SIGINT;// signal(SIGCHLD, sighandle);sigset_t set;sigset_t oldset;int ret = sigemptyset(&set);if (ret < 0){perror("sigemptyset error");return -1;}ret = sigaddset(&set, usig);if (ret < 0){perror("sigaddset error");return -1;}ret = sigismember(&set, usig);if (ret < 0){perror("sigaddset error");return -1;}else if (ret == 0){printf("error:usig not in set\n");return -1;}else if (ret == 1){printf("usig in set\n");}//添加信号SIGINT到阻塞信号集ret = sigprocmask(SIG_BLOCK, &set, &oldset);if (ret < 0){perror("sigprocmask error");return -1;}for (i = 0; i < 3; i++){pid = fork();if (pid < 0){perror("fork error");return -1;}else if (pid > 0){// father}else{// childprintf("here is child [%d],father is [%d]\n", getpid(), getppid());break;}}if (i == 3){//注册捕捉函数struct sigaction act;memset(&act, 0x00, sizeof(act));ret = sigemptyset(&act.sa_mask);ret = sigaddset(&act.sa_mask, SIGCHLD);act.sa_handler = sighandle;act.sa_flags = 0;ret = sigaction(SIGCHLD, &act, NULL);if (ret < 0){perror("sigaction error");return -1;}printf("here is father last words\n");while (1){}}return 0;
}//输出
usig in set
here is father last words
here is child [2751],father is [2750]
child [2751] is over
no more child need clean
here is child [2752],father is [2750]
child [2752] is over
no more child need clean
here is child [2753],father is [2750]
child [2753] is over
no child is living, pid==[-1]

示例:

使用定时器,定时发送计时结束信号SIGALRM

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/time.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>void sighandle(int signo)
{printf("signo is [%d]\n", signo);
}int main()
{// alarm(3);// int getitimer(int which, struct itimerval *curr_value);// int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);struct itimerval time;struct itimerval lasttimer;memset(&time,0x00,sizeof(time));memset(&time,0x00,sizeof(lasttimer));time.it_interval.tv_sec = 3;time.it_interval.tv_usec = 0;time.it_value.tv_sec = 1;time.it_value.tv_usec = 0;setitimer(ITIMER_REAL, &time, NULL);int ret;// 注册捕捉函数struct sigaction act;memset(&act, 0x00, sizeof(act));ret = sigemptyset(&act.sa_mask);// 信号处理的时候阻塞SIGALRM信号ret = sigaddset(&act.sa_mask, SIGALRM);act.sa_handler = sighandle;act.sa_flags = 0;ret = sigaction(SIGALRM, &act, NULL);while (1){sleep(1);getitimer(ITIMER_REAL,&lasttimer);printf("[%ld:%ld]\n",lasttimer.it_value.tv_sec,lasttimer.it_value.tv_usec);}return 0;
}
//输出
signo is [14]
[2:978958]
[1:955765]
[0:950218]
signo is [14]
[2:995498]
[1:984102]
[0:970387]
signo is [14]
[2:978756]
[1:964049]
[0:915772]
signo is [14]
...

最后

推荐一个零声教育学习教程,个人觉得老师讲得不错,分享给大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,
fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,
TCP/IP,协程,DPDK等技术内容,点击立即学习:链接

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • AWS无服务器 应用程序开发—第十六章 CI/CD CodeBuild
  • Java 获取客户端 IP 地址【工具类】
  • FTP 550 No such file or directory-
  • HDFS 面试题(一)
  • Qt Quick介绍
  • js-promise、async/await
  • 缓存技术实战[一文讲透!](Redis、Ecache等常用缓存原理介绍及实战)
  • WPF 深入理解四、样式
  • 用Flask定制指令上传Excel数据到数据库
  • 常用的sql语句
  • 板凳------56.Linux/Unix 系统编程手册(下) -- SOCKET 介绍
  • 4.2、浏览器请求详解(ajax、fetch、axios使用,手写ajax)
  • 【CTS】android CTS测试
  • Nginx和Tomcat负载均衡、动静分离集群1
  • 【C++】#20,#21
  • javascript从右向左截取指定位数字符的3种方法
  • java正则表式的使用
  • MyEclipse 8.0 GA 搭建 Struts2 + Spring2 + Hibernate3 (测试)
  • Perseus-BERT——业内性能极致优化的BERT训练方案
  • PV统计优化设计
  • PyCharm搭建GO开发环境(GO语言学习第1课)
  • seaborn 安装成功 + ImportError: DLL load failed: 找不到指定的模块 问题解决
  • 简单易用的leetcode开发测试工具(npm)
  • 问:在指定的JSON数据中(最外层是数组)根据指定条件拿到匹配到的结果
  • 白色的风信子
  • Java性能优化之JVM GC(垃圾回收机制)
  • ​香农与信息论三大定律
  • # 详解 JS 中的事件循环、宏/微任务、Primise对象、定时器函数,以及其在工作中的应用和注意事项
  • #C++ 智能指针 std::unique_ptr 、std::shared_ptr 和 std::weak_ptr
  • #if和#ifdef区别
  • #NOIP 2014# day.1 生活大爆炸版 石头剪刀布
  • #NOIP 2014# day.2 T2 寻找道路
  • $Django python中使用redis, django中使用(封装了),redis开启事务(管道)
  • (2024,RWKV-5/6,RNN,矩阵值注意力状态,数据依赖线性插值,LoRA,多语言分词器)Eagle 和 Finch
  • (4)事件处理——(7)简单事件(Simple events)
  • (day18) leetcode 204.计数质数
  • (Matlab)使用竞争神经网络实现数据聚类
  • (八)Docker网络跨主机通讯vxlan和vlan
  • (二十五)admin-boot项目之集成消息队列Rabbitmq
  • (附源码)node.js知识分享网站 毕业设计 202038
  • (汇总)os模块以及shutil模块对文件的操作
  • (生成器)yield与(迭代器)generator
  • (十八)SpringBoot之发送QQ邮件
  • (四)软件性能测试
  • (一)、软硬件全开源智能手表,与手机互联,标配多表盘,功能丰富(ZSWatch-Zephyr)
  • (原創) 如何將struct塞進vector? (C/C++) (STL)
  • (轉貼) UML中文FAQ (OO) (UML)
  • .Net Core 笔试1
  • .NET Core日志内容详解,详解不同日志级别的区别和有关日志记录的实用工具和第三方库详解与示例
  • .NET Core中如何集成RabbitMQ
  • .NET Framework 服务实现监控可观测性最佳实践
  • .net 简单实现MD5
  • .Net 转战 Android 4.4 日常笔记(4)--按钮事件和国际化
  • .NET/C# 使窗口永不获得焦点
  • .net2005怎么读string形的xml,不是xml文件。