个人网站允许做内部论坛吗,36氪网站是用什么做的,为什么网页打不开了,文网站建设费用计入什么科目引入#xff1a; 比如当前快递小哥需要通知你下来取快递#xff08;产生信号#xff09;#xff0c;然后通过电话或短信告知了你#xff08;发送信号#xff09;#xff0c;但是当前你正在打游戏#xff0c;所以你并不会马上去处理#xff0c;但是你会记得这件事… 引入 比如当前快递小哥需要通知你下来取快递产生信号然后通过电话或短信告知了你发送信号但是当前你正在打游戏所以你并不会马上去处理但是你会记得这件事信号的保存当你忙完之后你接着就去处理快递了信号的处理而一般会有3种处理方法1、幸福地打开快递默认动作 2、选择将快递送给好朋友自定义动作3、忽略他然后再打一把游戏忽略。
一、信号的预备知识 1.1 理解信号
问题1生活中常见的信号有哪些
——信号弹、下课铃、表达爱意、红绿灯、快递发短信取件码、旗语、狼烟、发令枪、冲锋号、肚子饿…… 问题2我们是怎么认识这些信号的想象我们从小到大然后认识新事物的过程
—— 小时候如果你的父母想让你认识一个具体的东西比如苹果那么他就必须通过一些简单的词语向你描述苹果的特征而如果认识具体的事比如说吃饭那么除了描述吃饭这件事的特征还得让你明白如何吃饭。
——所以我们能够记住这些常见的信号本质有两个步骤1识别信号 2知道信号的处理方法
结论1进程必须具备 识别信号能够处理信号的能力
问题3即使我们当前并没有信号产生我们是否也应该知道信号产生后应该干什么
——就好比我们现在并没有在吃饭但是这不代表我们不知道肚子饿的时候应该做什么所以我们可以这么理解当我们通过学习和认知明白了识别信号以及信号的处理方式之后这些知识已经深深嵌入我们的大脑。
结论2即使信号没有产生我们的进程也要具备处理信号的能力 信号的处理能力是属于进程内置功能的一部分 问题4信号产生了为什么有时候并不会被立即处理
—— 因为在某些时候我们可能正在做一些我们主观认为更重要的事比方说你正在打游戏已经推到对方高地了这是你的妈妈突然喊你吃饭这时候你并不想马上下去而是希望等游戏打完了再下去但是无论如何你都得下去吃饭所以你会记得这件事信号保存等你游戏打完的时候再去处理。
结论3当进程真的收到一个具体信号的时候进程可能正在执行更重要的任务而不能立即处理这个过程会有一定的时间窗口所以进程必须具备保存信号的能力
问题5 收到信号后我们可能会有哪些处理方式呢
——当你妈喊你吃饭的时候此时常规的理解应该下去吃饭默认动作但是你也有可能正在跟妈妈吵架正在赌气所以你假装没听到忽略也有可能你会一边看电视一边吃饭自定义动作
结论4进程处理信号的方式有三种1默认动作 2忽略 3自定义动作需要捕获信号
1.2 前台进程和后台进程 Linux中一次登录中一个终端一般会配上一个bash每一次登录只允许一个进程是前台进程可以允许多个进程是后台进程
问题1为什么要区分前台进程和后台进程呢为什么前台进程只能有一个呢
——为了区别又谁来获取键盘输入的资格
问题2ctrlc为什么能杀死前台进程呢
——ctrlc本质上被进程解释为2号信号被前台进程获取了信号
问题3前后台进程的理解
——当你在输入命令行的时候其实bash进程就是前台进程所以他得到了获取键盘输入的资格而当我们执行一个可执行程序后此时该可执行程序就变成了前台进程而bash则变成了后台进程. ——可如果在可执行程序后面加上 意思就是该可执行程序变成后台进程然后此时bash还是前台进程因此ctrlc就会失效因为他只能发给前台进程! 问题4为什么bash进程不会被ctrlc杀死
——因为bash进程非常重要不能随意被杀死所以他的内部对ctrlc这样的信号做了特殊处理
问题5 当一个死循环的可执行程序被放在后台执行的之后我们在给前台的bash进程输入命令的时候显示屏指令会很混乱但是为什么并不受影响
——因为信息不仅仅被键盘读取了也会回显到了显示器上输入和回显是写在不同的缓冲区所以我们看到的混乱影响的是输出但是不应该输入因此并不会影响命令的执行
——同理这也是为什么你输密码的时候即使显示器为了保护密码所以不帮你回显你也能够把密码输进去的原因
——而混乱的原因是因为显示器是一个共享资源且没有加以保护所以当多个进程同时访问的时候就会出现打印信息混乱的问题 1.3 认识信号 1-31是普通信号 不一定需要立即处理
34-64是实时信号必须马上处理
没有32和33所以一共是62个信号
1.4 认识signal
这是一个简单的信号捕获函数可以帮助我们给一个信号设置一个自定义方法 signal函数只要设置一次该进程的生命周期内就一直有效而myhandler函数只有在后面产生了这个信号之后才会被调用否则永远不会被调用 问题1我们signal函数在传参的时候不是已经绑定了信号了吗为什么handler函数里还需要signo
——因为未来这种方法可能会被多个信号当成他的自定义方法所以如果handler方法没有这个参数的话我怎么知道是因为收到哪个信号才进入handler函数的呢所以我们必须得有这个参数 问题2所有的信号都可以被捕捉和自定义吗
—— 大部分信号可以被自定义但是有些信号不行就好比9号信号就不行因为如果我们对他自定义了那么这个进程就永远都不会被杀死那万一他比如中病毒了那就会出大问题所以OS提前考虑到了这点在给予你自由度的同时也限制了你的行为就跟我们ctrlc杀不死bash进程一样自由给予我们更多的便捷但也同样需要约束其行为 1.5 硬件中断
引入1键盘是如何输入给内核的2ctrlc又是如何变成信号的——谈谈硬件中断
问题1键盘被按下肯定OS先知道可以我OS怎么知道键盘上有数据呢难道我要一直去定期轮询外设么
—— 因为外设有很多所以一直轮询是负担很大的事情所以在现代的计算机中虽然cpu不和外设直接做数据的交互但是外设可以通过想cpu发送硬件中断的信号然后让OS来帮助我们将数据拷贝到内存中
问题2可是这么多外设我cpu怎么知道这个信号来源于哪个外设呢
—— 所以必然存在不同硬件中断号码对应不同外设发出的信号而cpu不同的针脚就对应着不同外设发出的信号比如如果外设向cpu1号针脚发送信号通过高低电频那么cpu就知道这是什么设备了然后cpu的寄存器就会暂时将这个数据保存起来通过充放电
问题3cpu怎么处理这个信号呢
—— cpu收到外设的信号后他必然是通过指挥OS来帮助我们完成将外设数据拷贝到内存的工作所以OS必须得具备访问不同外设的方法其实我们的OS在刚刚启动的时候就默认在一个位置维护了一张中断向量表其中存储的就是OS访问不同外设的方法
问题4键盘上输入的不仅仅只有数据啊那ctrlc是怎么变成信号的
——所以OS在使用中断方法的时候会先进行判断如果是控制信号ctrlc的话就会将他转化成2号信号发送给进程如果仅仅是单纯的数据那么就会把数据放到内存中的缓冲区里
问题5OS调用方法后他怎么知道拷贝完成了没有因为可能有其他进程也在等呢
—— 外设在拷贝完成时也会给cpu发送中断信号所以当OS识别到时就会知道这个动作已经执行完毕了然后转而就会去做其他工作了
结论1 所以我们会发现OS其实一点都不关心外设OS是通过不断接受硬件中断来处理外设的当他在cpu的寄存器中识别到相关外设发出的硬件中断信号后然后通过中断向量表找到从该外设拷贝数据到内存中的方法并执行 而什么时候拷贝结束也是由外设向cpu发送硬件中断信号OS才能得知的
结论2 外设拿到数据后向cpu发送硬件中断然后OS识别到后再执行中断方法这个采用的是软硬件结合的方式而我们之后学习的进程中的信号就是纯软件方式对进程模拟的硬件中断 二、信号的产生
2.1 同步和异步
1、信号的产生和我们自己的代码是异步的
如何理解同步和异步
1比如你是一个老师你口渴了让张三去帮你买水这个时候你告诉同学们让大家先自习等张三回来了再继续上课
——此时张三什么时候回来会影响到你什么时候上课因为大家都得等他所以这就是同步
2还是刚刚的场景张三去买水而你还是照常上课所以无论张三什么时候回来都不会影响你上课的进度只有当张三喊报告的时候你才会响应一下让他进教室甚至如果张三旷课了你也不管。
——此时张三买水和我上课就是异步的你做你的我做我的互相不影响只有你买水回来了我可能会稍微暂停一下课程然后响应一下。 你回来之前我是不会等你的。
2、无论信号如何产生最终一定是由OS发送给进程的因为OS是进程的管理者
2.2 键盘组合键和kill命令
ctrlc2号信号
ctrl\ 3号信号
kill -signo pid向某个pid的进程传递signo信号
2.3 系统调用
2.3.1 给任意进程发任意信号-kill 2.3.2 给自己发送任意信号-raise 2.3.3 给自己发送特定信号-abort abort是中止的意思 abort函数是属于3号手册所以他并不仅仅只是简单地发送6号进程 当我们尝试去捕获6号信号的时候我们发现最后进程还是会终止 但是单纯给这个进程发送6号信号却不会 所以我们可以得到的结论是
1abort执行完指定的自定义函数后会自动恢复成默认然后重新发送6号信号abort内部多做了一点工作所以使用abort的时候无论6号信号是否被捕捉进程都会被强制终止
2kill是系统调用而raise和abort只不过是在kill的基础上进一步封装的C库函数所以他可能会有一些差异化的处理方式一些细节需要我们自己去理解
2.3.4 模拟实现kill命令 2.4 异常硬件条件
2.4.1 除0异常和野指针的解析
除0错误收到了8号信号 野指针收到了11号信号段错误 问题1OS是怎么知道进程的内部出现除0错误的
——cpu在处理该进程的时候他的一些信息都是隶属于该进程的上下文而除0会让cpu上的状态寄存器中的一个溢出标记bit位变为1 因此OS就可以检测到异常然后发送信号让进程崩溃。
问题2为什么进程出现异常了OS不崩溃
——虽然我们修改的是cpu的状态寄存器但是当前的信息只属于该进程的上下文所以只会影响你这个进程具有独立性跟我OS没有任何关系我依然可以去跑其他进程 问题3OS怎么知道进程内部出现段错误
—— 段错误其实就是内存错误而我们虚拟地址空间和物理空间是通过页表去映射的页表就相当于是一个kv模型而为了提高转化的效率有一个叫做MMU的内存管理单元帮助我们做虚拟地址到物理地址的转化如果转化成功的话会将转化成功的物理地址放在一个寄存器里而如果转化失败的话也会将转化失败的虚拟地址放在一个寄存器里而当OS检测到的时候就会发送段错误的信号 问题4那为什么OS在发现进程异常后为什么必须发送信号来让该进程终止呢
—— CPU也是硬件而OS作为硬件的管理者必须对CPU的状态非常了解所以当OS发现cpu状态寄存器出现异常的时候那该进程的后续的执行肯定就没有任何意义了所以必须向该进程发送强制终止的信号 ——OS是通过不同的寄存器或者寄存器里不同的标记位来判断是哪种异常的所以异常是首先表现在硬件上然后由OS识别过后再发送相关的信号给当前正在cpu上运行的进程
问题5我们通过捕获信号不让进程崩溃但是为什么信号会被一直触发呢
——我们通过捕获信号进程如果不崩溃的话那么该进程就要一直被调度运行可是虽然你保护了这个进程但是由于你始终没有修正硬件错误所以OS一调度该进程就会发现有错误然后就会一直给你发信号
问题6那我们可以去修正这个硬件错误么
——首先不谈你是否有这个权限问题是由你进程引发的即使你修正了硬件错误往下执行了但是因为你中间出错过无论如何我都不会相信你的运行结果了所以你执行下去是没有任何意义的你出错的就应该自觉退出
问题7既然出现异常的时候进程必须得退出那我们为什么还要去捕获呢
——我们捕获异常并不是为了去解决这个异常也没有这种能力而是为了能够通过这个渠道来让进程退出之前用户能够更加清晰地知道出现了什么问题或者是有那种重要数据需要临时保存下来或者是需要关闭某些资源。 这样可以尽可能减少损失同时方便上层用户了解情况土一点说异常捕获不是为了让你不死而是为了让你死个明白或者死之前托付一些重要的事情。
问题8OS明明可以直接干掉进程他狠起来连自己都可以干可他为什么不这样做呢而是要用发信号这种温和的方式
——一方面是因为OS作为底层并不清楚上层在干什么他担心你万一做的是一件特别重要的事那我们是不是应该在进程退出之前做一些准备比如临时存储重要数据打印一些错误信息的日志这样未来可以方便用户去修正这个问题让进程死个明白交代后事
——从OS的视角来看我可以杀死这个进程但是我担心这个进程很重要如果我直接把你杀了到时候你用户出问题了找不到原因还回来找我撒气那干脆这样我检测到了异常我就给你发信号当你检测到信号的时候你可以通过一些捕获然后设置一些自定义动作来让进程在终止前做一些必要的收尾工作然后再exit 我已经给你机会了你如果还处理不好那就是你自己的问题了跟我OS没有任何关系
——所以我们会发现OS虽然像个皇帝一样高高在上但他的行为其实也是要受到约束的不能无脑地去行事
2.5 软件条件
异常并不是只会由硬件产生
2.5.1 一个进程发送信号杀死另一个进程
我可以获取其他进程的pid然后用kill命令把他杀了 2.5.2 系统调用出错 OS对于文件会有不同的态度取决于具体问题的严重性一些情况下会通过系统调用接口的返回值告诉你 2.5.3 alarm 参数闹钟响的秒数
返回值闹钟提前响的时间比方说我们对闹钟设置的都是5s另一个闹钟已经响了但是你从设定到现在只过去了2s这时说明你提前了3s所以要返回3 闹钟不是异常所以只会响一次 让闹钟一直响的方法捕获之后再设闹钟可以通过这种方式在进程中设置一些定时任务 OS中会存在大量的闹钟设定同一个进程可以设置多个不同进程也可以设置因此OS必须要将他们管理起来
问题1闹钟结构体里有什么呢
——1进程pid 知道该闹钟隶属于哪个进程 2当前时间戳参数未来时间戳只要当前时间戳大于未来时间戳就表明超时了就会给对应的进程发14号信号
问题2如果我用链表管理起来我怎么知道哪一个要超时了呢难道一直遍历链表么
——我们可以用一个优先级队列小堆管理起来这样只要当前堆顶元素没有超时那么整个优先级队列就都没有超时 2.5.4 core dume
core dump究竟是什么呢我们会发现一些信号会带有core
我们通过一段代码看看这个core dump标记位是否发生变化 然而因为云服务器默认没有开启core功能所以我们观察不到现象 接下来我们要尝试打开core功能
1、ulimit -a查看当前系统的资源配置 2、ulimit -c 10240 修改block大小开启core功能 所以core dump是核心转储的意思打开系统的core dump功能一旦进程出现core类型运行时错误的异常OS就会将进程在内存中的运行信息给我dump转储到进程的当前目录形成core.pid文件
他的核心思想是先运行再通过core文件进行事后调试
问题1你都可以通过发信号、然后捕获来告知错误原因那为什么还要多次一举形成一个core文件呢
—— 因为我想知道更详细的信息比如知道是在哪行出错的形成了core文件后我们可以用-g进行编译然后打开gdb输入core-file core.pid 可以看到非常详细的信息 问题2core功能看起来还不错那为什么云服务器默认不打开core功能呢
——因为大多数情况下如果一个服务挂掉了应该由运维程序员去重启但是大公司后端服务器集群很多所以如果手动去重启就很麻烦所以他会做很多自动化运维的手段比如说当服务挂掉的时候会立马检测错误然后想办法先恢复出来比如重启然后事后再通过一些日志信息来排查。所以大多数情况下如果服务挂掉后都会重启的可如果存在那种很差的代码跑起来就挂那么每次一重启就会挂那么就会出现很多core文件core文件会占据一定的空间时间长了时候本来可能只是一个简单的问题但是后期就变成了磁盘打满core文件的内存问题甚至极端情况下OS也有可能会挂掉所以core一般来说要在线上服务关掉的这样可以保证重启的功能可以维持