【Linux信号专题】二、信号是如何产生的
Linux信号专题——信号是如何产生的
专栏传送门 :《Linux从小白到大神》 系统学习Linux开发、VIM/GCC/GDB/Make工具、Linux文件IO、进程管理、进程通信、多线程等,请关注专栏免费学习。
1. 终端按键产生信号
- Ctrl+c :2号信号SIGINT,表示终止/中断。(SIG INT → signal interrupt)
- Ctrl+z:20号信号SIGTSTP,表示暂停/停止。(SIG T STP → signal terminal stop)
- Ctrl+\ :3号信号SIGQUIT,表示退出。(SIG QUIT → signal quit)
2. 硬件异常信号
当程序出现硬件异常会产生信号:
- 除0操作,浮点型错误,8号信号SIGFPE。
- 非法访问内存,11号信号SIGSEGV,段错误。
- 总线错误,7号信号SIGNUS。
3. 函数产生信号
3.1 kill函数
kill命令和kill函数都可以产生信号来杀死进程。kill命令产生信号:kill -SIGKILL pid;kill函数:给指定进程发送指定信号(不一定杀死)。
- 头文件及函数原型
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
-
函数功能
The kill() system call can be used to send any signal to any process group or process. 给指定进程发送指定信号。
-
函数参数
-
pid:进程ID
- If pid is positive, then signal sig is sent to the process with the ID specified by pid. 如果pid > 0,发送信号给指定的进程。
- If pid equals 0, then sig is sent to every process in the process group of the calling process. 如果pid = 0,发送信号给与调用kill函数的进程属于同一进程组的所有进程。
- If pid equals -1, then sig is sent to every process for which the calling process has permission to send signals, except for process 1 (init), but see below. 如果pid = -1,发送给进程有权限发送的系统中所有进程。
- If pid is less than -1, then sig is sent to every process in the process group whose ID is -pid. 如果pid < -1,对pid取模发给对应进程组。
-
sig:信号名,不推荐直接使用数字,应使用宏名,因为不同操作系统信号编号可能不同,但名称一致。
If sig is 0, then no signal is sent, but error checking is still performed; this can be used to check for the existence of a process ID or process group ID.
-
-
函数返回值
- On success (at least one signal was sent), zero is returned. 成功返回0。
- On error, -1 is returned, and errno is set appropriately. 失败返回-1 (ID非法,信号非法,普通用户杀init进程等权级问题),设置errno。
3.2 raise函数
- 包含头文件与函数原型
#include <signal.h>
int raise(int sig);
-
函数功能
he raise() function sends a signal to the calling process or thread. 给当前进程发送指定信号(自己给自己发) 。
- In a single-threaded program it is equivalent to kill(getpid(), sig);
- In a multithreaded program it is equivalent to pthread_kill(pthread_self(), sig);
- If the signal causes a handler to be called, raise() will only return after the signal handler has returned.
-
函数参数
- sig
-
函数返回值
raise() returns 0 on success, and non-zero for failure. 成功返回0,失败返回非0值。
/************************************************************
>File Name : test.c
>Author : Mindtechnist
>Company : Mindtechnist
>Create Time: 2022年05月23日 星期一 14时20分42秒
************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
int main(int argc, char* argv[])
{
printf("pid: %d\n", getpid());
sleep(1);
raise(SIGKILL); /* 相当于
kill(getpid(), SIGKILL); */
return 0;
}
3.3 abort函数
- 包含头文件及函数原型
#include <stdlib.h>
void abort(void);
-
函数功能
The abort() first unblocks the SIGABRT signal, and then raises that signal for the calling process. 给自己发送异常终止信号 6) SIGABRT 信号,终止并产生core文件。
-
函数参数
- void
-
函数返回值
The abort() function never returns.
4. 时钟信号
4.1 alarm函数
- 包含头文件及函数原型
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
-
函数功能
设置定时器(闹钟),定时给调用进程(也就是自己)发送SIGALRM,来约定进程几秒钟后结束。在指定seconds后,内核会给当前进程发送14)SIGALRM信号,进程收到该信号,默认动作终止。 每个进程都有且只有唯一一个定时器。定时与进程状态无关(自然定时法),就绪、运行、挂起(阻塞、暂停)、终止、僵尸等等无论进程处于何种状态,alarm都会计时。
alarm() arranges for a SIGALRM signal to be delivered to the calling process in seconds seconds.
-
If seconds is zero, no new alarm() is scheduled.
-
In any event any previously set alarm() is canceled.
Signal Value Action Comment SIGALRM 14 Term Timer signal from alarm(2) Term Default action is to terminate the process. /*终止进程*/
-
-
函数参数
- seconds:时间,单位秒。alarm(0)相当于取消闹钟。
-
函数返回值
alarm() returns the number of seconds remaining until any previously scheduled alarm was due to be delivered, or zero if there was no previously scheduled alarm. 返回上次定时器剩余的秒数。我们实现约定好多少秒时候发送一个信号,alarm()函数返回距离发送信号还剩余的秒数,如果没有剩余时间或没有约定发送信号返回0。可以这么理解,如果是第一次开启定时器,返回0;如果上一次设定了alarm(5),两秒之后又设置了alarm(3),那么这个alarm()返回上一次定时器剩余的时间,也就是5-2=3秒。
用法示例:
/************************************************************
>File Name : test.c
>Author : Mindtechnist
>Company : Mindtechnist
>Create Time: 2022年05月23日 星期一 14时20分42秒
************************************************************/
#include <stdio.h>
#include <unistd.h>
int main(int argc, char* argv[])
{
int ret = alarm(3);
printf("first alarm(3) return: %d\n", ret);
sleep(2);
ret = alarm(5);
printf("second alarm(5) return: %d\n", ret);
while(1)
{
printf("pid: %d\n", getpid());
sleep(1);
}
return 0;
}
编译运行得到结果
**示例2:**time命令计时与IO优化
#include <stdio.h>
#include <unistd.h>
int main(int argc, char* argv[])
{
int count = 0;
alarm(1);
while(1)
{
printf("%d\n", count++);
}
return 0;
}
编译运行,使用time命令可以查看程序执行的时间(实际执行时间 = 系统时间 + 用户时间 + 等待时间),time ./a.out
在上面的时间中:
real:总共的时间(自然时间);
user:用户使用时间;
sys:系统时间;
可以看到最大计数到了7572,并且user几乎没有分配到时间,这是因为IO操作(打印屏幕)造成的,我们可以重定向一下输出
可以看到user的时间增加了,并且最大计数达到了306087。实际上程序运行的瓶颈大部分在于IO,优化程序,首先优化IO。
4.2 setitimer函数
- 包含头文件及函数原型
#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);
-
函数功能
The system provides each process with three interval timers, each decrementing in a distinct time domain. 设置定时器(闹钟),可代替alarm函数,精度微秒us,可以实现周期定时。
-
函数参数
-
which:指定定时方式
- ITIMER_REAL:decrements in real time, and delivers SIGALRM upon expiration. 自然定时法,ITIMER_REAL对应信号14)SIGLARM,计算自然时间。(实际执行时间 = 系统时间 + 用户时间 + 等待时间)。
- ITIMER_VIRTUAL:decrements only when the process is executing, and delivers SIGVTALRM upon expiration. 虚拟空间计时(用户空间),ITIMER_VIRTUAL对应信号26)SIGVTALRM,只计算进程占用cpu的时间。(计算进程执行时间)
- ITIMER_PROF:decrements both when the process executes and when the system is executing on behalf of the process. Coupled with ITIMER_VIRTUAL, this timer is usually used to profile the time spent by the application in user and kernel space. SIGPROF is delivered upon expiration. 运行时计时(用户+内核),ITIMER_PROF对应信号27)SIGPROF,计算占用cpu及执行系统调用的时间。(进程执行时间+调度时间)
-
new_value:要设置的定时器时间
struct itimerval { struct timeval it_interval; /* next value 周期性的时间*/ struct timeval it_value; /* current value 下一次闹钟到来的时间 */ }; struct timeval { long tv_sec; /* seconds 秒*/ long tv_usec; /* microseconds 微秒*/ }; /*秒+微妙才是真正的时间,微妙是为了更精确*/
- it_interval:用来设定两次定时任务之间间隔的时间。
- it_value:定时的时长 。
- 两个参数都设置为0,即清0操作。
-
old_value:原来的定时器时间
-
-
函数返回值
- On success, zero is returned.
- On error, -1 is returned, and errno is set appropriately.
**示例1:**使用setitimer实现alarm函数定时功能
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
int main(int argc, char* argv[])
{
struct itimerval temp = {{0, 0}, {3, 0}};
setitimer(ITIMER_REAL, &temp, NULL); /*ITIMER_REAL,3秒后发送SIGALRM信号*/
while(1)
{
printf("pid: %d\n", getpid());
sleep(1);
}
return 0;
}
编译运行,3秒后闹钟
**示例2:**周期性定时器
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <signal.h>
/*信号处理回调函数*/
void m_catch(int sig)
{
/*捕捉到信号执行此函数,不杀死进程*/
printf("catch signal: %d\n", sig);
}
int main(int argc, char* argv[])
{
signal(SIGALRM, m_catch/*函数指针做函数参数*/);
/*signal信号捕捉函数,当产生SIGALRM信号的时候,去执行m_catch函数*/
struct itimerval temp = {{2, 0}, {4, 0}}; /*第一次等待4秒,以后每隔2秒发送一个信号*/
setitimer(ITIMER_REAL, &temp, NULL);
while(1)
{
printf("pid: %d\n", getpid());
sleep(1);
}
return 0;
}
编译执行,可以看到第一次隔了4秒捕获到信号,后面周期性的每隔2秒捕获一次信号,不会杀死进程,可以通过ctrl+c杀掉进程。
**示例3:**setitimer实现alarm函数
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <signal.h>
unsigned int malarm(unsigned int seconds)
{
struct itimerval temp = {{0, 0}, {0, 0}};
struct itimerval ret;
temp.it_value.tv_sec = seconds;
setitimer(ITIMER_REAL, &temp, &ret);
printf("tv_sec: %ld, tv_mirsec: %ld\n", ret.it_value.tv_sec, ret.it_value.tv_usec);
return ret.it_value.tv_sec;
}
int main(int argc, char* argv[])
{
int ret = malarm(5);
printf("malarm() return: %d\n", ret);
sleep(2);
ret = malarm(6);
printf("malarm() return: %d\n", ret);
while(1)
{
printf("pid: %d\n", getpid());
return 0;
}
return 0;
}
编译运行,时间可能会不太准确,这是正常的
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库