Liunx进程信号
- 游戏开发
- 2025-07-21 20:42:02
Liunx中如何查看信号种类:
信号名称信号编号描述SIGHUP1终端挂起或控制进程结束SIGINT2终止进程(由键盘输入 Ctrl+C 产生)SIGQUIT3终止进程并生成核心转储文件(由键盘输入 Ctrl+\ 产生)SIGILL4非法指令SIGTRAP5跟踪陷阱SIGABRT6终止进程并生成核心转储文件(由调用 abort() 函数产生)SIGBUS7非法地址访问SIGFPE8浮点异常SIGKILL9无条件终止进程SIGUSR110用户定义的信号1SIGSEGV11无效的内存引用SIGUSR212用户定义的信号2SIGPIPE13管道破裂SIGALRM14定时器超时SIGTERM15请求终止进程(默认终止信号)SIGSTKFLT16协处理器栈错误SIGCHLD17子进程状态改变SIGCONT18继续被暂停的进程SIGSTOP19停止进程SIGTSTP20暂停进程(由键盘输入 Ctrl+Z 产生)SIGTTIN21后台进程尝试读取控制终端SIGTTOU22后台进程尝试写控制终端SIGURG23套接字的紧急情况SIGXCPU24CPU 时间限制超时SIGXFSZ25文件大小限制超过SIGVTALRM26虚拟定时器超时SIGPROF27分析计时器超时SIGWINCH28窗口大小调整SIGIO29异步 I/O 事件SIGPWR30电源故障SIGSYS31非法系统调用 前后台进程 前后台进程的概念前台进程在命令行操作时,只能有一个,后台进程可以有多个。
接下来我们启动一个后台进程。 我们创建一个后台进程,并且让他给文件不停的写入。 此时我们查看 log.txt 可以看到。进程在后台运行,那我们如何查看呢? jobs 就可以看到 如何杀掉后台进程呢? 我们需要把fg后台放到前台,然后退出。 下面附上常用命令:
快捷键描述Ctrl+C终止并退出前台进程,回到ShellCtrl+Z暂停前台命令执行,放到后台,回到Shelljobs查看当前在后台执行的命令&在后台执行命令fg N将进程号码为N的命令放到前台执行bg N将进程号码为N的命令放到后台执行 进程信号的产生 键盘产生用户在Linux下执行一个前台进程,然后使用ctrl+c操作,会直接终止掉该前台进程。这是因为用户按下ctrl+c,这个键盘输入产生一个硬件中断,被OS获取,解释成信号,发送给目标前台进程,前台进程因为收到信号,进而引起进程退出。
ctrl+c组合键只能终止掉前台进程,对后台进程无效。这时我们可以使用kill -9指令来该终止进程 crtl+c的信号本质就是2号信合 接下来我们通过一个函数验证这个事: 像进程发送2号信合,运行新程序。 查看进程退出码:echo $?
························································································································································
阻塞信号信号的常见分类:
实际执行信号的处理动作,称为信号递达(Delivery)。信号从产生到递达之间的状态,称为信号未决(pending)。进程可以选择阻塞(Block)某个信号。 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。需要注意的是,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后的一种处理动作。
在内核中的表示 未决信号,就是你的进程已经接收到了信号了,只是还没被信号处理函数处理的那些信号。
特别说明:虽然未决信号的定义是上面这样,但是这里我们需要更加具体一点,未决信号特指进程收到且被阻塞的信号。
每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图中,SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。在上图中,SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会在改变处理动作之后再接触阻塞。在上图中,SIGQUIT信号未产生,正在被阻塞,不能执行自定义函数。处理动作包括默认、忽略以及自定义。 sigset_t 根据信号在内核中的表示方法,每个信号的未决标志只有一个比特位,非0即1,如果不记录该信号产生了多少次,那么阻塞标志也只有一个比特位。sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态。
在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞。 在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。 阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask)
信号集操作函数 sigset_t类型对于每种信号用一个bit表示“有效”或“无效”,至于这个类型内部如何存储这些bit则依赖于系统的实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_t变量,而不应该对它的内部数据做任何解释,比如用printf直接打印sigset_t变量是没有意义的。
#include <signal.h> int sigemptyset(sigset_t *set);// 对指定信号集进行清空。 int sigfillset(sigset_t *set);//对指定信号集进行置1 int sigaddset(sigset_t *set, int signum);//将指定的信号signum添加到信号集中 也就是置1 int sigdelset(sigset_t *set, int signum);//将指定的 信号signum在信号集中置0 int sigismember(const sigset_t *set, int signum); //判定signum是否为1接下来介绍重要函数:
#include<signal.h> int sigprocmask(int how,const sigset_t *set,__sigset_t *oset); //若返回成功则返回0.how参数
选项含义SIG_BLOCK设置包含了我们希望添加到当前信号屏蔽字的信号,相当于 `mask = maskSIG_UNBLOCK设置包含了我们希望从当前信号屏蔽字中解除阻塞的信号,相当于 mask = mask & ~setSIG_SETMASK设置当前信号屏蔽字为 set 所指向的值,相当于 mask = set接下来我们屏蔽2号信合!
#include<iostream> #include<unistd.h> #include<signal.h> using namespace std; void handler(int signo) { cout<<"handler get signo:"<<signo<<endl; } int main() { signal(2,handler); //set==block oset== oblock sigset_t set,oset; sigemptyset(&set);//初始化 sigemptyset(&oset); sigaddset(&set,2);//设置对2号信合屏蔽 但是并没有设置进入系统 sigprocmask(SIG_BLOCK,&set,&oset); while(1) { cout<<"mypid:"<<getpid()<<endl; sleep(1); } return 0; }#############################
设置完成后,我们对代码进行编译。
可以发现我对2号信合免疫了。
pending未决队列表的获取:
sigpending读取当前进程的未决信号集,通过set参数传出来。
接下来我们以一个综合案例操作:
屏蔽2号信合 这个屏蔽是阻塞通过sigpending查询2号信合发送2号信合。 我们可以看到,发送2号信合,此时2号信合处于未决状态。 在10次后,我们取消对2号的阻塞 信号的捕捉信号的捕捉发生在用户态和内核态的互相转换中,首选我们必须先知道什么是用户态,什么是内核态。
用户态和内核态①OS在不在内存中被加载呢? ?——在 无论进程怎么切换,我们都可以找到内核的代码和数据,前提是你只要能够有权利访问!
②当前进程如何具备权利 访问这个内核页表乃至访问内核数据呢?——要进行身份切换。 进程如果是用户态的——>只能访问用户级页表 0~3G 进程如果是内核态的——>访问内核级和用户级的页表 3~4G 、③我怎么知道我是用户态的还是内核态的呢? CPU内部有对应的状态寄存器CR3, CR3有比特位标识当前进程的状态 0:内核态,3:用户态
④0—>3 用户态切到内核态的情况:1.系统调用的时候。2.时间片到了,进程间切换。3.其他等等。执行完毕就继续切回用户态。即:程序运行从用户态切换到内核态的操作:中断/异常/系统调用,例如
<1> 整数除以零操作会导致用户态—>内核态:因为会导致程序异常(分母不能为0) <2> sin()函数调用操作不会切换状态,因为库函数并不会引起运行态的切换 <3> read 系统调用操作会导致用户态—>内核态:符合系统调用接口⑤内核态vs用户态 内核态可以访问所有的代码和数据——内核态具备更高权限 用户态只能访问自己的
⑥我们的程序,会无数次直接或者间接的访问系统级软硬件资源(管理者是OS),本质上,你并没有自己去操作这些软硬件资源,而是必须通过OS-
无数次的陷入内核(1.切换身份3->0 2.切换页表,切到内核级页表)->调用内核的代码->完成访问的动作->结果返回给用户(1.切换身份0->3 2. 切换页表,切到用户级页表)->用户得到结果
⑦while(1);死循环进程普通程序会身份切换吗? —>也会陷入内核,来回切换身份 —>你也有自己的时间片 —>时间片到了的时候->OS收到中断信息,把进程从cpu移走,进程切到内核态,更换内核级页表 —>保护上下文,执行调度算法 —>选择了新的进程 —>恢复新进程的上下文 —>用户态,更换成用户级页表 —>CPU执行的就是新进程的代码!
重点: 用户态到内核态的两种切换场景:
除0错误。操作系统互捕捉这个错误,并且切换到内核态使用系统调用函数接口,会从用户态切换到内核态。自定义捕捉信号的处理过程 这个图是对自定义信号的处理,以及检测时机。
调用系统调用函数,此时用户态切换内核态此时操作系统处理完之后,检测处理信号信号为自定义信号处理方式 ,内核态不处理用户态代码处理完成,继续返回内核态从内核态返回用户态正无穷记忆法!!!
信号的捕捉函数 signal 常用捕捉函数sigaction 信号捕捉函数 int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);该函数需要将方法写入结构体中。
struct sigaction { void (*sa_handler)(int); void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; int sa_flags; void (*sa_restorer)(void); };**sa_handler:**这是一个指向函数的指针,当接收到指定的信号时,这个函数会被调用。如果设置为 SIG_IGN,则忽略该信号;如果设置为 SIG_DFL,则使用默认的信号处理行为。 **sa_mask:**这是一个信号集,用于在信号处理函数执行期间阻塞的信号。这确保了在处理一个信号时,不会受到其他信号的干扰。 也就是说,阻塞二号信号的 其他的几个参数并不需要我们了解。
上一篇
Linux存储的基本管理