当前位置: 首页 > news >正文

手机网站建设专家搭建个官网需要多少钱

手机网站建设专家,搭建个官网需要多少钱,windows软件开发流程,怎么在云服务器上建设网站#x1f496;作者#xff1a;小树苗渴望变成参天大树#x1f388; #x1f389;作者宣言#xff1a;认真写好每一篇博客#x1f4a4; #x1f38a;作者gitee:gitee✨ #x1f49e;作者专栏#xff1a;C语言,数据结构初阶,Linux,C 动态规划算法#x1f384; 如 果 你 … 作者小树苗渴望变成参天大树 作者宣言认真写好每一篇博客 作者gitee:gitee✨ 作者专栏C语言,数据结构初阶,Linux,C 动态规划算法 如 果 你 喜 欢 作 者 的 文 章 就 给 作 者 点 点 关 注 吧 文章目录 前言一、信号的预备工作1.1生活-计算机1.2ctrlc终止进程1.3通过硬件来谈谈ctrlc 二、信号的产生2.1os自己给进程发送信号2.2硬件异常和软件条件2.2.1硬件异常2.2.2软件条件 2.3 core dump 三、信号的保存3.1信号的其他相关概念3.2在内核中表示3.3sigset_t3.4信号集操作函数3.5验证3.1节的概念 四、信号的处理4.1信号处理的流程4.2 信号捕捉的另一个函数sigaction 五、总结 前言 今天博主将给大家讲解的信号这个知识信号和信号量是两个哪有任何关系的概念所以大家不要以为信号量没有学就不敢来学信号信号是我们日常生活中必备的有了信号才会让我们社会变得稳定计算机里面的信号也是起了这样的作用没有信号那么程序就会乱所以接下来博主就通过生活信号的例子来映射到计算机里面的信号在带大家认识计算机信号是怎么样会有一条讲解逻辑大家跟着博主的思路走下去吧。 讲解逻辑 预备工作讲解信号的认识信号的产生信号的保存信号的处理 一、信号的预备工作 1.1生活-计算机 先给大家讲解一个小故事跟着博主总能时不时听到一个小故事 有一天小明在家非常的饿但是他又不想出门此时他就点了一份外卖但是他不能干等着觉得太无聊了就开了一把王者当他准备推别人高低了外卖到了打电话来说你外卖到了来去一下但是你此时有更重要的事情要做你就说等会去拿但你打完这把此时你大概率会取外卖而不是再开一把上面的小故事就包含了讲解逻辑的四点我们知道打电话来了就是信号的认识真的打电话来了就是信号的产生等一会再拿就需要把信号保存起来打完了去拿就是信号的处理。 显然大家对上面会产生疑惑但是又觉得这是必然的谁不知道打电话来了就是外卖到了呢好比红灯停绿灯行的道理呢但是博主还是需要一点点的给大家灌输信号的概念越是简单的东西就要细细的讲。 听完上面的故事接下来通过上面的故事引发了下面这三点结论 在生活中我们常见的信号有信号弹上下课铃声红绿灯闹钟以及外卖的电话等等 你是怎么认识这些信号 a.肯定是有人教我自己记住了这些常见的信号 b.教会了识别信号还要教处理信号的方法 即使是我们现在没有信号的产生我也知道信号产生之后我该干什么 大家现在在家里坐着也知道红灯绿灯这个信号如果发生我们该干什么 信号产生了我们有可能不会立即处理这个信号在合适的时候后面会讲。 因为我们此时在做更重要的事情好比在推高地所以在信号产生后-时间窗口-信号处理在这个时间窗口内你必须记住信号已经到来了。 上面用加粗标记的你在计算机里面指的是进程也对应三点结论 进程必须要有识别信号和信号产生后的处理能力信号没有产生也要具备处理信号的你能力所以这些信号在一开始就内置在进程的描述对象里面创建时候自动初始化进程即便是没有收到信号也能知道哪些信号该怎么处理所以在进程中可以在任意位置使用kill -l查看有哪些信号当一个进程针对收到一个具体的信号的时候进程可能并不会立即处理这个信号会在合适的时候所以一个进程当信号产生到信号开始被处理就一定会有一个时间窗口就要保证在时间窗口内进程要具有临时保存这些信号的能力这也叫信号相对于进程控制来说好似异步的。 我们还没有开始讲解进程的信号通过生活的例子我们就可以得出进程应该也符合生活中的结论 1.2ctrlc终止进程 我们常用操作哪些是具备了发送信号的功能 我们来写一个简单的程序 #includeiostream #include unistd.h using namespace std;int main() {while(1){couti am aprocessendl;sleep(1);}return 0; }大家应该知道CTRLC会终止进程但是为什么可以终止进程呢这个肯定和信号有关但是先放放先带大家认识一下前台进程和后台进程 1前台进程 ./test 2后台进程 ./test 前台进程和后台进程的区别是我们在前台进程运行test.cc其余的指令就跑不了(问题1)而且命令行也显示不出来但是CTRLc问题2可以终止进程而后台进程我们发现指令程序还可以继续跑(问题1)而且分开输入指令依旧可以跑问题3最重要的是CTRLC终止不了进程了问题2但是还是打印在显示器上问题4 问题2 我们的每一次登录就是一个终端窗口只允许一个进程是前台进程bash就是一开始的前台进程和运行多个后台进程当有其他前台进程bsah就自动变回后台进程当没有前台进程bash就变成前台进程前台进程能够收到键盘输入所以当我们的test变成后台进程这个进程就收不到键盘输入的CTRLc,就退出不了但我们的bash是前台进程的时候我们使用CTRLcbash怎么不退出原因是bash进程受保护了收到ctrlc不做处理 问题1 我们的CTRLc只能终止前台进程后台进程只能通过kill -9 pid杀死原因是只有前台进程才可以收到键盘输入test是前台的时候可以被ctrlc终止是后台的时候就不行 问题3 答案看1.3章节的第四点 问题4 后台进程为什么还打印在显示器上不能以为打印在屏幕上就是前台进程这是一个显示器文件和进程是没有关系的该往显示器打就往显示器上打印 讲解完毕前后台我们提出疑惑为什么我们的ctrlc能够终止前台进程 原因是收到了2号信号,是终止程序我们来看看进程内置了多少个信号kill -l 我们一共有62个信号没有32 33号信号这些信号其实都宏定义左右两边都是一个意思0-31是我们这篇要讨论的信号34-64是实时信号收到信号立马要处理我们不讨论。为什么没有32 33号信号这就与历史有关大家下来可以去搜搜 信号的处理方式 大家默认的处理动作无视这个信号不做处理自己设计一个处理动作自定义捕捉后面会经常用这个验证结论 上面的三点在我们日常生活中其实这样比如红绿灯默认是红灯等绿灯行不管红绿灯直接无视红绿灯亮起你有自己的一套动作就这三种 突然说起这个是为了验证我们程序在输入CTRLC确实是收到2号信号 我们来介绍一个函数 这个函数的第一个参数是传入要修改信号处理方式的信号第二个参数对应自定义动作是一个函数指针这个函数有一个参数是我们第一个参数让我们一起来验证一下吧 #includeiostream #include unistd.h #include sys/types.h #include signal.h using namespace std; void myhandle(int signal)//这个函数后面会经常用到 {couti get a signal:signalendl; } int main() {while(1){//signal(SIGINT,myhandle);signal(2,myhandle);//修改2号信号的默认处理方式couti am aprocess:getpid()endl;sleep(1);}return 0; }确实我们验证我们的进程是收到了2号信号因为修改了处理方式所以就没有终止进程因为处理方式只能选择一种 解决疑惑 我们的signal函数为什么第一个参数已经传信号了为什么myhandle还要传参数 系统接口都是c语言里面写的函数直接本质是独立的不是c的类声明一个成员变量内部的函数都可以使用这个成员变量signal内部会调用myhandle这个函数因为在一个进程里面可能不止一个信号要修改处理方式都是调用myhandle这个处理方式为了标识是哪个信号调用了这个处理方式的函数所以需要传参。就好比下面 signal(2,myhandle); signal(3,myhandle); 这样就可以知道哪个信号使用这个处理方式 我们的signal为什么放在第一行不放在其他地方我们的目的是让这个进程以后收到这个2号信号换成自定义处理方式不需要放在循环里面放在最后也不行放在最前面就恁恶搞保证这个歌程序在任意时刻收到这个信号都可以执行我的自定义方式 看看哪些信号可以被修改处理方式 既然可以修改信号的处理方式我们如果把9号也修改进程在死循环那么这个进程是不是就无敌了我们来看验证结果 通过结果我们发现除了9号和19号不能被修改其余都可以被修改这样说明系统不会让一个无敌的进程存在因为根据常理这两个信号允许被修改可能会造成一系列严重的后果大家下来自己去思考一下也很容易理解。 1.3通过硬件来谈谈ctrlc 以键盘代替硬件 这一幅图让大家了解硬件的数据是怎么被os读到内存里面的。博主讲解这个一个是为了给大家涨知识另一个是让大家再次认识信号我们的硬件中断其实就是一个信号但是和我们今天讲的信号没有关系但是我们今天讲的信号就是用软件的方式对进程模拟硬件中断 如果是那种abcd的输入按键os就直接识别到这样的数据但是收到像ctrlc这样的组合键的时候会做判断将ctrlc转换成2号信号给进程 我们的键盘有数据os就会将键盘的数据读取到内存那么我们的os怎么知道数据读取结束呢os不必知道键盘数据读完也会给cpu发送硬件中断还是一样的套路其他硬件也是这样的操作 为什么我们cin,scanf在输入的时候会进行阻塞等待键盘输入呢原因是我们os是进程的管理者你键盘不给我数据我就让这个进程等待你啥时候给我我就让进程继续运行下去是os控制着一切的。 cpu的引脚连接多个硬件但不是每个硬件都对应一个寄存器这样就会导致多对一的关系万一多个硬件发生中断怎么办CPU先处理那个硬件中断呢所以我们设计者为了避免这个问题在体格时刻值允许一个硬件中断发过来即使你同时使用鼠标和键盘在屏幕上看着是一起的但是对于CPU他是不同时刻获取中断的我们感知太慢了给我们他们是同时发送中断的错觉。 来回答1.2中问题3的问题 相信大家的问题应该得到了解答我们有的时候要回车才可以有的时候按下来就有通过上面的图大家应该就可以明白设置了缓冲区A的缓冲策略什么场景用什么刷新策略。 二、信号的产生 上面是我们讲解信号的预备工作接下来就开始讲解信号的产生。 通过1.3节我们通过ctrlc这样的组合键给进程发送信号也就是信号产生的过程也知道进程是怎么收到的组合键的通过os. 所以接下来就来看看我们键盘上面还有哪些组合键可以给进程发送信号博主带大家来测试一下 2.1os自己给进程发送信号 这个信号的产生的不一定是进程出现了问题而是os自己去发送的。有三种方式 第一种方式 ctrlc:2 ctrl\:3 ctrlz:19 大家看到结果了吧还有其他的大家可以自己去摸索。 第二种方式 kill -signal pid第三种方式 这个是通过系统调用接口来产生信号一会介绍完系统调用接口博主带大家模拟实现一个符合第二种方式的mykill也会比较有意思 kill 这是一个可以在命令行输入也可以当成函数知识名字一样一个是指令一个是函数不要以为是一样的这个函数的主要功能就是给任意进程发送信号好比父子进程 第一个参数进程的pid第二个参数就是信号 来看案例 #includeiostream #include unistd.h #include sys/types.h #include signal.h using namespace std; void myhandle(int signal) {couti get a signal:signalendl; } int main() {coutgetpid()endl;signal(7,myhandle);int cnt5;while(1){couti am a procendl;if(cnt0){kill(getpid(),7);//我这里就一个进程所以进程pid就是自己cout我收到7号信号了,退出循环endl;break;}cnt--;sleep(1);}return 0; }我们使用kill函数实现了给进程发送信号大家下来自己测试一下给不同进程发信号可以使用管道让我们进程间进行通信把你要发信号的进程号传过去。接下来模拟的mykill中也有体现 raise 这是一个kill子集的函数他的作用是给调用他的进程发送信号所以少了一个参数。 raise(7);//把刚才kill的位置换成这个就可以了这个函数还是比较简单的 abort 这个函数是给调用他的进程发送指定信号不需要传参。也就是六号信号我们将它修改默认方法看看结果 abort();signal(6,myhandle); abort();大家发现我们修改了六号的默认处理方式之后doily abort之后确实是使用了我们自定义的处理方式但是最后还出现一个abort函数单独调用的结果也显示出来了这是因为abort函数做了特殊处理。 结论 上面三个函数每一种发送信号的方式都展示出来了给任意进程发任意信号给唯一进程发送任意信号给唯一进程发送唯一信号。后面两个都本质都是调用了第一个函数去实现的。博主也通过第一个函数来给大家写一个我们自己在命令行上的mykill程序。 mykill.cc #includeiostream #include unistd.h #include sys/types.h #include signal.h #includestring #includestdlib.h using namespace std; void UseKillPage(string name)//使用文档 {coutkill page:endl;cout\t -signal pidnameendl; } int main(int argc,char*argv[]) {if(argc!3){UseKillPage(argv[0]);return 0;}//获取命令行参数string argv1argv[1];argv1.erase(0,1);//去掉-这个字符int signalstoi(argv1.c_str());pid_t pidstoi(argv[2]);int nkill(pid,signal);if(n-1){perror(kill);return 0;}return 0; }test.cc #includeiostream #include unistd.h #include sys/types.h #include signal.h #includestdlib.husing namespace std; void myhandle(int signal) {couti get a signal:signalendl; } int main() {signal(2,myhandle);while(1){cout I am a process, pid: getpid() endl;sleep(1);}return 0; }我们使用自己写的mykill也完成了对其他进程进行发送信号的功能大家下来可以将自己的mykill放到PATH下程序名改成kill那么就和系统的kill使用一样了大家下来去试试。 2.2硬件异常和软件条件 这两个产生信号非常的相似但是又有点不一样但是共同点都是代码出现了问题是os收到软硬件的问题给进程发信号不是os主动的行为其次我们的硬件异常产生问题是博主重点讲解的让大家可以更好的认识信号 2.2.1硬件异常 第四种方式 在C或者java等一些语言都说过异常但是今天的硬件异常和之前的没有关系硬件异常被硬件以某种方式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释 为SIGFPE信号发送给进程。再比如当前进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程大家应该明白原来是这样发生硬件异常的但是计算机具体是怎么完成这一系列的操作的这个博主后看画图给大家进行讲解。 先来看看硬件异常的现象 1除0异常 #includeiostream #includeunistd.h using namespace std;int main() {coutdiv beforeendl;sleep(2);int a 10;a/0;coutaendl;coutdiv afterendl;return 0; }我们看到遇到除0之后我们看不到div after这个代码了而且我们看到的是浮点数错误看名字对应的应该是8号信号他的默认处理动作是退出程序但是你怎么保证就是8号信号我们换成默认动作看看还是之前的自定义函数myhandle 我们看到我们的除0确实是收到了8号信号并且收到异常我们的程序还没有退出原因是我们修改了默认处理动作被自定义捕捉了。我们通过man 7 signal也看出来他的默认动作是退出程序。 问题我们的程序没有写循环我程序出现异常了你os发一次信号不就行了为什么一直发信号一会回答- 2野指针异常 按照上面的方式来验证一下野指针异常 int main() {//signal(8,myhandle);coutptr beforeendl;sleep(2);int *pNULL;*p100;coutptr afterendl;return 0; }这是段错误收到的是11号信号来验证确实收到了11号信号 我们发现和除0错误的效果是一样的程序确实收到11号信号而且修改了自定义捕捉进程就不退出了我们的11号信号默认的处理动作也是退出程序 问题我们的程序和上面一样怎么也是循环收到信号一会解决。 总结:通过上面的两个案例说明我们的硬件异常会收到信号也就是硬件异常让信号产生了同样硬件异常不一定会使进程退出因为会捕捉但是硬件的异常你没有办法去修改就算不退出也没有意义因为程序已经出错了在跑下去的结果也不敢保证是正确的所以自定义动作的时候处理完自己的后序工作也让进程退出吧那为什么设计者可以让这个两个信号的默认动作可以被修改呢像9号或者19号哪些不被修改不就行了原因来看下一章节 通过硬件的来分析上面出现的问题的原因来看图解 通过上面的图大家应该知道我们的除0和野指针为什么叫硬件异常因为这个两个操作都会有对应的硬件而这些硬件在出产的时候就设定好了不按照他的开发手册就会出现异常不止这个两个硬件会发生异常还是好多硬件都会比如磁盘网卡等 解决问题 为什么我们之前上面的两个例子在修改默认处理动作的时候会死循环的发送信号原因是我们的pc指针指向除0代码的时候会发送一次信号此时下面的代码就没有执行的必要所以pc指针会一直指向这段除0的代码你的进程一直没有退出所以就会被cpu不断的拿上拿下所以状态寄存器里面一直会是溢出的os说我们发送一次信号说出现异常了你还不退出我又修改不了这个异常你每次都会造成硬件出现问题所以我只能一直给你发送信号野指针循环打印也是这样cpu通过虚拟地址访问其实也就是pc指针指向了野指针那一块代码后面的分析和前面的一样。 硬件异常的出现实际不是让你解决这个异常的你也没有办法解决他只是想让你处理一下后序工作比如打日志数据保存大部分进程出现硬件异常都是直接退出如果os让所有进程遇到硬件异常直接退出那么我们的后序工作都做不了了1所以已经出异常但是我们的异常代码还是可以执行的比如上面的例子我们遇到除0异常还是可以执行默认或者自定义的处理动作这不就是信号的产生我们没有立即处理吗因为要给用户最大的宽容度万一进程出现异常直接给进程干掉又重要数据的话os就要担责所以出异常了就给进程发送一个信号你自己自定义处理动作但是我也给你一个默认动作这也是硬件异常信号的处理动作可以被修改的原因。看到这里大家在回头来看我们上面说的例子为什么博主要这样的设计案例给大家展示这样可以将我们的知识展开讲由不懂到懂的过程在回头理解这也是我们学习底层原理必须要做的所以一般上面看现象看完现象看本质。 我猜大家应该还有疑惑这里博主就给大家一起解决了我们的os是怎么知道寄存器里面由数据大家不要忘记了os也是硬件的管理者由对应的描述硬件的结构体我们有对应读取硬件的方法一旦数据有变化或者不对时os里面就可以知道就发送对应的信号给进程那么进程是怎么收到信号准确来说是进程的task_struct怎么收到信号的大家还记得一开始博主就说过信号是内置在进程里面的os直接修改进程结构体里面关于信号的属性不就可以了大家还记得博主一开始说过的我们这篇就讲解31个信号吗1-31我们的tast_struct里面肯定有一个属性是int signal类似的这31个信号数字很特殊刚还就对应我们整型的每一个比特位就是位图的结构0号位置不同用来做标记位其余的刚好31位对应每一个信号但是这是针对普通信号 通过上面位图得出三大结论 比特位的内容是0还是1表明是否收到信号比特位的位置表示信号的编号所谓的“发信号”本质就是os去修改task_struct的信号位图对应的比特位写信好比较准确。 这个结论会在信号的保存这一章节去通过内核带大家演示的这个目前先知道他是通过位图的方式去保存的具体在内核是什么样方式去保存的一会再说。 2.2.2软件条件 第五种方式 这个也是程序出现的问题大家还记得博主在讲解匿名管道的时候第四种情况写端打开读端关闭此时是写端的进程就会被杀掉因为写已经没有意义了此时就会收到我们对应的13号信号这个博主就不给大家演示了看这篇博客. 今天重点给大家讲解的是alarm函数 和SIGALRM信号 这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。打个比方,某人要小睡一觉,设定闹钟为30分钟之后响,20分钟后被人吵醒了,还想多睡一会儿,于是重新设定闹钟为15分钟之后响,“以前设定的闹钟时间还余下的时间”就是10分钟。如果seconds值为0,表示取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数 这个函数就是一个类似于闹钟的装置参数是多长时间会出发这个闹钟一般闹钟设置一次就会响一次这个函数设置一次也就触发一次来看验证 #includeiostream #includeunistd.h #includesignal.h using namespace std; int main() {int nalarm(5);while(1){couti am a proc,pidgetpid()endl;sleep(1);}return 0; }确实闹钟响了而且默认动作是退出进程他收到的是14号信号让我们来证明一下吧使用myhandle函数 确实是收到14号信号而且闹钟只会响一次那么多设置几个怎么办 大家看到结果了吧 讲解这个一是来让大家看到这个信号的产生完全使由软件条件去产生的而是博主接下来讲解一下类似于定时器的功能在我们大厂一般需要对服务器做定期检查有时候看看程序有没有出现问题接下来就简单的实现一下 #include iostream #include string #include vector #include functional #include unistd.h #include sys/types.h #include sys/wait.h #include signal.h #include stdlib.husing namespace std;typedef functionvoid () func; vectorfunc callbacks;uint64_t count 0;void showCount() {// cout 进程捕捉到了一个信号正在处理中 signum Pid: getpid() endl;cout final count : count endl; } void showLog() {cout 这个是日志功能 endl; } void logUser() {if(fork() 0){execl(/usr/bin/who, who, nullptr);exit(1);}wait(nullptr); }// 定时器功能 // sig: void catchSig(int signum) {for(auto f : callbacks){f();}alarm(1); }int main(int argc, char *argv[]) {signal(SIGALRM, catchSig);alarm(1); callbacks.push_back(showCount);callbacks.push_back(showLog);callbacks.push_back(logUser);while(true) count;return 0; }2.3 core dump 大家还记得我们在进程等待的时候有一幅图吗 当时在讲解这个图的时候有一个比特位叫core dump没有讲解今天我们就可以把他拿出来讲了 在2.2.1章节我们使用man 7 signal手的去查看信号的默认动作 圈中都是信号默认处理动作都是退出进程那么为什么有trem和core这两个有什么区别呢我们以2号和8号为例给大家演示效果肯定是coredump有关系我们使用进程等待就core dump也打印出来看看 #includeiostream #includeunistd.h #includesys/types.h #includesys/wait.h #includestdlib.h #includestdio.h #includestring.h using namespace std; int main() { pid_t idfork();if(id0){int cnt50;while(cnt){coutchild proc pid:getpid()endl;cnt--;sleep(1);if(cnt50){break;}}exit(0);}int status;pid_t retwaitpid(id,status,0);if(retid){coutchild wait suceeful:codeexit:((status8)0xff) signal:(status0x7f) coredump:((status7)1)endl;}return 0; }我们发现上面两个没有啥区别啊coredump都是0这是为什么原因是 我们的云服务默认的core dump是关闭的虚拟机默认应该是打开的 我们使用ulimit -a来查看 我们使用下面的命令给他打开ulimit -c 10240这个大小随便给 我们再来测试一下上面的代码 2号 8号 此时我们就看出来两者的区别了有core功能的退出不止是单纯的退出而且 会把错误信息用文件的形式从内存中带到当前进程的目录下这也叫核心转储我们没有办法直接打开查看要配合gdb使用core-file去加载刚才的文件 我们将刚才的代码写的简单一点 int main() {int a10;a/0;return 0; }他可以帮助我们定位到错误程序越大这个好处越明显coredump的具体含义就是我们的程序在退出收到默认是core的处理动作是是否生成core文件。 为什么云服务默认是关闭的虚拟机默认是打开的原因是云服务器是生产工具他可能有一个小bug在有一天发生了一直开机重启那么就导致我们的一直生成core文件这样问题最后就不是这个给小bug引起的而是由于磁盘满了引起了通过上面的图我们也可以发现这个文件内容不多但是大小挺大的所以一般生产工具就默认关闭的而虚拟机是个人工具没有关系。 结论通过上面的所有方式我们实现了信号的产生可以说有信号在让我们的进程看到一些场景就可以自己去处理不需要用户去搞1就好比我们已经长大了知道红绿灯了不像小时候还需要家里人带而且我们也提到发信号是怎么做到的通过os去修改进程里面的位图属性的比特位就可以了接下来我们将要讲解的时候信号的保存来看下节 三、信号的保存 通过上卖弄的学习相信大家对信号已经不在那么陌生了我们通过修改位图属性来给进程写信号那么既然有东西属性让os去写这也侧面说明信号要被保存下来的就好比你把明天决定不吃的饭菜放在冰箱里面保存有啥意义信号也是你放进去肯定是要保存的所以博主就要通过内核带大家来看看我们的信号是如何被保存起来的以及怎么执行处理方式的并且通过一系列的函数来带大家看到位图属性里面的内容再次来验证919号信号必须要执行默认方法跟着博主的思路走下去吧。 3.1信号的其他相关概念 实际执行信号的处理动作称为信号递达(Delivery)信号从产生到递达之间的状态,称为信号未决(Pending)。就是我们之前提到的时间窗口进程可以选择阻塞 (Block )某个信号。就是屏蔽一个信号就算收到信号也不执行处理动作被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作 相信学过前面的知识这上面五点大家应该清楚大家可以按照生活中的一个例子你讨厌一个老师不想做他作业就可以阻塞他布置的作业即使他没有布置我也选择阻塞反正他布置了我不写有一天你发现这个老师还不错你解除对他布置作业的阻塞去完成作业下次布置作业你在记下来去写这个过程叫做保存按照这个例子对比上面的五点去理解会简单很多。 3.2在内核中表示 我们的进程内部其实有三张表第一张表就是表示信号有没有被阻塞第二个表就是表示收到那个信号第三个就是要执行的方法前两个表都是位图只是用数组的形式表示出来了第三张表就是对应执行的默认方法这三张表我们要横向看 第一行我们的1号信号没有被阻塞也没有收到信号没有修改所以使用默认对应方法第二行我们的2号信号被阻塞也收到2号信号了但是被阻塞默认方法就改成了忽略第三行我们的三号信号被忽略了也没有受到信号但是修改了处理方法当收到信号也不会执行知道解除阻塞才会执行 每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图的例子 中,SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。 SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。 SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函sighandler。 如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?POSIX.1允许系统递送该信号一次或多次。Linux是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。本章不讨论实时信号 3.3sigset_t 从上图来看,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。 因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。下一节将详细介绍信号集的各种操作。 阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略 3.4信号集操作函数 这些操作函数都是对我们刚才提到的两个表做操作的忽略我们用户可以一开始就设定一下但是pending表只能由os发信号去改变不能我们一开始就设定但是我们可以一直查看pending表里面的数据接下来就是通过改变阻塞表的数据来验证收到信号会不会因为阻塞被忽略当阻塞已解除我们才会执行递达操作将上面的五点进行验证。 sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态,至于这个类型内部如何存储这些bit则依赖于系统实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_ t变量,而不应该对它的内部数据做任何解释,比如用printf直接打印sigset_t变量是没有意义的 int sigemptyset(sigset_t *set); int sigfillset(sigset_t *set); int sigaddset (sigset_t *set, int signo); int sigdelset(sigset_t *set, int signo); int sigismemberconst sigset_t *set, int signo); 函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含 任何有效信号。函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示 该信号集的有效信号包括系统支持的所有信号。 注意,在使用sigset_ t类型的变量之前,一定要调 用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号。 这四个函数都是成功返回0,出错返回-1。sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种 信号,若包含则返回1,不包含则返回0,出错返回-1。 我们的sigset-t其实也是一个类型他定义变量也是在栈上开辟的上面的五个函数的操作其实就是对这些比特位就操作属于用户层的想要给系统的表中写入就必须调用下面的两个系统函数 1sigprocmask对block表做修改 调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集) #include signal.h int sigprocmask(int how, const sigset_t *set, sigset_t *oset); 返回值:若成功则为0,若出错则为-1 第一个参数 他是起功能的作用第一个是向已经有的block表中添加新的set当中有的信号的屏蔽重复就不会有变化 第二个是取消set当中有的信号的阻塞第三个就是把block表中都设置和色图比特位一样不管之前的block表中是怎样的。 第二个参数就是传刚才操作定义的信号集变量的地址 第三个参数不管上面的那种功能都会改变旧的block表所以这是一个输出型参数就旧表的内容带出来目的是为了恢复。 2sigpending:对pending表进行读取数据 #include signal.h int sigpending(sigset_t *set); 读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1。 3.5验证3.1节的概念 #includeiostream #includesignal.h #includeunistd.h using namespace std;void pritfpending(sigset_t *set) {for(int i31;i1;i--){if(sigismember(set,i)){cout1;}else{cout0;}}coutendl; }void myhandle(int singno) {coutcatch signalsingnopid:getpid()endl; } int main() {signal(2,myhandle);sigset_t set;//定义一个sigemptyset(set);//将二号信号进行屏蔽并且写到block表中sigaddset(set, SIGINT);sigprocmask(SIG_SETMASK, set, NULL);int cnt0;while(1){int nsigpending(set);//读取pending表if(n0)continue;pritfpending(set);//打印pending表sleep(1);if(cnt5){kill(getpid(),2);cout我收到了二号信号但是被阻塞不执行自定义动作,并且pending表的倒数第二位变成1endl;}cnt;if(cnt10){cout unblock 2 signo,我将递达处理二号信号处理完我的pending表的倒数第二位变成0 endl;sigprocmask(SIG_UNBLOCK, set, NULL);//10秒后解除阻塞再次执行递达动作因为默认是退出看不到结果所以进行捕捉一下cout五秒后我将再回收到一个2号信号endl;}if(cnt15){cout我收到了二号信号但是没有被阻塞执行自定义动作endl;kill(getpid(),2);}}return 0; }接下里带大家来看看在自定义哪里我们的9号和19号不能被捕捉那么在阻塞这里我们看看能不能被阻塞我们将set的所有位置都设置为1那么就以为这所有的信号都被屏蔽了在给程序发送信号。来看案例每次运行改变cnt的初始值 int main() {signal(2,myhandle);sigset_t set;//定义一个//sigemptyset(set);sigfillset(set);//将二号信号进行屏蔽并且写到block表中//sigaddset(set, SIGINT);sigprocmask(SIG_SETMASK, set, NULL);int cnt1;while(1){kill(getpid(),cnt);coutpid:getpid()第cnt号信号可以被阻塞endl;int nsigpending(set);//读取pending表if(n0)continue;pritfpending(set);//打印pending表sleep(1);cnt;}return 0;} os在自定义捕捉哪里都开始防着你了在这不也防着你。 总结相信讲到这里大家对于信号的保存应该理解了还希望大家下来可以自己去测试一样。这个函数没有共享内存哪个函数复杂这个函数理解了博主话的两份图就理解不要把两个结构当成一个就行了接下来博主将带大家认识信号的处理 四、信号的处理 4.1信号处理的流程 讲解上面的知识之后我猜想同学对于信号的处理可能想不出来什么问题就是执行我们的处理动作就行了啊但是博主想讲的不只于此我们之前说过信号的处理不一定立即处理要在合适的时候那么这个合适的时候到底是什么时候当我们的进程从内核态返回到用户态的时候会进行信号的检测和处理系统调用是用户的代码去调用的需要os自动的会做身份切换从用户态切换到内核态或者反着来int80 这个汇编指令。接下来博主给大家来解释上面说的我们要重谈进程地址空间来看图解 我们通过上面的图大约知道用户态和内核态的作用了那让我们一起看看信号的处理是什么样的 ![在这里插入图片描 我们的信号收到默认和忽略的处理动作的时候到第三步就处理完毕然后退回内核态返回给用户态但是遇到自定义函数就麻烦了我们到第三步的时候发现是自定义函数要通过用户态来调用那么为什么os自己不调用按道理是可以的但是os怕用户在这个函数里面做手脚获取os的信息就不好的所以不敢使用os的权限所以就需要从内核态返回用户态去执行处理完还要再一次回到内核态到第五步因为我们的函数执行完需要把修改一些关于信号的一些其他属性收尾工作 这时候就需要使用内核态去操作然后最后再回到用户态记忆图 上面讲述完信号的处理流程大家要注意的是我们的用户态和内核态的关系它两者是在进程运行期间需要频繁切换的用户态和内核态和执行各的代码虽然内核有权限执行用户的代码但是它不愿意这样做这个大家要明白。而且pending位图的1是什么时候变成0的答案是信号处理之前这个等会大家验证一下。我们先来可能看下一个知识点。 4.2 信号捕捉的另一个函数sigaction 这也是一个改变信号处理动作的函数功能比signal更强大,让我们一起通过官方文档来看看这个函数是怎么去使用的吧 sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回- 1。signo是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非 空,则通过oact传出该信号原来的处理动作。act和oact指向sigaction结构体将sa_handler赋值为常数SIG_IGN传给sigaction表示忽略信号,赋值为常数SIG_DFL表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册了一个信号处理函 数,该函数返回值为void,可以带一个int参数,通过参数可以得知当前信号的编号,这样就可以用同一个函数处理多种信号。显然,这也是一个回调函数,不是被main函数调用,而是被系统所调用。 第一个参数传入的是我们的信号 第二个参数是一个结构体里面有一些属性我们只看圈住的没圈住的是实时信号应该关注的。这个结构体里面存放的是关系我们信号的处理方法等 第三个参数是一个输出型参数和sigprocmask类似这是返回一个修改之前的结构。 1有两个属性我们要研究我们先看第一个属性第二个一会在说 #includeiostream #includesignal.h #includeunistd.husing namespace std;void my_handler(int signo) {coutcatch signal signoendl; } int main() {struct sigaction act,oact;memset(act, 0, sizeof(act));memset(oact, 0, sizeof(oact));//初始化act.sa_handler my_handler;//将结构体里面的方法属性给修改成自定义函数sigaction(2,act,oact);while(true){couti am a proc:getpid()endl;sleep(1);}return 0; }我们看到这个函数的效果和signal是没有什么区别。来看第二个属性 2第二个属性 大家有没有想过当我们的进程收到一个信号来了被自定义捕捉了那么此时他在执行自定义函数的时候又来了一个同样的信号过来会怎么样在4.1节我们说过内核为什么不想帮助用户u1执行代码还要费尽心思返回用户态去执行原因是怕用户在自定义函数有访问内核的操作不安全所以在自定义函数里面我们的信号正在执行又来了一个同样的信号此时自定义函数里面万一有调用系统函数的接口此时就要进入内核态那么就又会对来了同样的信号进行检测和处理而之前的进程还没有执行完毕呢这样就套娃了os为了防止这种事情的发生当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么 它会被阻塞到当前处理结束为止。 如果 在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字 我们先来验证是否执行一个信号时又来了一个同样的信号被屏蔽了 #includeiostream #includesignal.h #includeunistd.h #includestring.husing namespace std; void printpending() {sigset_t set;sigpending(set);for(int i31;i1;i--){if(sigismember(set,i)){cout1;}else{cout0;}}coutendl; } void my_handler(int signo) {coutcatch signal signoendl;while(1){printpending();sleep(1);}} int main() {struct sigaction act,oact;memset(act, 0, sizeof(act));memset(oact, 0, sizeof(oact));act.sa_handler my_handler;//将结构体里面的方法属性给修改成自定义函数sigaction(2,act,oact);while(true){couti am a proc:getpid()endl;sleep(1);}return 0; }我们通过打印pending表发现来了同样的信号没有被处理因为pending位图会在信号递达的时候置成0所以通过pending位图来间接反映block位图被屏蔽。 想要额外在屏蔽其他信号的来临怎么办使用sa_mask,它是一个sigset_t类型的所以要使用我们信号集操作函数去改变它的比特位我们来看案例 #includeiostream #includesignal.h #includeunistd.h #includestring.husing namespace std; void printpending() {sigset_t set;sigpending(set);for(int i31;i1;i--){if(sigismember(set,i)){cout1;}else{cout0;}}coutendl; } void my_handler(int signo) {coutcatch signal signoendl;while(1){printpending();sleep(1);}} int main() {struct sigaction act,oact;memset(act, 0, sizeof(act));memset(oact, 0, sizeof(oact));sigemptyset(act.sa_mask);sigaddset(act.sa_mask,3);//屏蔽其他信号sigaddset(act.sa_mask,4);sigaddset(act.sa_mask,5);act.sa_handler my_handler;//将结构体里面的方法属性给修改成自定义函数sigaction(2,act,oact);while(true){couti am a proc:getpid()endl;sleep(1);}return 0; }我们想要达到的效果是在执行二号信号的时候把我们345号信号也给屏蔽我将对比来验证 通过结果我们看到使用sa_mask可以在我们处理一个信号的时候也屏蔽其他信号的让每一次只执行一个信号的处理动作 总结 上面就是我们说的信号处理的所有知识点希望大家下去好好理解一下对于信号的处理博主就讲解到这里博主将会在下一篇博客中给大家在画一个详细的图来好好理解一下信号的处理还有几个小知识点都放在下一篇博客希望大家过来支持 五、总结 博主画了小两周的时间写了快三万字讲解了整个信号的过程里面有的只是是关于硬件的补充所有对大家来说有点陌生但是硬件不会我们学习软件要完全掌握的大家了解就好了在想研究的深点可能就要读者自己下来去查找资料信号这一节也我们更充分的认识了进程他是怎么做到对信号的操作解决了我们之前的一些疑问如果读者还有什么问题在评论区告诉我你的问题。我会给大家解答的话不多说了我们下篇进行信号的小知识点补充以及线程的前期补充知识点讲解。 ⏰
http://www.zqtcl.cn/news/614262/

