网站设计欣赏中国,苏州模板网站专业设计,微信小程序推广引流怎么做,家政公司简介模板信号产生 1.预备知识2.信号产生2.1通过键盘发送信号2.2系统调用接口向进程发送信号2.3硬件异常产生信号2.4软件条件2.5总结 自我名言#xff1a;只有努力#xff0c;才能追逐梦想#xff0c;只有努力#xff0c;才不会欺骗自己。 喜欢的点赞#xff0c;收藏#xff0c;关… 信号产生 1.预备知识2.信号产生2.1通过键盘发送信号2.2系统调用接口向进程发送信号2.3硬件异常产生信号2.4软件条件2.5总结 自我名言只有努力才能追逐梦想只有努力才不会欺骗自己。 喜欢的点赞收藏关注一下把 首先说明一点信号不是信号量。不能把这两个东西放在一起。
那信号讲什么呢
1.预备知识
那信号是怎么回事这里只能这样说信号是针对进行发送某种信号到来的一种机制让信号能被进程处理。让我们在后面的知识中更能理解这句话的含义。
先见识见识信号。 前面数字是信号的编号后面大写的是宏。 就比如杀死一个进程
kill -9 进程pid这里可以使用编号也可以使用SIGKILL
再可以数一数信号有多少个。 其实并没有64个03233信号是没有的。 【131】普通信号 【3464】实时信号 我们不学这个
先说信号的概念帮我们简单了解一下信号但再说信号一些概念之前我们先从生活角度中的信号来帮我们理解。
生活中的信号 1.红绿灯 2.闹钟 3.信息通知 4.劳资蜀道三 5.女朋友把你拉黑 6.烽火台狼烟 等等这些都是我们生活中的信号。
我们以红绿灯为例。人是能够识别红绿灯的。 这里识别有两层意思。
第一个问题可能会觉得很奇怪你为什么能够识别红绿灯 第二个问题当信号来的时候你不一定会立即处理这个信号 信号的产生是异步的。
举个栗子你正在宿舍打着游戏这时外卖小哥给你打电话让你下楼取餐但是你忙着打着游戏并没有立刻下楼去取而是让他把外卖放在楼下。当你打完游戏记起还有外卖没拿所以去楼下拿外卖。当然还有另一种情况你打游戏上头了。然后忘记有外卖在楼下这回事。 当绿灯到达的时候你有三种处理动作 如何把上面的这些概念迁移到进程中呢
这里要有一个共识信号是给进程发的 进程是如何识别信号的认识动作 进程本身就是程序员编写的属性和逻辑的集合。所以这里先粗略的说是由程序员编码完成的。后面学了信号更多知识就可以详细说明了。 当进程收到信号的时候进程可能正在执行更重要的代码所以信号不一定会被立即处理 进程本身必须要有对于信号的保存能力 进程在处理信号的时候一般有三种动作默认自定义忽略【信号被捕捉】
在我们现在还没有学过信号上面124我们都不能具体解释不过3我们可以根据以往学过的知识来分析分析。
如果一个信号是发给进程的而进程要保存那么应该保存在哪里 task_structPCB结构体中。
如何保存呢 更准确来说如何保存是否收到了指定信号【131】。 是否是一种两态我们是不是可以在task_struct结构体里当然task_struct结构体中包含其他一大堆的属性可以存在一个unsigned int signal32位比特位。 所以在进程中是不是只要存在对应的位图结构然后当我们收到信号时是不是只要将对应信号的位置由0-1就代表我们已经完成了信号的发送并且让进程暂时把这个信号保存起来了。
那如何理解信号的发送呢 发送信号的本质修改PCB的信号位图 PCB是内核维护的数据结构对象-----PCB的管理者是OS谁有权力修改PCB中的内容呢------OS!!
所以无论未来我们学习多少种发送信号的方式本质都是通过OS向目标进程发送的信号!! 未来想让用户也能发送信号-------OS必须要提供发送信号处理信号的相关系统调用
我们使用kill命令底层一定调用了对应的系统调用
2.信号产生
2.1通过键盘发送信号
int main()
{int cnt0;while(true){printf(我是一个进程我正在运行%d\n,cnt);sleep(1);}return 0;
}ctrlc热键终止前台进程。 本质ctrlc是一个组合键----OS识别----OS将ctrlc解释成为2号信号2SIGINT-----处理三种动作。但我们对2号信号没做任何改变所以是默认处理。
man 7 signal //查看信号对应的手册Action(行为)Term Terminal终端结束进程 Comment(解释)从键盘中断
所以2号信息的默认动作结束进程。
接下来验证一下是不是发送了2号信号。
先介绍一个函数signal对指定的信号设置一个自定义动作。 signum信号编号捕捉那个信号 handler函数指针捕捉这个信号后你想怎么做这是一个回调函数
接下来验证
#includeiostream
#includeunistd.h
#includecstdio
#includesignal.hvoid handler(int signo)
{cout捕捉到信号:signoendl;
}int main()
{signal(2,handler);int cnt0;while(true){printf(我是一个进程我正在运行%d\n,cnt);sleep(1);}return 0;
}我不是对2号信号进行捕捉吗并且代码还做了修改为什么运行结果没什么变化。
注意这里是signal函数的调用并不是handler的调用并且仅仅是设置了对信号的捕捉方法并不代表方法被调用了。所以一般这个方法不会执行除非收到对应的信号。 这两种方法都可以发送信号。然后signal函数对2号信息进行捕捉。 现在发2号信号虽然能被捕捉但是进程怎么退不出来了。 这是因为我们把2号信号默认动作改成了自定义动作。 如果想退出怎么办
kill -9 编号 //杀死进程或者在自定义动作种加一个exit。
void handler(int signo)
{cout进程捕捉到了一个信号信号编号是:signoendl;exit(0);
}其实还有一个组合建ctrl\发送的是3号信号。也能终止进程。 这里留了一个问题Core和Trem都是终止进程。为什么OS要设置两种不同的行为有什么用
2.2系统调用接口向进程发送信号 kill可以给任意进程发送任意信号。 pid目标进程pid sig发送几号信号 成功返回0识别返回-1。 我们给进程发信号底层用的就是这个。
前面说过信号是由OS向进程发送的。OS有这个能力但不代表有权限使用这个能力。 信号的发送是由用户发起而OS执行的。
接下来我们写的代码想呈现这样的效果一个进程正在运行另一个进程在命令行给这个进程发送任意信号。
//mysignal.cc#includeiostream
#includeunistd.h
#includecstdio
#includesignal.h
#includesys/types.h
#includestringusing namespace std;void Usage(const string proc)
{cout\nUsage proc pid signo\nendl;
}// ./mysignal pid signo------命令行参数
int main(int argc,char* argv[])
{if(argc ! 3){Usage(argv[0]);exit(1);}pid_t idstoi(argv[1]);int signostoi(argv[2]);int nkill(id,signo);if(n ! 0){perror(kill);}return 0;
}//mytest.cc#includeiostream
#includeunistd.hint main()
{int cnt0;while(true){printf(我是一个进程,pid:%d,我正在运行%d\n,getpid(),cnt);sleep(1);}return 0;
}这个不就是和我们在命令行执行kill命令一样的原理吗。
kill()可以向任意进程发送任意信号 raise给自己发送指定信号。-----就相当于 kill(getpid()任意信号) int main()
{int cnt0;while(true){printf(cnt:%d,pid:%d\n,cnt,getpid());if(cnt 10)raise(9);sleep(1);}return 0;
}abort给自己发送指定的信号6号信号。------相当于kill(getpid(),SIGABRT) int main()
{int cnt0;while(true){printf(cnt:%d,pid:%d\n,cnt,getpid());if(cnt 10)abort();sleep(1);}return 0;
}关于信号处理的行为的理解有很多的情况进程收到大部分的信号默认处理动作都是终止进程。
既然大部分信号默认都是终止进程那有那么多类的信号有什么用
信号的意义信号的不同代表不同的事情但是对事情发生之后的处理可以一样。 也就是说进程意外终止了我们可以根据信号不同来确定是什么原因导致的。
2.3硬件异常产生信号
信号产生不一定非得是用户显示发送的。
看下面一段代码
int main()
{int cnt0;while(true){printf(cnt:%d,pid:%d\n,cnt,getpid());int a10;a/0; }return 0;
}为什么除0会终止进程 因为当前进程会收到来自OS发送的信号。SIGPFE。
如何证明呢
void handler(int signo)
{cout进程捕捉到了一个信号信号编号是:signoendl;
}int main(int argc,char* argv[])
{signal(SIGFPE,handler);int cnt0;while(true){printf(cnt:%d,pid:%d\n,cnt,getpid());int a10;a/0; }return 0;
}我确实捕捉到了8号信号但为什么OS一直发送信号呢 难道是我这里一直在死循环的原因
修改一下代码
int main()
{signal(SIGFPE,handler);int cnt0;int a10;a/0; while(true){printf(cnt:%d,pid:%d\n,cnt,getpid());}return 0;
}发现还是一直在发送8号信号。 这到底是为什么
先来解答OS如何得知应该给当前进程发送8号信号呢或OS怎么知道我除0了呢 CPU运算异常了OS会不会知道 OS肯定会知道CPU运算出现了问题因为OS是软硬件资源的管理者。
OS查看到状态寄存器溢出位由0-1OS就识别到CPU内部出错了。 谁导致CPU出错了 CPU当前正在调度谁就是那个进程出现了问题OS向目标进程发送8号信息目标进程收到8号信号后序处理就会终止自己了。
那为什么一直发信息呢 收到信号不一定会引起进程退出没有退出进程可能还会被CPU调度。 CPU内部的寄存器只有一份但是寄存器种中的内容属于当前进程的上下文CPU内部状态寄存器溢出标记位由0-1你是没有能力或者动作去修改这个问题的。 当进程被切换的时候就有无数次状态寄存器被保存和恢复的过程所以每一次恢复的时候就让OS识别到了CPU内部的状态寄存器中标记位是1每一次都会发8号信号。
再看一种由硬件异常产生的信号。
int main()
{signal(SIGFPE,handler);int cnt0; while(true){printf(cnt:%d,pid:%d\n,cnt,getpid());int* ptrNULL;*ptr10;}return 0;
}为什么野指针就奔溃了 因为OS会给当前进程发送指定的11号信号。 证明一下。
void handler(int signo)
{cout进程捕捉到了一个信号信号编号是:signoendl;
}int main()
{signal(11,handler);int cnt0; while(true){printf(cnt:%d,pid:%d\n,cnt,getpid());int* ptrNULL;*ptr10;}return 0;
}OS怎么知道我野指针了呢 根据我们以前学的知识虚拟地址—物理地址的转换要经过页表。今天我要告诉你除了页表还有一种硬件MMU。MMU是内存管理单元。 MMU其实是通过读取页表中的内容在内部形成对应的物理地址然后再去访问我们对应的物理地址。 当我们ptr解引用访问的是0号地址。经过页表映射发现在映射的时候当前进程是不允许去访问对应的0号地址的。不允许访问当然可以拦截不让你访问。但更重要的是你为什么会访问所以OS觉得你犯错了就应该付出相应的代价所以MMU这个硬件因为对应的越界访问野指针访问发送异常。OS知道当前硬件发生异常所以OS将异常转换成11号信号发送给目标进程。
2.4软件条件
在管道我们说过匿名管道的一个场景读端关闭写端一直写没有任何意义OS会给当前写进程发送SIGPIPE信号然后进程终止了。
所谓的进程OS管道尤其是管道和这一整套OS发信号的原因和OS发信号的过程和硬件都没有关系。而是仅仅因为读端关闭了这一软件条件所触发的OS发送信号给目标进程这种场景我们就称之为软件条件会触发信号。
下面我们要说的是一种定时器软件条件。给当前进程设定闹钟alarm()。 设置一个时钟时刻发送信号。
seconds多少秒之后发送信号 返回值是0或者是以前设定的闹钟时间还余下的秒数 发送的是SIGALRM(14)信号。
int main()
{//这个闹钟是给现在设的还是给未来设的//是不是我调用了alarm我的进程会立马收到对应的闹钟呢//答案并不是。这是给未来设置的闹钟。是1秒之后向我这个进程发信号。alarm(1);int cnt0;while(true){printf(cnt: %d\n,cnt);}return 0;
}根据运行结果请问我们这段代码有什么用呢
其实这是统计1S左右我们计算机能够将数据累计多次次。
修改一下代码再看一下效果。
int cnt0;void catchSig(int signo)
{cout进程捕捉到了一个信号信号编号是:signo cnt :cntendl;
}int main()
{signal(SIGALRM,catchSig);alarm(1);while(true){cnt;}return 0;
}次数多了很多次这是因为printf会访问外设而访问外设比较慢。 还有就是这个闹钟是一次性闹钟响了之后就不响了。
如果想响多次要重新在设定闹钟。
void catchSig(int signo)
{cout进程捕捉到了一个信号信号编号是:signo cnt :cntendl;alarm(1);
}alarm(0)取消闹钟并且返回闹钟剩下多少时间。
int main(int argc,char* argv[])
{signal(SIGALRM,catchSig);alarm(5);while(true){cnt;if(cnt 3){int nalarm(0);coutnendl;}sleep(1);}return 0;
}为什么设闹钟就是软件条件了呢 闹钟其实就是用软件条件实现的。 2.5总结 1.上面所说的所有信号产生最终都要有OS来进行执行为什么 OS是进程的管理者 信号的处理是否是立即处理的 在合适的时候(什么合适的时候下面说)。 3.信号如果不是被立即处理那么信号是否需要暂时被进程记录下来记录在哪里最合适呢 4.一个进程在没有收到信号的时候能否能知道自己应该对合法信号作何处理呢 5.如何理解OS向进程发送信号能否描述一下完整的发送处理过程 上面的问题我们都可以从接下来信号的学习中得到答案。
信号产生这里还有最后一个问题。
man 7 signal //信号手册Stop暂停进程Cont继续进程Ign忽略进程(这个信号说完最后面解释)这些都没有问题。 TermCore都是终止进程有什么区别 其实这有关于进程退出时核心转储问题。
看下面一段代码
int main()
{//核心转储while(true){int a[10];a[100]10;}return 0;
}int main()
{//核心转储while(true){int a[10];a[1000]10;}return 0;
}数组明明都越界了啊怎么进程没有奔溃报错 其实在C的时候就说过数组越界不一定会报错因为对数组的检查是随机的。这是我们在语言层面的理解。
int main()
{//核心转储while(true){int a[10];a[10000]10;}return 0;
}那这次怎么就检测出来了。按照语言层面解释可能是因为这次越界被检测到了。
接下来我们从底层理解
编译器上编译你的代码时在栈上给你开辟多大空间和编译器是强相关的你要申请10个int大小元素的数组它确实给你的就是10个元素指的是数组的元素但是并不代表给你的代码块或者函数分配栈帧结构是10个元素的大小可能给你的会很大所以呢即便你越界了但是你还是在有效栈区里所以没有报错除非你访问了一个完全不是你的空间。比如你现在访问的时候访问的是系统的地址空间中或者访问到一个不让你访问的区域那么此时OS系统就能识别出来。所以OS在识别越界的问题上有可能也死别不出来从而出现把数据改变了但用户不知情的情况。 这个信号是11号信号段错误它的终止方式是Core。 像Trem这种结束是正常结束OS不会做额外操作的。而以Core这种结束OS除了终止进程它还要做其他工作。
但是以Core为终止我也没见OS做什么额外工作啊 除了给我打印出一个错误描述像Trem终止进程不也是给我打印出一个错误描述吗。
在云服务器上默认如果进程是Core退出的我们暂时看不到明显现象如果想看到需要打开一个选项。
ulimit -a //可以看到系统给我们当前资源设置的上限core file size 大小为0这是云服务器默认关闭了core file选项。 想要打开ulimit就带上你想要设置谁-c选项大小为多少。
ulimit -c 1024 //打开云服务器core file选项默认可以向OS中形成最大为1024个block的数据块然后运行同样的代码 相比较我们之前运行的时候除了段错误后面还跟了一个core dumped 发现我们当前目录下多了一个以core命名的文件。 所谓的核心转储当进程出现异常的时候我们将进程在对应的时刻在内存中的有效数据二进制数据转储到磁盘中。
该文件我们用vim打开是一堆乱码。我们是无法识别的。 那形成核心转储有什么意义呢或者说为什么要有核心转储
一般进程在运行的时候出现崩溃其实我们更想知道的是为什么会崩溃在哪里崩溃。所以OS为了便于我们后期做调试会将进程在运行期间出现崩溃的代码的相关上下文数据全部dump到磁盘中用来进行支持调试。
如何支持呢 linux下默认编译都是release不能调试debug才能调试因此我们编译时带上-g选项。 当前自动帮我们评判进程收到11号信号引起的段错误报错是在mysignal.cc的第37行代码是a[10000]10引起的错误。直接就帮我们找到了错误。 这种直接快速定位到出问题的方式我们称之为事后调试。 像这种以2号信号Trem终止进程并不会在当前目录下形成core文件。
TremCore都是进程终止它们的区别是以Core退出的可以被核心转储的以便于后序快递定位问题以Trem退出就是正常终止进程。
以后进程出现异常退出你可以查看是什么信号的什么行为导致的如果是Core把Core打开再执行一下gdb快速定位问题。
信号产生到目前为止差不多讲完了但这里可能有人会有这样的疑问。如果我们把所有信号都捕捉换成自定义动作不让进程退出那进程是不是无法被杀死了。
void catchSig(int signo)
{cout进程捕捉到了一个信号信号编号是:signoendl;
}int main()
{for(int signo1;signo31;signo){signal(signo,catchSig);}while(true) sleep(1);return 0;
}难道真的无法杀死了 kill -9还是可以杀死进程无论你怎么修改无法对9号信号设定捕捉即使你做了OS也不会给你设置。