淘宝 网站开发 退货,项目计划书怎么写,网站开发哪些公司,WordPress自己安装了插件文章目录 一、信号的概念1.信号概念2.前台与后台进程3.信号的处理4.硬件层面5.信号与我们的代码是异步的 二、信号的产生1.产生的方式2.键盘组合键3.kill命令4.系统调用4.1 kill系统调用4.2 raise4.3 abort 5.异常软件条件5.1 异常产生信号5.2 alarm#xff08;软件条件产生信… 文章目录 一、信号的概念1.信号概念2.前台与后台进程3.信号的处理4.硬件层面5.信号与我们的代码是异步的 二、信号的产生1.产生的方式2.键盘组合键3.kill命令4.系统调用4.1 kill系统调用4.2 raise4.3 abort 5.异常软件条件5.1 异常产生信号5.2 alarm软件条件产生信号 6.core dump 一、信号的概念
1.信号概念
我们常见的信号有信号弹、下课上课铃声、求偶、红绿灯、快递发短信取件码等等… a.那么我们是怎么认识这些信号的 当然是有人教我们最后我们记住了 这个认识首先我们要识别信号其次还要知道信号的处理方法。最后记住他们 b.即便是我们现在没有信号产生我也知道信号产生了之后我该干什么 c.信号产生了我们可能并不立即处理这个信号在合适的时候因为我们可能正在做更重要的事情。 — 所以信号产生后一直到信号处理时中间一定有一个时间窗口。在这个时间窗口内我们必须记住信号到来 上面这些我们指代的就是进程
所以 进程必须识别 能够处理信号 — 即便信号没有产生也要具有处理信号的能力 — 信号的处理能力属于进程内置功能的一部分进程即便是没有收到信号也能知道哪些信号该怎么处理当进程真的收到了一个具体的信号的时候进程可能并不会立即处理这个信号在合适的时候会进行处理一个进程必须当信号产生到信号开始被处理就一定会有时间窗口所以进程具有临时保存哪些信号已经发生了的能力 如下图所示我们前面所作的相当于是一个信号的预备部分 2.前台与后台进程
我们还记得我们使用CTRL C可以杀掉前台进程。像如下所示的就是前台进程该进程运行时shell不会接收其他命令了 但是如果我们这样做它就是一个后台进程了即在程序后面加上一个即可而且我们还发现我们直接CTRL C已经杀不掉它了。 在我们的Linux中一次登录中一个终端一般会配上一个bash。每一个登录只允许一个进程是前台进程可以允许多个进程是后台进程。 如果我们要杀掉后台进程我们只能使用kill -9命令了 一般来说前台进程和后台进程的区别就是谁能获取键盘输入 键盘输入首先是被前台进程收到的 那么既然一开始bash是前台进程那么为什么使用CTRLC时候bash不退出呢 这当然是因为bash在里面对这个信号做了特殊处理 CTRL C 本质是被进程解释成为收到了信号2号信号
我们知道我们的系统一共有62个信号没有0号32号33号 我们将前31个信号称之为普通信号。后面的34~64我们称之为实时信号
一旦信号产生不立即处理就是普通信号立即处理是实时信号
这些信号本质就是一些数字在linux中它们是以宏的方式定义的就是这些数字。
3.信号的处理 信号的处理方式 默认动作忽略自定义动作 我们现在可以验证一下进程收到2号信号的默认动作就是终止自己 我们先来看一下这个函数 #include signal.h
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);这个函数的作用是设置对于signum信号的处理方法处理方法为handler
这个signal函数是一个系统调用
它可以修改进程对于特定信号的处理动作
我们用如下代码来验证
#include iostream
#include unistd.h
#include signal.h
using namespace std;void myhandler(int signo)
{cout process get a signal: signo endl;
}
int main()
{signal(SIGINT, myhandler);while(true){cout I am a crazy process endl;sleep(1);}return 0;
}运行结果为 可见我们已经证明了2号信号的默认动作
我们也可以设置让2号信号可以退出 运行结果为 对于这个signal函数只需要设置一次即可往后都有效 只有收到了对应的信号才会调用这个方法 注意不是所有的信号都可以自定义的。有些信号不能自定义 4.硬件层面 键盘数据是如何输入给内核的CTRL C又是如何变成信号的 键盘被摁下肯定是OS先知道的
那么OS怎么知道键盘上有数据了
如下图所示是冯诺依曼体系结构 我们知道Linux下一切皆文件。键盘也是它也有自己对应的struct file。以及缓冲区 往键盘输入数据本质就是把输入的数据拷贝到缓冲区上。所以操作系统就知道了
所以我们就可以用readwrite通过文件的方式把数据读到进程当中 那么操作系统要把键盘上的数据拷贝到缓冲区中。在这个过程中操作系统怎么知道键盘上有数据了
其实CPU上是有很多针脚的。我们的CPU是直接插到主板上的。而键盘是可以间接的和CPU直接物理上连接到的。虽然CPU不从键盘读数据。但是键盘可以给CPU发送一个硬件中断。
也就是说一旦外设有数据就可以给CPU发送一个中断从而让操作系统去完成文件拷贝。键盘、网卡等都可以给CPU发送中断。
可是外设一多CPU如何知道是谁给发送的中断呢
这就会有一个中断号的概念。他们会通过这些针脚直接将中断号发送给CPU
一旦硬件CPU知道键盘上有数据了。 CPU的寄存器凭什么能保存数据呢 其实这个本质就是充放电的过程。如果是高电平代表充电了就有1了。 在软件层面上操作系统一启动就会形成一张中断向量表。里面放的是方法的地址。这些方法是直接访问外设的方法—主要是磁盘显示器键盘 然后最后这个读取键盘的方法才是将键盘的数据放到缓冲区的方法 所以其实整个流程就是键盘一旦有数据会通过中断将中断号给CPUCPU会利用这个中断号让操作系统直接去通过中断向量表找到对应的读取键盘的方法然后通过这个方法就会让数据从键盘拷贝到这个文件缓冲区上。 所以键盘这个外设是通过中断来工作的。这个就是硬件中断
而我们前面所说的信号也是通过一堆数字来进行控制。这两者其实比较相似但是没有关系。一个是软硬件结合的一个是纯软件行为。
我们所用的信号就是用软件的方式对进程模拟的硬件中断 当我们键盘读取的是CTRL C这样的组合键的时候操作系统其实还会对键盘上的数据进行判断。判断是数据还是控制如果是控制比如CTRL C会把这个转化为2号信号发送给进程。而不是放到缓冲区中。所以进程就收到了2号信号 像我们之前所谓的输入数据后往显示器上回显的过程其实是这样的先将数据放到键盘的缓冲区然后将键盘的缓冲区的数据放到显示器的缓冲区最后就能输出了 如果这里不给显示器的缓冲区就是不回显 当然在这个过程中也会有其他的进程给显示器的缓冲区上放数据 所以即便在显示器上的是乱的但是我们还是能成功的执行指令 5.信号与我们的代码是异步的
信号的产生的和我们自己的代码的运行是异步的
同步就是发生一件事后等这件事发生完了才继续做我们的事情
异步就是这件事发生后我们不管这个事情继续做我们的事情 信号是进程之间事件异步通知的一种方式属于软中断 二、信号的产生
1.产生的方式 键盘组合键kill命令系统调用异常软件条件 以上是信号产生的方式但是无论信号如何产生最终一定是谁发送给进程的 当然是OS 那么是为什么呢 操作系统是进程的管理者 2.键盘组合键
比如CTRLC是2号信号
我们可以试一下捕捉三号信号
使用CTRL \即可捕捉3号信号
#include iostream
#include unistd.h
#include signal.h
using namespace std;void myhandler(int signo)
{cout process get a signal: signo endl;//exit(1);
}
int main()
{//signal(SIGINT, myhandler);signal(3, myhandler);while(true){cout I am a crazy process endl;sleep(1);}return 0;
} CTRL Z是19号信号
#include iostream
#include unistd.h
#include signal.h
using namespace std;void myhandler(int signo)
{cout process get a signal: signo endl;//exit(1);
}
int main()
{//signal(SIGINT, myhandler);//signal(3, myhandler);signal(19, myhandler);while(true){cout I am a crazy process endl;sleep(1);}return 0;
} 如下所示我们似乎会发现我们上面似乎并没有将19号信号用自定义的方法进行处理
其实这是因为不是所有的信号都是可以被signal捕捉的比如199号信号
我们可以用下面的代码进行测试。这里就不做演示了
#include iostream
#include unistd.h
#include signal.h
using namespace std;void myhandler(int signo)
{cout process get a signal: signo endl;//exit(1);
}
int main()
{//signal(SIGINT, myhandler);//signal(3, myhandler);//signal(19, myhandler);for(int i 1; i 31; i){signal(i, myhandler);}while(true){cout I am a crazy process endl;sleep(1);}return 0;
} 19号是用来暂停进程的9号信号是用来杀掉进程的。 这两个都是跟执行相关的。这是为了预防进程出现意外所设计的。不能被捕捉的 3.kill命令
kill -信号 进程pid4.系统调用
4.1 kill系统调用 #include sys/types.h
#include signal.h
int kill(pid_t pid, int sig);它的两个参数分别是pid和信号的编号。与命令行中的kill是很相似的
如果成功返回0失败返回-1
我们可以简单的利用这个系统调用接口实现一个kill命令
#include iostream
#include unistd.h
#include signal.h
#include string
#include sys/types.h
using namespace std;void Usage(string proc)
{cout Usage:\n\t proc signum pid\n\n;
}int main(int argc, char* argv[])
{if(argc ! 3){Usage(argv[0]);exit(1);}int signum stoi(argv[1]);pid_t pid stoi(argv[2]);int n kill(pid, signum);if(n -1){perror(kill);exit(2);}return 0;
}测试结果如下所示 4.2 raise #include signal.h
int raise(int sig);它的作用发送一个信号给调用本方法者
我们可以用如下代码进行测试
#include iostream
#include unistd.h
#include signal.h
#include string
#include sys/types.h
using namespace std;
void myhandler(int signo)
{cout process get a signal: signo endl;exit(1);
}
int main()
{signal(2, myhandler);int cnt 5;while(true){cout I am a process, pid: getpid() endl;sleep(1);cnt--;if(cnt 0) raise(2);}return 0;
}运行结果为 这个raise相当于
kill(getpid(), 2);4.3 abort 它的作用是引起一个正常的进程直接终止
它相当于给自己发送一个6号信号
我们先用下面代码进行测试
int main()
{int cnt 5;while(true){cout I am a process, pid: getpid() endl;sleep(1);cnt--;if(cnt 0) abort();}return 0;
} 如果我们继续将代码改为下面的让6号信号可以被捕捉
void myhandler(int signo)
{cout process get a signal: signo endl;//exit(1);
}
int main()
{signal(6, myhandler);int cnt 5;while(true){cout I am a process, pid: getpid() endl;sleep(1);cnt--;if(cnt 0) abort();}return 0;
}
那么结果为 我们会注意到虽然我们捕捉了以后并没有将他给终止但是abort依然将他给终止了。
我们可以再来观察一下
在下面的代码中我们不让他自己abort了我们现在在命令行上发送6号信号
void myhandler(int signo)
{cout process get a signal: signo endl;//exit(1);
}
int main()
{signal(6, myhandler);int cnt 5;while(true){cout I am a process, pid: getpid() endl;sleep(1);cnt--;//if(cnt 0) abort();}return 0;
}我们发现进程并没有被终止
所以其实是abort里面封装了一层必须让进程终止
5.异常软件条件
5.1 异常产生信号
我们先使用如下代码
#include iostream
#include signal.h
#include unistd.h
using namespace std;int main()
{cout div before endl;sleep(1);int a 10;a / 0; cout div after endl;sleep(1);return 0;
}运行结果为 像这种情况就是收到了信号了
收到的是8号信号
我们可以用七号手册
man 7 signal往下翻就可以找到这个信号详情了可以看到确实是八号信号 如果我们用下面的代码进行测试
#include iostream
#include signal.h
#include unistd.h
using namespace std;
void handler(int signo)
{cout get a signal, number: signo endl;
}int main()
{signal(SIGFPE, handler);cout div before endl;sleep(1);int a 10;a / 0; cout div after endl;sleep(1);return 0;
}那么运行结果为。打印很多的收到八号信号 而且这个过程中进程是不会退出的 这里的都还是比较好解释的
不过我们可能比较好奇的是为什么在这里他会一直发送这个八号信号呢
即信号为什么会一直被触发
我们先看下面的代码
#include iostream
#include signal.h
#include unistd.h
using namespace std;
int main()
{//signal(SIGFPE, handler);cout point error before endl;sleep(1);// int a 10;// a / 0; int* p nullptr;*p 10;cout point error after endl;sleep(1);return 0;
}运行结果为 这里也是代码没有跑完直接崩溃了这个本质也是收到了信号。收到的是11号信号
我们在捕捉一下11号信号
#include iostream
#include signal.h
#include unistd.h
using namespace std;
void handler(int signo)
{cout get a signal, number: signo endl;
}int main()
{signal(SIGSEGV, handler);cout point error before endl;sleep(1);// int a 10;// a / 0; int* p nullptr;*p 10;cout point error after endl;sleep(1);return 0;
}运行结果为 和前面十分类似会不断的捕捉11号信号而且还不会退出
所以进程收到异常信号不一定会退出。但是一定会执行异常处理方法
虽然上面捕捉后一直在打印没有退出不过其实我们大概率还是要将他们给退出的。因为一直打印也没什么用 运行结果为 为什么 /0 野指针会给进程发送信号?导致进程崩溃 这里是因为除零野指针会给系统带来问题操作系统识别到这些问题然后OS给进程发送的信号。信号的默认处理动作就是终止自己所以就崩了 操作系统为什么能检测到除零野指针呢 首先如下所示如果是除零错误 在CPU上有一个eip/pc寄存器可以用来记录当前执行的是哪一行代码 还有一种寄存器是状态寄存器。它里面的每一位都有特定的含义 其中有一个就是溢出标志位 一旦我们的代码除零了这个溢出标志位就变为1了 像我们在CPU的这些数据都是这个进程的上下文 也就是说这里虽然我们修改的是CPU内部的状态寄存器但是这里只影响我们自己。不会影响其他进程进程切换的时候其他进程会将自己的上下文数据放上去 在这里操作系统一定会知道这里出错了。因为CPU是硬件OS是硬件的管理者。 所以操作系统才会向进程发送信号 如果是野指针异常 如下图所示在CPU里面有一个内存管理单元因为直接查页表太慢了所以有一个MMU硬件来进行查表。 一旦异常也就是地址转化失败了。虚拟到物理转化失败了。 在CPU内还有一个寄存器一旦转化失败了。它会把转化失败的虚拟地址放在这里 也就是说一旦转化失败CPU也能识别 而且因为他们是用的不同的寄存器所以CPU也能区分出来是哪种报错操作系统也就知道了 这里我们进程出异常以后我们本应该退出。但是如果我们非要不退出。 意味着这个进程一直被调度运行。 硬件一直存在这个问题。我们也没有修正。随着我们的调度。操作系统一直在检测到这个异常然后我们也一直在捕捉这个信号。所以就一直打印。 所以捕捉异常其实就不是让我们不让进程退出的而是让我们知道我们这个进程是怎么死掉的 那么异常只能由硬件产生吗
当然不是。
比如我们之前的管道如果一开始读写端都打开但是我们突然关闭了读端。那么写端进程就会被杀掉。会收到一个SIGPIPE13号信号。这就是一种软件异常。
也有的异常操作系统只是会返回值出错的形式进行处理 运行结果为 5.2 alarm软件条件产生信号 #include unistd.h
unsigned int alarm(unsigned int seconds);alarm 系统调用用于设置一个定时器当定时器计时器达到指定的时间时内核会发送一个 SIGALRM 信号(14号信号)给调用进程。这可以用于实现定时器功能例如在一定时间间隔内执行某个特定的操作或执行定时任务
seconds 参数表示定时器的秒数。如果 seconds 参数为非零值表示设置定时器在指定秒数后会发送 SIGALRM 信号给进程。如果 seconds 参数为零则表示取消之前设置的定时器。
返回值是剩余的未完成的定时器秒数。如果之前有一个定时器已经设置调用 alarm 会取消之前的定时器并返回剩余的秒数。如果没有之前的定时器或者之前的定时器已经到期返回值为 0。
比如如下的代码中
#include iostream
#include signal.h
#include unistd.h
using namespace std;
int main()
{int n alarm(5);while(1){cout proc is running... endl;sleep(1);}return 0;
}运行结果为 我们可以捕捉一下这个信号
#include iostream
#include signal.h
#include unistd.h
using namespace std;
void handler(int signo)
{cout get a signal, number: signo endl;//exit(1);
}int main()
{ signal(SIGALRM, handler);int n alarm(5);while(1){cout proc is running... endl;sleep(1);}return 0;
}这里并不是异常导致的所以不会循环式的疯狂捕捉。
我们也可以下面这样做就可以每隔三秒打印一次了
#include iostream
#include signal.h
#include unistd.h
using namespace std;
void handler(int signo)
{cout get a signal, number: signo endl;int n alarm(3);//exit(1);
}
int main()
{ signal(SIGALRM, handler);int n alarm(3);while(1){cout proc is running... endl;sleep(1);}return 0;
}有了这个闹钟我们就可以在执行主要任务的同时去定时完成其他任务了。像下面这样即可
#include iostream
#include signal.h
#include unistd.h
using namespace std;void work()
{cout print log ... endl;
}void handler(int signo)
{work();//cout get a signal, number: signo endl;int n alarm(3);//exit(1);
}
int main()
{ signal(SIGALRM, handler);int n alarm(3);while(1){cout proc is running... endl;sleep(1);}return 0;
}关于它的返回值我们可以这样做
#include iostream
#include signal.h
#include unistd.h
using namespace std;
void handler(int signo)
{cout get a signal, number: signo endl;int n alarm(5);cout 剩余时间: n endl; }int main()
{ signal(SIGALRM, handler);int n alarm(50);while(1){cout proc is running..., pid: getpid() endl;sleep(1);}return 0;
}运行结果如下 理解alarm 我们知道每个进程都可以使用alarm设置闹钟所以操作系统中一定有大量的闹钟。所以操作系统要管理闹钟所以闹钟就会用struct结构体描述然后用链表等数据结构管理起来。这样所谓的闹钟管理就变成了对链表等的增删查改。 这个alarm结构体里面一定有pid或者pcb的指针。 操作系统底层中alarm的底层所用的时间用的是时间戳。这样最简单。 只要系统的当前时间大于等于里面设置的时间就会发信号。 不过我们遍历链表的时候是比较浪费时间的。所以用一个小堆是最简单的。 6.core dump
我们可以看一下信号的详细信息手册 我们可以注意到常见的信号中大部分是终止信号的。还有一些是暂停(Stop)继续Cont忽略(Ign)等。
我们可以注意到终止信号中有一些是Core,有一些是Term
在我们当时提到进程退出的时候有一个这个字段core dump标志 这个是用来表示是Core方式被杀还是Term方式
接下来我们使用这段代码来看看这个标志位
#include iostream
#include signal.h
#include unistd.h
#include sys/types.h
#include sys/wait.husing namespace std;
int main()
{pid_t id fork();if (id 0){int cnt 500;while (cnt){cout I am a chid process, pid: getpid() cnt: cnt endl;sleep(1);cnt--;}exit(0);}int status 0;pid_t rid waitpid(id, status, 0);if(rid id){cout child quit info, rid: rid exit code: ((status8)0xFF) exit signal: (status0x7F) core dump: ((status7)1) endl;}
}如果被2号信号所杀如下所示这个标志为为0。而2号信号对应的Term方式 如果我们用8号信号所杀我们发现这个core dump 标志位还是0。但是8号信号对应的是Core。 我们发现这两个都没有什么变化。
其实如果我们当前用的是虚拟机的话。如果进程崩掉的话。我们会发现当前目录会形成一个临时文件这个临时文件是XXX.core文件。而我们当前的云服务器上会发现没有这是因为默认云服务器上的core功能是被关闭的 那么这是为什么呢是什么呢怎么办呢? 我们使用下面这个命令,它可以查看系统当中一些标准的配置
ulimit -a 其中这个core file size选项我们可以使用下面指令去查
ulimit -c我们可以看到结果为0也就是说这个core默认是被关掉的
我们可以使用下面指令去设置它
ulimit -c 10240(要设置的大小)如下所示也就是说最大是这么大 上面就是开启core
如果要关闭的话我们可以直接设置为0 当我们设置好了以后我们再去使用2号信号和8号信号结果就不一样了 现在此时八号信号的core dump就是1了
更关键的是我们发现现在确实有这个临时文件了 这个很明显就是刚刚的pid 打开系统的core dump功能 一旦进程出现异常OS会将进程在内存中的运行信息给我dump(转储)到进程的当前目录磁盘形成core.pid文件。 这就是核心转储core dump 而这个功能在云服务器上默认是被关闭的 那么为什么要进行核心转储呢 上面的错误一定是运行时错误。 此时我们要知道什么原因错误了。在哪一行错误了。 所以我们要用core dump来进行定位我们的原始在运行时哪里出错了 我们可以先生成调试版本的可执行程序 #include iostream
#include signal.h
#include unistd.h
#include sys/types.h
#include sys/wait.husing namespace std;
int main()
{
int a 10;
int b 0;a / b;cout a a endl;
}运行结果如下所示 此时很显然已经帮我们核心转储了 然后我们使用gdb调试时候可以直接使用下面命令 core-file core.23652我们发现就可以直接定位出错原因了 所以core可以直接复现问题之后直接定位到出错行 也就是说先运行在core-file是事后调试 所以它就可以为这些最常出现的问题有一个core功能去终止。 所以这个就相当于Term Core 为什么这个功能云服务器是关闭的呢 因为core dump功能消耗的内存比较大。而我们的服务器一般一旦挂掉就会自动重启。计算机的速度是很快的。如果重启后又挂掉了。这样瞬间会冲击磁盘。磁盘被写满后可能操作系统也会挂掉。 此时问题就严重了。所以一般都要禁掉这个功能的。