php 大型网站开发教程,网店设计风格有哪些,软件开发行业发展前景,公司建网站需要多少钱信号的概念与相关知识认识
信号是向目标进程发送消息通知的的一种机制。 信号可以以异步的方式发送给进程#xff0c;也就是说#xff0c;进程无需主动等待#xff0c;而是在任何时间都可以接收到信号。
信号的种类
用kill-l命令查看系统定义的信号列表#xff1a; 前台…
信号的概念与相关知识认识
信号是向目标进程发送消息通知的的一种机制。 信号可以以异步的方式发送给进程也就是说进程无需主动等待而是在任何时间都可以接收到信号。
信号的种类
用kill-l命令查看系统定义的信号列表 前台进程和后台进程的认识
我们首先要知道打开xshell软件时默认就已经执行了shell进程而这就是前台进程而且前台进程只有一个。当我们运行一个我们写的死循环进程时
#include iostream
#include unistd.h
using namespace std;int main()
{while(1){cout haha endl;sleep(1);}return 0;
} 其实此时该进程就自动变成了前台进程而操作系统会自动的将shell切换成后台进程。而且此时我们呢无论输入什么指令都不会被执行因为我们所输入的指令默认都会被前台进程接收。但是我们可以通过kill -9 进程pid和Ctrl c来终止此前台进程终止以后操作系统还会自动的将shell设置为前台进程。记住Ctrl c并不能终止前台进程shell。 如何生成后台进程
如果我们想要将进程执行成后台进程的话可以在运行进程的后面加上符号
而对于这两个数字对应的分别就是后台进程编号和该后台进程pid。 而且我们可以同时可以执行多个后台进程。 查看后台进程
通过jobs指令查看后台进程 后台进程与前台进程的转换
通过fgfront ground后台进程编号将后台进程变成前台进程 通过Ctrl z指令和bgback ground暂停进程编号将前台进程变成后台进程 我们可以通过Ctrl z将前台进程暂停但是我们的前台必须要有进程执行否则系统就会挂掉了此时操作系统会默认将shell设置为前台进程因此原先的前台进程就被暂停在了后台中。 操作系统是怎么知道外设有数据的
操作系统通常通过中断机制来确定外设是否有数据可用。中断是计算机系统中的一种重要机制它是指在计算机执行某个任务时由于发生了某种特殊事件或接收信号硬件或软件会中断当前任务的执行转而去处理需要优先处理的事件或任务。中断可以打破程序的顺序执行提供了一种异步随时处理的方式。而且中断处理程序执行结束后处理器会恢复之前保存的上下文继续原来的任务执行。
我们知道CPU包括运算器和控制器的对于控制器其实就是其他设备的控制的因为是要控制信息所以也会和外设相交互。CPU在硬件上是和内存关联而自身会提供一个个从0开始编号的针脚。针脚是和主板直接相连而主板上插入着很多的硬件电路相当于USB口。而这些硬件电路就和我们的外设直接关联着。 当我们的外设在使用的时候此时对应的针脚就会接收外设传来的高电频从而点亮针脚只有能进行IO的外设才能向CPU发消息的。而我们的每个针脚都有对应的编号中断号所以此时的编号就会被写到寄存器里就可以被操作系统读取。所以我们的外设有数据的时候本质上就是通过发送中断号的方式来判断。
而我们中断号的作用其实就是调用对应硬件的方法的。在操作系统内部提供了一张中断表该表是一个函数指针数组数组下标对应的就是中断号0号是正常运行其中存放的就是特定硬件的读取方法。 信号的产生
键盘产生
信号的产生其实就是通过键盘产生。当我们运行一个死循环程序时按住Ctrl c其实就是发送2号信号组合键时就可以中断进程。我们一般在shell上输入的指令都会放到进程的缓冲区中进行读取而当我们输入Ctrl c组合键并没有将数据输入给进程而是直接转换成某种动作向进程发送信号的方式。而进程接收信号并处理从而终止进程。
我们操作系统当中每一个进程都会维护一张处理信号的方法表其实就是函数指针数组该数组中的每一个下标对应的数据内容都是信号的处理方法。 认识signal函数
该函数的返回值是一个参数为int的函数指针 。而参数一是信号编号参数二的类型和函数返回值类型一样是一个函数指针。其实该函数的功能就是重置自定义进程接收信号后的反应机制也就是重置信号处理的函数指针数组里的方法。但是一些信号如9号信号不能被自定义捕捉修改。
void handler(int signo)
{cout 进程收到了 signo 信号 endl;exit(1);
}
int main()
{signal(2, handler); // 自定义捕捉2号信号while (1){cout 死循环 endl;sleep(1);}return 0;
}信号的本质 信号实质是操作系统进程的管理者向目标进程发送的进程也需要对信号进行管理。而底层其实就是信息的输入与输出操作系统向对应的进程发送信号编号然后进程PCB中存在中管理信号的结构体其结构体内部会存在一张位图而每一位都对应着各个编号的信号如果比特位内容为1就表明收到该位置对应的信号。
所以一个进程含有信号的函数指针数组和信号位图。 系统调用产生
通过kill函数对指定进程发送信号 该函数就是我们shell命令行中的kill指令所以我们的实现使用就可以通过命令行参数
//kill模拟实现
//kill -9 pid
int main(int argc,char* argv[])
{if(argc!3){cout输入方式错误endl;coutplease input argv[0] signum process_idendl;return -1;}int signum stoi(argv[1]1);//去掉-字符int process_id stoi(argv[2]);kill(process_id,signum);return 0;
} 通过raise函数对当前进程发送信号
void handler(int signo)
{cout向当前进程发送信号signoendl;
}
int main()
{signal(2,handler);//重置当前进程的信号处理表中的2号方法while(1){raise(2);sleep(1);}return 0;
} 通过abort函数对当前进程发生终止信号
void handler(int signo)
{cout向当前进程发送信号signoendl;
}
int main()
{signal(6,handler);//重置当前进程的信号处理表中的6号方法abort();while(1){cout运行中...endl;}return 0;
} 代码编译异常产生信号
除0异常
int main()
{int a10;int reta/0;//0不能作为除数return 0;
}
我们知道CPU包含运算器和控制器。所以CPU会存在很多的寄存器会临时存储调度进程中需要进行运算的数据。而CPU中还存在着一种状态寄存器status状态寄存器中有很多的标志位很多标志位是用来衡量运算的结果状态的。而溢出标记为也是其中。当我们的操作系统调度进程时将改进程放到运行队列中如果CPU的运算器发现除数为0时状态寄存器中的溢出标志位就被置为1。而此时寄存器中的标志位出问题时CPU中的内部模块或针脚就会向操作系统通知而操作系统就会根据针脚为下标的函数指针数组表的内容执行对应的中断方法。操作系统接下来就会将溢出标志位解释为kill目标进程信号的方式向目标进程发信号。 这对应的恰好就是信号表中的8号信号。 更改8号信号处理方式
void handler(int signo)
{cout进程错误信号signoendl;sleep(1);
}
int main()
{signal(8,handler);//自定义8号信号处理方法int a10;a/0;//0不能作为除数return 0;
} 现象与解释 我们发现运行可执行程序以后就开始间隔一秒的死循环输出错误信息而并没有退出。
对此现象我们要知道寄存器硬件是属于CPU的而寄存器里的内容是属于当前调度的进程的寄存器≠寄存器里的内容。
当正常情况下我们除0错误时状态寄存器的对应标志位会置为1所以此时就会向操作系统发送错误信号最终终止进程而其他进程被CPU调度运行时对应的寄存器数据就会被覆盖改写所以寄存器也就供当前进程使用了。
而对于以上情况时我们进行了自定义的信号处理方式所以我们的状态寄存器发现了除0错误以后就会向操作系统发送信号操作系统就会解释为8号信号发送给进程而进程会通过调用handler函数来处理8号错误信号。而该进程并没有退出且CPU一直在调度该进程但是异常是一直存在的所以CPU就会一直向操作系统发送信号 。
CPU发现异常会通过操作系统向进程发送信号而进程也会对信号进行处理但是进程没有退出CPU中标记进程执行位置的寄存器并不会往下调度。 软件条件产生信号
#include unistd.h
unsigned int alarm(unsigned int seconds);
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM14号信号, 该信号的默认处理动作是终止当前进程。
闹钟的设置使用
int n0;
void handler(int signo)
{n alarm(0);//参数等于0表示取消之前设置的闹钟返回值n还是之前闹钟的剩余时间//参数大于0表示重设闹钟coutalarm result : nendl;exit(1);
}
int main()
{coutmypid getpid()endl;signal(14,handler);alarm(20);//返回值是闹钟剩余的时间,一般都是0while(1){}return 0;
} 信号的保存
我们操作系统向进程传输信号的时候进程一般可能并不会立即处理会再合适的时候处理。
信号常见概念 实际执行信号的处理动作称为信号递达(Delivery)信号从产生到递达之间的状态,称为信号未决(Pending)。进程可以选择阻塞 (Block )某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作 信号递达
信号递达分为信号的忽略信号接收以后取消响应、信号的默认无论之前信号应答方式如何一旦默认就恢复最初的处理方式、信号的自定义捕捉我们自己实现handler方法。而这些递达方式均可以通过signal函数进行实现。 void handler(int signo)
{cout信号自定义捕捉处理 signoendl;//exit(1);
}
int main()
{coutmypid getpid()endl;signal(2,handler);sleep(10);cout默认处理endl;//signal(2,SIG_IGN);//忽略信号处理signal(2,SIG_DFL);//默认信号处理while(1){sleep(1);}return 0;
}信号未决
信号未决顾名思义就是进程收到信号但还未对信号做出决策。也就是我们的操作系统向进程信号后将信号标识到进程PCB中的信号位图中。
信号阻塞
信号被阻塞就是指当前进程不对所接收的信号做任何处理但是依旧记录着该信号。也就是说明该信号在未处理时一直处于未决阶段不递达。
而递达中的信号忽略并不是阻塞因为忽略其实是递达的一种也就是已经对信号做了处理而阻塞是还没做处理。 信号在内核中的表示 内核中会存在数据结构为其进程维护这三张表分别是block表该位图表标识着该位置处信号是否阻塞和屏蔽pending表和block表的结构一样其实就是未决表标识该进程是否收到该信号handler表这是一张函数指针数组表其中的数组小标对应着信号的编号其中的内容就是对应信号的处理方法。 对信号集的操作
我们的handler表可以通过我们实现的函数进行传参从而对该表的内容进行修改。对于阻塞位图表与未决位图表我们也有对应的函数进行修改。
既然要设置信号集而信号集的结构又是位图结构所以我们肯定是要用到该结构的对象然后通过系统调用函数将该信号集对象设置到进程的内核中。
sigset_t类型
sigset_t是一种数据类型其实底层也就是我们的位图结构0对应的就是无该位置对应的信号1对应的就是有。而且提供了相关函数将该类型的数据进行处理。
int sigemptyset(sigset_t *set);//初始化set所指向的信号集,表示该信号集不包含任何有效信号
int sigfillset(sigset_t *set);//初始化表示该信号集包含所有系统支持的有效信号
int sigaddset (sigset_t *set, int signo);//添加信号signo
int sigdelset(sigset_t *set, int signo);//删除信号signo
int sigismemberconst sigset_t *set, int signo);//判断是否存在信号signo
在使用sigset_ t类型的变量之前,一定要调 用sigemptyset或sigfillset做初始化,使信号集处于确定的 状态。 更改屏蔽字阻塞信号集sigprocmask
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功则为0,若出错则为-1
假设当前信号屏蔽字为mask
how说明 SIG_BLOCK set包含了我们希望阻塞的附加信号相当 maskmask|set SIG_UNBLOCK set包含了我希望解除阻塞的信号相当于 maskmask(~set) SIG_SETMASK 设置当前信号屏蔽字为set所指向的值相当于 maskset
对于参数二和三如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。
如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。
如果oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字
使用该函数
//sigprocmask信号屏蔽void handler(int signo)
{cout当前收到signo号信号endl;
}
int main()
{coutmypid getpid()endl;sigset_t set,oset;//创建临时位图block表signal(2,handler);//自定义二号信号的方法//清空临时信号集sigemptyset(set);sigemptyset(oset);//设置信号集到临时变量sigaddset(set,2);sigprocmask(SIG_SETMASK,set,oset);//将信号集设置到该进程中while(1){}return 0;
} 当我们发送二号信号时该进程是屏蔽此信号的也就是并没有做出任何的反应。
而且我们的9号信号是不会被屏蔽的。 获取未决信号集sigpending
其实对于pending表我们之前已经了解可以通过键盘上的组合键等方式来设置pending表所以我们现在想知道的就是pending表中的内容。
sigpending(sigset_t *set)
读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1
函数使用
//sigpending获取未决信号集void handler(int signo)
{cout当前收到signo号信号endl;
}
void print(const sigset_t tmp)
{for(int i1;i32;i){if(sigismember(tmp,i))cout1;elsecout0;}coutendl;
}
int main()
{signal(2,handler);//自定义//屏蔽2号信号coutmypid getpid()endl;sigset_t set,tmp,oset;sigemptyset(set);sigemptyset(tmp);sigemptyset(oset);sigaddset(set,2);sigprocmask(SIG_BLOCK,set,oset);//将信号集添加到该进程中int count0;while(1){sigpending(tmp);print(tmp);//取消屏蔽信号处理if(count3){cout解除2号信号的屏蔽状态endl;sigprocmask(SIG_UNBLOCK,set,oset);//解除屏蔽后进程立马就递达了}sleep(1);count;}return 0;
} 对于非屏蔽的信号也就是在pending集中处于未决状态当我们发送某一个信号时该pending表的对应信号位图数据瞬间由1置为0在执行handler方法之前。 信号的处理
认识进程内核态和用户态
我们对于信号的处理需要先了解进程状态存在内核态和用户态。我们进程默认都是用户态的也是一种受控的状态访问的资源都是有限的。内核态是一种操作系统的工作状态能够访问大部分系统资源如系统调用函数。 我们的进程地址空间的0到3GB都属于用户空间3到4GB属于内核空间。而对应的就分别是用户态和内核态。当我们进程在启动的时候内存中的操作系统会首先被映射到内核空间的而这之中虚拟和物理地址之间通过内核级页表进行转换。所以当我们在调用系统函数的时候就可以在地址空间从正文代码中去访问内核空间里的数据。但是此时是需要将进程从用户态切换到内核态。
我们CPU是实现对进程的调度的而且CPU中存在很多的寄存器。对于进程的用户态还是内核态CPU会通过一个CS寄存器中两个比特位数据进行判定01表示内核11表示用户。所以对应状态切换就是改变CS寄存器中的数据。同时进行页表也同样保存在寄存器中的切换。
信号捕捉处理
我们知道信号的处理是要在内核中进行的因为我们内核中有关于信号的三张表block、pending、handler。所以我们信号处理实际就是在进程从内核态返回到用户态的时候。
进程在执行接收的信号时首先回去切换到内核态去遍历三张表判断是否接受信号是否阻塞是否自定义了handler方法。如果没有自定义handler方法就是直接在内核中进行执行。但是如果自定义了handler方法的话就需要对信号方法进行捕捉处理。此时进程时时需要返回到用户态去遍历handler中的代码的如果不转换成用户态而是保持内核态去访问handler方法的话可能会造成非法资源的访问有危险当执行完handler方法之后依旧是要切换回来的 而切换回来是因为通过了特殊的系统调用执行了sigreturn返回到内核。此时信号处理完毕最终进程返回到用户态。 而且对于进程在运行期间是会存在很多次的进程切换的。就例如代码执行死循环然后通过Ctrl c命令直接终止进程的过程。此时进程运行被CPU调度但是每个进程都有时间片所以在CPU执行的过程中时钟中断会不断进行检测如果该进程时间片到了操作系统就会终止此进程然后将该进程从CPU中剥离此时进程上下文等数据都会保存到进程PCB中好在下次调度是直接将数据拷贝到寄存器中在运行队列重新排队。此时CPU会执行Ctrl c命令切换到内核态执行该命令终止进程。 sigaction读取和修改与指定信号相关联的处理动作
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);struct sigaction
{ void (*sa_handler)(int);void (*sa_sigaction)(int, siginfo_t *, void *);sigset_t sa_mask;int sa_flags;void (*sa_restorer)(void);
}; 我们主要了解一下第二个参数sigaction类型其中该类型是一个结构体其中主要认识一下sa_handler字段和sa_mask字段。
对于sigaction结构体sa_handler的使用就相当于是signal函数对信号自定义捕捉
void handler(int signo)
{cout recieve a signal: signoendl;
}
int main()
{struct sigaction act,oact;act.sa_handlerhandler;sigaction(2,act,oact);while(1);return 0;
} 对于sigaction结构体的sa_mask的使用表明需要额外屏蔽的信号
当某个信号的处理函数被调用时还没调用完成,内核会自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时则会自动恢复原来的信号屏蔽字。这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞直到当前该信号handler方法处理完为止。
验证
void print(const sigset_t tmp)
{for(int i31;i1;i--){if(sigismember(tmp,i))cout1;elsecout0;}coutendl;
}
void handler(int signo)
{cout recieve a signal: signoendl; while(1)//执行不退出{sigset_t pending;sigpending(pending);print(pending);sleep(1);}}int main()
{coutpid getpid()endl;struct sigaction act,oact;act.sa_handlerhandler;sigaction(2,act,oact);sleep(5);return 0;
}分析当我们 第一次按住Ctrl c时会执行2号信号的自定义方法此时在执行之前就会将pending集中的2号由1置为0开始执行对应的方法。而接下来再次接收该信号时就会发生阻塞不会执行2号信号的方法而且会将pending表中的2号位置置为1. 如果在调用信号处理函数时,除了当前信号被自动屏蔽正在执行当前信号且没退出时之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。
以上代码加上同时屏蔽三四号信号
int main()
{coutpid getpid()endl;struct sigaction act,oact;act.sa_handlerhandler;sigemptyset(act.sa_mask);//初始化sigaddset(act.sa_mask,3);//同时屏蔽三四号信号sigaddset(act.sa_mask,4);sigaction(2,act,oact);sleep(10);return 0;
}当我们进程sleep(10)的过程中接受了信号的话那么就会执行信号的处理方法处理完以后就会停止继续sleep而是执行sleep之后剩余代码。 认识SIGCHLD信号
我们知道可以通过wait和waitpid函数清理僵尸进程,父进程可以阻塞等待子进程结束,也可以非阻塞地查询是否有子进程结束等待清理(也就是轮询的方式)。其实,子进程在终止时会给父进程发SIGCHLD信号,该信号的默认处理动作是忽略,父进程可以自定义SIGCHLD信号的处理函数,从而在此实现回收子进程的工作。
void handler(int sig)
{pid_t id;while( (id waitpid(-1, NULL, WNOHANG)) 0)//循环等待回收所有子进程防止多个进程同时退出形成阻塞导致不会调用handler方法。WNOHANG等待方式防止子进程一直不退出而导致父进程无法执行后续代码。{printf(wait child success: %d\n, id);}printf(child is quit! %d\n, getpid());
}
而且对于linux平台中还有一种方式父进程不用进行等待回收子进程的资源数据当子进程退出时操作系统会自动的回收子进程的资源。
signal(SIGCHLD, SIG_IGN);//忽略