相关文章:

  • 网站开发和网站运营的区别嘉兴市秀洲区住房和建设局网站
  • 西安网站开发公司哪家强如何做付费阅读网站
  • ios认证 东莞网站建设天津企业网站建设方案
  • 高网站排名吗wordpress 拼音别名
  • 网站出现的问题杭州旅游网站建设
  • 陕西城乡建设部网站怎么用自己注册的域名做网站
  • 企业邮箱注册价格汕头做网站优化的公司
  • 高校工会网站建设网站静态页面生成
  • 辽宁省营商环境建设局 网站做网站前端后端ui什么意思
  • 合作社网站模板贵州安顺建设主管部门网站
  • 网站不备案能访问吗哪家做企业网站
  • 做网站写的代号好跟不好的区别企信网企业信用信息系统
  • 网站需要服务器吗手机网站解决方案
  • 网站子网页怎么做国外网站 模板
  • 手机评测网站标志设计分析
  • 网页游戏网站建设成都公司网站
  • 网站流量统计分析的误区wordpress二级目录安装
  • 深互动平台网站wordpress后台无法访问
  • 建立网站需要服务器吗网站建设辶首先金手指十四
  • 做的成功的地方网站办公室工装设计公司
  • 怎样添加网站上百度商桥代码网站建设实验报告手写
  • 江阴做网站优化辽宁世纪兴电子商务服务中心
  • 最新创建的网站搭建网站的平台有哪些
  • 全国房地产网站企管宝app下载
  • 无线网络网站dns解析失败南通模板建站多少钱
  • h5手机网站建设哪家好北京海淀房管局网站
  • 制作一个简单的网站冬奥会网页设计代码
  • 如何做网站 百度西充建设部门投诉网站
  • 怎么创建自己的博客网站网站优化主要内容
  • 太原网站建设推广建设网站观澜