一、信号的概念
1.信号的概念和机制
信号共性:
简单、不能携带大量信息、满足条件才发送。
信号的特质:
信号是软件层面上的“中断”。一旦信号产生,无论程序执行到什么位置,必须立即停止运行,处理信号,处理结束,再继续执行后续指令。
所有信号的产生及处理全部都是由【内核】完成的。
2.与信号相关的事件和状态
产生信号:
- 按键产生:如 Ctrl+c、Ctrl+z、Ctrl+\
- 系统调用产生:如 kill、raise、abort
- 软件条件产生:如 定时器alarm
- 硬件异常产生:如 非法访问内存、内存对齐出错
- 命令产生:如 kill命令
递达:递送并且到达进程
未决:产生和递达之间的状态,主要由于阻塞导致该状态
信号的处理方式:
- 执行默认动作
- 忽略(丢弃)
- 捕捉/自定义(调用户处理函数)
执行默认动作的五种形式:
Term:终止进程
Ign: 忽略信号 (默认即时对该种信号忽略操作)
Core:终止进程,生成Core文件。(查验进程死亡原因, 用于gdb调试)
Stop:停止(暂停)进程
Cont:继续运行进程
阻塞信号集(信号屏蔽字):
将某些信号加入集合,对他们设置屏蔽,当屏蔽x信号后,再收到该信号,该信号的处理将推后(解除屏蔽后)。
本质:位图,用来记录信号的屏蔽状态。一旦被屏蔽的信号,再解除屏蔽前 一直处于未决态
未决信号集:
1.信号产生,未决信号集中描述该信号的位立刻翻转为1,表信号处于未决状态。当信号被处理对应位翻转回为o。这一时刻往往非常短暂。
2.信号产生后由于某些原因(主要是阻塞)不能抵达。这类信号的集合称之为未决信号集。在屏蔽解除前,信号一直处于未决状态。本质:位图,用来记录信号的处理状态。该信号集中的信号,表示已经产生,但尚未被处理
3.信号四要素:
1. 编号 2. 名称 3. 对应事件 4. 默认处理动作
信号使用之前,应先确定其4要素,而后再用!!!
kill -l 查看当前系统中常规信号
常用信号一览表:
1) SIGHUP: 当用户退出shell时,由该shell启动的所有进程将收到这个信号,默认动作为终止进程
2) SIGINT:当用户按下了<Ctrl+C>组合键时,用户终端向正在运行中的由该终端启动的程序发出此信号。默认动作为终止进程。
3) SIGQUIT:当用户按下<ctrl+\>组合键时产生该信号,用户终端向正在运行中的由该终端启动的程序发出些信号。默认动作为终止进程。
4) SIGILL:CPU检测到某进程执行了非法指令。默认动作为终止进程并产生core文件
5) SIGTRAP:该信号由断点指令或其他 trap指令产生。默认动作为终止里程 并产生core文件。
6) SIGABRT: 调用abort函数时产生该信号。默认动作为终止进程并产生core文件。
7) SIGBUS:非法访问内存地址,包括内存对齐出错,默认动作为终止进程并产生core件。
8) SIGFPE:在发生致命的运算错误时发出。不仅包括浮点运算错误,还包括溢出及除数为0等所有的算法错误。默认动作为终止进程并产生core文件。
9) SIGKILL:无条件终止进程。本信号不能被忽略,处理和阻塞。默认动作为终止进程。它向系统管理员提供了可以杀死任何进程的方法。
10) SIGUSR1:用户定义 的信号。即程序员可以在程序中定义并使用该信号。默认动作为终止进程。
11) SIGSEGV:指示进程进行了无效内存访问。默认动作为终止进程并产生core文件。
12) SIGUSR2:另外一个用户自定义信号,程序员可以在程序中定义并使用该信号。默认动作为终止进程。
13) SIGPIPE:Broken pipe向一个没有读端的管道写数据。默认动作为终止进程。14) SIGALRM: 定时器超时,超时的时间 由系统调用alarm设置。默认动作为终止进程。
15) SIGTERM:程序结束信号,与SIGKILL不同的是,该信号可以被阻塞和终止。通常用来要示程序正常退出。执行shell命令Kill时,缺省产生这个信号。默认动作为终止进程。
16) SIGSTKFLT:Linux早期版本出现的信号,现仍保留向后兼容。默认动作为终止进程。
17) SIGCHLD:子进程状态发生变化时,父进程会收到这个信号。默认动作为忽略这个信号。
18) SIGCONT:如果进程已停止,则使其继续运行。默认动作为继续/忽略。
19) SIGSTOP:停止进程的执行。信号不能被忽略,处理和阻塞。默认动作为暂停进程。
20) SIGTSTP:停止终端交互进程的运行。按下<ctrl+z>组合键时发出这个信号。默认动作为暂停进程。
二、kill函数
int kill(pid_t pid, int sig)
给指定进程发送指定信号(不一定杀死)
参数:
pid:> 0:发送信号给指定进程
= 0:发送信号给跟调用kill函数的那个进程处于同一进程组的进程。
< -1:取绝对值,发送信号给该绝对值所对应的进程组的所有组员。
= -1:发送信号给,有权限发送的所有进程。
signum:待发送的信号
返回值:
成功: 0
失败: -1 errno
子进程发送信号kill父进程:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>
#include<signal.h>
void sys_err(const char *str)
{
perror(str);
exit(1);
}
int main(int argc,char *argv[])
{
pid_t pid = fork();
if (pid > 0)
{
printf("patent,pid = %d\n",getpid());
while(1);
}
else if(pid == 0)
{
printf("child pid = %d,ppid = %d\n",getpid(),getppid());
sleep(2);
kill(getppid(),SIGKILL);
}
return 0;
}
让父进程进入死循环,子进程睡2秒(确保父进程先执行),然后在子进程中使用kill杀死父进程
make之后执行得到以下输出:
patent,pid = 1728043
child pid = 1728044,ppid = 1728043
Killed
kill -9 10698:杀死PID为10698的这个进程
kill -9 -10698:杀死进程组为10698的所有进程
三、alarm函数:使用自然计时法
unsigned int alarm(unsigned int seconds)
参数:
seconds:定时秒数
返回值:
上次定时剩余时间。
无错误现象。
alarm(0):取消闹钟。
定时发送SIGALRM给当前进程,每个进程都有唯一的闹钟
使用alarm函数计时,打印变量i的值:
#include<stdio.h>
#include<unistd.h>
int main(void)
{
int i;
alarm(1);
for (i = 0;;i++)
{
printf("%d\n",i);
}
return 0;
}
time 命令 : 查看程序执行时间。 实际时间 = 用户时间 + 内核时间 + 等待时间。
time ./alarm
程序运行的瓶颈在于IO,优化程序,首选优化lO。
time ./alarm > out
四、setitimer函数:设置闹钟,可以替代alarm函数,精度微秒us,可以实现周期定时
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
参数:
which:ITIMER_REAL: 采用自然计时。 ——> SIGALRM
ITIMER_VIRTUAL: 采用用户空间计时 ——> SIGVTALRM
ITIMER_PROF: 采用内核+用户空间计时 ——> SIGPROF
new_value:定时秒数
old_value:传出参数,上次定时剩余时间。
返回值:
成功: 0
失败: -1 errno
类型:
struct itimerval{
struct timeval{
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
}it_interval;---> 用于设定两个定时任务之间的间隔时间
struct timeval{
time_t tv_sec;
suseconds_t tv_usec;
}it_value; ---> 第一次定时秒数
};
可以理解为有2个定时器
- 一个用于第一个闹钟什么时候触发打印
- 一个用于之后间隔多少时间再次触发闹钟。
使用setitimer定时,向屏幕打印信息:
#include<stdio.h>
#include<sys/time.h>
#include<signal.h>
void myfunc(int signo)
{
printf("hello world\n");
}
int main()
{
struct itimerval it,oldit;
signal(SIGALRM,myfunc);//注册SIGALRM信号的捕捉处理函数
it.it_value.tv_sec = 2;
it.it_value.tv_usec = 0;
it.it_interval.tv_sec = 5;
it.it_interval.tv_usec = 0;
if (setitimer(ITIMER_REAL,&it,&oldit) ==-1)
{
perror("setitimer error");
return -1;
}
while(1);
return 0;
}
输出:第一次信息打印是两秒间隔,之后都是5秒间隔打印一次
五、信号集操作函数
1. 信号集set函数
sigset_t set;
自定义信号集。
sigemptyset(sigset_t *set);
清空信号集
sigfillset(sigset_t *set);
全部置1
sigaddset(sigset_t *set, int signum);
将一个信号添加到集合中
sigdelset(sigset_t *set, int signum);
将一个信号从集合中移除
sigismember(const sigset_t *set,int signum);
判断一个信号是否在集合中。 在-->1, 不在-->0
2. sigprocmask函数
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
设置信号屏蔽字和解除屏蔽
参数:
how:SIG_BLOCK: 设置阻塞(与)
SIG_UNBLOCK:取消阻塞(取反位与)
SIG_SETMASK:用自定义set替换mask。(不推荐)
set:自定义set
oldset:旧有的 mask。
3. sigpending函数
int sigpending(sigset_t *set);
读取未决信号集
参数:
set:传出的未决信号集。
信号操作函数使用原理分析:
利用自定义集合,来设置信号阻塞,输入被设置阻塞的信号,可以看到未决信号集发生变化
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
void print_set(sigset_t *set)
{
int i;
for(i=1; i<32; i++) {
if(sigismember(set,i))
putchar('1');
else
putchar('0');
}
printf("\n");
}
int main(void)
{
//1. 定义自己的信号集
sigset_t set, oldset, pedset;
sigemptyset(&set); //清空信号集
sigaddset(&set,SIGINT);//将2号信号(Ctrl C)加入信号集内
//2. 将系统的信号集替换为自己的
int ret = sigprocmask(SIG_BLOCK,&set,&oldset); //SIG_BLOCK设为阻塞
if(ret == -1)
{
perror("sigprocmask error");
exit(1);
}
//3. 查看未决信号集的状态
while(1) {
ret = sigpending(&pedset); //这个函数会传出状态,用pedset接收
if(ret == -1)
{
perror("sigpending error");
exit(1);
}
print_set(&pedset); //调用上面的函数 查看是否在信号集中
sleep(1);
}
return 0;
}
代码解释:
- 定义自己的信号集set;
- 清空信号集,使用sigaddset()加指定的信号加入信号集内
- 使用sigprocmask()将系统的信号集替换为自己的,它的第一个参数是设置条件(阻塞,取消阻塞)
- 使用sigpending()查看未决信号集
- 封装一个sigismember函数,来打印sigpending传出的信号集