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;
}
编译运行,时间可能会不太准确,这是正常的