Linux:进程信号(一.认识信号、信号的产生及深层理解、Term与Core)
上次竣事了进程间通讯的知识先容:Linux:进程间通讯(二.共享内存详细解说以及小项目使用和相关指令、消息队列、信号量1.认识信号
**概念:**在Linux系统中,进程之间可以通过信号进行通讯,实现异步信息的发送和接收。
信号是Linux系统中一种轻量级的通讯机制,用于关照进程发生了某种事件或异常情况。进程可以发送信号给其他进程,也可以接收来自其他进程或系统的信号。
[*]异步:是一种编程模子或通讯方式,指的是在进行操作或通讯时,不需要等待前一个操作完成或响应返回,而是可以继续执行下一个操作或使命(二者是并发的,一个不用等另一个)。异步编程可以进步系统的并发性和响应性,使得步伐能够更高效地利用资源和处理多个使命
[*]异步信息通常指的是在通讯或交流过程中,信息的发送和接收是差异步的,即发送方和接收方的速度或时间不划一。这种情况下,接收方大概会在差异的时间点接收到发送方发送的信息
可以使用kill -l来查看信号
https://i-blog.csdnimg.cn/direct/8e00687fbd7541a19c364d50e6d0d8d5.png
[*] 1-31是普通讯号,34-64是实时信号,没有0,没有32 33
[*] 使用数字和名称都行(本质也是宏定义)
进程看待信号方式
[*] 在没有发生的时间,进程就已经知道如果发生了,怎么进行处理:这句话大概指的是预先设置好的信号处理方式。在Linux系统中,进程可以使用signal()大概sigaction()等系统调用来注册信号处理函数,如许当特定信号发生时,系统会调用相应的信号处理函数来处理该信号。
[*] 进程能认识信号:这句话指的是我们可以辨认和处理特定的信号。Linux系统定义了一系列标准信号(如SIGINT、SIGTERM、SIGKILL等),每个信号都有特定的寄义和默认处理方式,进程可以根据需要辨认和处理这些信号。
[*] 信号到来的时间,如果进程正在处理更重要的事变,导致暂时不能处理到来的信号,那么进程必须要把到来的信号进行暂时生存:这指的是信号的异步性。当进程正在执行某些重要使命时,如果接收到信号,大概无法立即处理,此时系统会将信号暂时生存,等到合适的时机再进行处理。
[*] 信号到了,可以不立即处理,选择在合适的时间处理:进程可以选择在合适的时机处理信号,而不是立即响应。这种灵活性使得进程能够根据自身状态和需求来处理信号。
[*] 信号的产生是随时产生的,我们无法精确预料,以是信号是异步发送的:信号是由其他用户、进程或系统事件产生的,进程无法精确猜测信号的产生时机。因此,信号的发送是异步的,进程需要通过信号处理函数来处理这种异步事件。
异步发送指的是信号是由其他用户或进程产生的,而接收信号的进程在信号到达之前大概一直在处理自己的使命
2.信号的产生
2.1信号的处理的方式 — signal()函数
https://i-blog.csdnimg.cn/direct/51a8331633cf4060abf248e93739d727.png
signal()函数是Linux系统中用于注册信号处理函数的函数。它的原型如下:
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);//sighandler_t是个函数指针
这个函数接受两个参数:signum表示要捕获的信号编号,handler表示要注册的信号处理函数。
[*] 参数说明:
[*]signum:表示要捕获的信号编号,可以是预定义的信号宏如SIGINT、SIGTERM等,也可以是用户自定义的信号编号。
[*]handler:表示要注册的信号处理函数,其**原型通常是void handler(int signal_number)(调用如许的函数)。**可以是函数指针(自定义),也可以是SIG_IGN(忽略信号)或SIG_DFL(默认处理)。
[*] 返回值:
[*]signal()函数的返回值是一个函数指针,指向之前注册的信号处理函数。如果之前未注册过该信号的处理函数,则返回SIG_DFL(默认处理)。
[*] 信号处理方式:
[*]如果handler为函数指针,则表示注册自定义的信号处理函数,当收到指定信号时,系统会调用该函数进行处理。
[*]如果handler为SIG_IGN,表示忽略该信号,即当收到指定信号时不进行任那边理。
[*]如果handler为SIG_DFL,表示使用系统默认的处理方式,通常是停止进程或执行默认操作。
[*] 注意事项:
[*]当使用signal()函数注册信号处理函数时,处理函数并不会立即执行,而是在未来收到对应的信号时才会执行
[*]如果注册了一个处理SIGINT信号的处理函数,但是进程从未收到SIGINT信号,那么注册的处理函数也就永远不会被调用。这种情况大概会发生
[*] 定义信号处理函数:指的是编写现实的处理信号的函数,即编写处理SIGINT信号的具体函数逻辑。这个函数通常具有特定的原型,如void handler(int signal_number)。
[*] 注册信号处理函数:指的是使用signal()函数将定义好的信号处理函数与特定的信号关联起来。通过注册信号处理函数,系统会在收到对应的信号时调用这个函数来处理信号。
完备的表述应该是:定义一个处理SIGINT信号的处理函数,并通过signal()函数将这个处理函数注册到SIGINT信号上。当进程收到SIGINT信号时,系统会调用注册的处理函数来处理该信号。
2.2kill指令产生信号
kill指令是用于向进程发送信号的命令。通过kill命令,可以向指定进程发送差异类型的信号,例如SIGTERM、SIGKILL等。这些信号可以触发进程中注册的信号处理函数,大概直接停止进程的执行。
kill命令的基本语法为:
kill <PID>
<PID>是要发送信号的目标进程的进程ID。可以通过ps命令或其他方式获取目标进程的进程ID。
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
using namespace std;
int main()
{
while(true)
{
cout << "I'm a process, pid:" << getpid() << endl;
sleep(2);//我们写个死循环,每隔两秒打印一下
}
return 0;
}
https://i-blog.csdnimg.cn/direct/67107141185c4f32aa8dbc7ce8c77361.png
2.3键盘产生信号
[*] 我们之前使用的Ctrl+c:ctrl + c -> OS 解释成为2(SIGINT)号信号 -> 向目标进程进行发送 -> 进程收到-> 进程响应
[*]用户按下Ctrl+C组合键,操作系统会将这个操作解释为发送SIGINT(信号编号为2)信号给目标进程。
[*]目标进程收到SIGINT信号后,会执行与之关联的信号处理函数。通常情况下,SIGINT信号会导致进程停止执行,雷同于用户自动输入exit大概点击关闭窗口。
[*] 之前使用的Ctrl+\:ctrl + c -> OS 解释成为3(SIGQUIT)号信号 -> 向目标进程进行发送 -> 进程收到-> 进程响应
[*]用户按下Ctrl+\组合键,操作系统会将这个操作解释为发送SIGQUIT(信号编号为3)信号给目标进程。
[*]目标进程收到SIGQUIT信号后,会执行与之关联的信号处理函数。与SIGINT差异的是,SIGQUIT信号通常用于哀求进程停止,并且会生成core文件(如果core文件生成是启用的话)
验证:
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
using namespace std;
void handler(int signum)
{
cout << "got a signal, number is : " << signum << endl;
}
int main()
{
signal(SIGINT, handler);// 对2号信号SIGINT处理
signal(SIGQUIT, handler); // 对号信号SIGQUIT处理
while(true)
{
cout << "I'm a process, pid:" << getpid() << endl;
sleep(2);
}
return 0;
}
https://i-blog.csdnimg.cn/direct/709d6b9c1df84cbebfa964d75e0c4aaf.png
2.4系统调用发送信号 —kill系统调用、raise()和abort()库函数
kill是一个常见的系统调用,用于向指定的进程发送信号。通过kill系统调用,一个进程可以向另一个进程发送差异类型的信号,从而实现进程之间的通讯和控制。
kill系统调用的原型如下:
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
[*]pid参数指定了要发送信号的目标进程的进程ID(PID)。如果pid为正数,则表示发送信号给进程ID为pid的进程;如果pid为0,则表示发送信号给与调用进程在同一进程组的所有进程;如果pid为-1,则表示发送信号给所有有发送权限的进程。
[*]sig参数指定了要发送的信号的编号,可以是预定义的信号常量(如SIGKILL、SIGTERM等),也可以是自定义的信号编号。
kill系统调用的返回值为0表示乐成发送信号,-1表示发送信号失败,并且在这种情况下,可以通过errno全局变量获取具体的错误信息。
我们可以利用这个,来实现一个kill指令
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <cerrno>
#include <cstring>
using namespace std;
int main(int argc, char *argv[])
{
if (argc != 3)
{
cout << "Usage: kill -signum pid" << endl;
return 1;
}
int signum = stoi(argv + 1); // 获取信号的数字
int pid = stoi(argv); // 获取pid
int n = kill(pid, signum);
if (n == -1)
{
cerr << "kill failed: " << strerror(errno) << endl;
}
return 0;
}
可以对任意进程发送任意的信号
[*] raise()函数:raise()函数用于向当前进程发送一个信号。它的原型如下:
int raise(int sig);
sig是要发送的信号编号。
乐成时返回0,失败时返回非零值
对自己发送任意信号
int main()
{
int count = 0;
while (true)
{
cout << "cnt: " << count++ << endl;
sleep(1);
if (count == 3)
{
cout << "send 9 to itself" << endl;
raise(9);
}
}
return 0;
}
https://i-blog.csdnimg.cn/direct/a0bd568618db47369673a7ca532e423a.png
[*] abort()函数:abort()函数用于异常停止步伐的执行。当调用abort()函数时,步伐会立即停止,并向操作系统发送SIGABRT信号。abort()函数的原型如下:
void abort(void);
abort()函数会导致步伐生成一个core文件,用于调试。一般来说,abort()函数被用于发现步伐中的严重错误,并且需要立即停止步伐执行。
给自己放指定信号(6号SIGABRT)
2.5软件条件产生信号
[*]管道
读端关闭其文件描述符并且不再读取数据时,如果写端继续向管道写入数据,操作系统会发送一个SIGPIPE信号给写端进程。默认情况下,这个信号会停止写端进程。SIGPIPE信号是一个用于处理管道写端在写操作时无读端接收的情况的信号。
[*]alarm()函数
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动作是停止当前进程。
函数的返回值是0大概是从前设定的闹钟时间还余下的秒数
在Linux系统中,SIGALRM信号的默认行为是停止进程。当步伐设置一个定时器并在定时器到期时产生SIGALRM信号时,如果步伐没有显式地捕获和处理这个信号,那么默认情况下操作系统会停止该进程。
alarm(0),代表取消闹钟:alarm(0)函数会扫除之前设置的定时器,并返回剩余的定时器时间(如果有的话)而且不会再触发SIGALRM信号
怎么理解软件条件:
软件条件是指软件层面上的一种异常情况或特定的条件,通常由软件中断信号触发,用来关照进程某种特定的事件已经发生
布局体与堆等数据布局都是软件,也有条件触发
https://i-blog.csdnimg.cn/direct/e8db47ca10b84e728f55d276b55c9681.png
2.6异常产生信号
[*] 代码除0了,收到8号信号SIGFPE
void handler(int signum)
{
cout << "got a signal, number is : " << signum << endl;
exit(0);
}
int main()
{
signal(8, handler); // 8号信号SIGFPE
int a = 10;
int b = a / 0;
cout << b << endl;
return 0;
}
https://i-blog.csdnimg.cn/direct/0129876327824faeba69ce6d424edd1a.png
[*] 访问野指针指向的空间,收到11号信号SIGSEGV(段错误)
void handler(int signum)
{
cout << "got a signal, number is : " << signum << endl;
exit(0);
}
int main()
{
signal(11, handler); // 11号信号SIGSEGV
int* pa=nullptr;
*pa=10;
return 0;
}
3.信号产生的深层理解
键盘产生信号
https://i-blog.csdnimg.cn/direct/2b27b25cd62244609ed66c2f077162d1.png
那么现在又有问题了,什么叫做解释成为信号,什么叫做发送给进程?
信号暂时生存在那里呢?
进程的PCB中,使用位图布局来存1到31号的信号:比特位的位置来表示信号编号,比特位的01来表示是否收到指定的信号
那么发送信号本质上是写入信号:
https://i-blog.csdnimg.cn/direct/194c3343a09a4efe81808a41b8c16390.png
task_struct是内核数据布局,只有OS有能力写入。我们用户只能使用系统调用。以是,无论信号产生的方式有多少种,最终都是OS在进程中写入信号的
异常产生信号
[*]除0异常
https://i-blog.csdnimg.cn/direct/36f17919f6c245d4b35eedd1b569ac64.png
但如果我们自定义处理里,没有进行exit()退出,那么就会一直打印
因为,寄存器中的数据都是进程的上下文,CPU一直在进行进程的调度,那么就涉及到进程上下文的生存和恢复,因为我们没有进行退出操作,以是每次恢复后,异常还是存在。
[*]野指针异常
https://i-blog.csdnimg.cn/direct/f9786189dc344dce83f9d6b35916087b.png
最终信号一定都是OS进行写入进程中的信号位图中
总结一下:
[*] 上面所说的所有信号产生,最终都要有OS来进行执行,为什么?OS是进程的管理者
[*] 信号的处理是否是立即处理的?在合适的时间
4.Term与Core
https://i-blog.csdnimg.cn/direct/edd6fadfde8f4c5896a5a7842faa5d50.png
[*]Term(Termination):
[*]当进程接收到一个默认处理动作为Term的信号时,操作系统会立即停止该进程的执行。
[*]进程在接收到如许的信号后,会立即停止运行,并开释其所占用的系统资源。
[*]除非进程已经捕获了该信号并定义了自己的信号处理函数,否则进程会按照默认的Term动作被停止。
[*]Core(Core Dump):
[*]当进程接收到一个默认处理动作为Core的信号时,操作系统不但会停止该进程的执行,还会生成一个核心转储文件(core dump file)。
[*]核心转储文件是进程在异常停止时的内存映像,它包罗了进程在停止时的状态信息,如变量值、函数调用栈等。
[*]这个文件对于步伐员来说非常有用,因为它可以资助他们分析进程瓦解的原因,进行调试和修复。
[*]与Term差异,Core动作在停止进程的同时还会生成一个额外的文件。
需要注意的是云服务器默认关闭了core file的选项:因为如果步伐瓦解是由于某种未知的错误或条件触发的,并且这个问题没有得到及时办理,那么核心转储(core dump)文件大概会不停生成,占用大量的磁盘空间
ulimit -a 是一个在 Linux中用于显示当前 shell 会话的资源限定的命令。ulimit 命令允许用户设置或查看各种 shell 和进程资源限定。这些限定可以资助防止系统资源的滥用,如 CPU 时间、文件大小、打开的文件描述符数量等。
当你运行 ulimit -a 时,它会列出所有当前设置的资源限定。以下是一些常见的 ulimit 资源和它们的描述:
[*]-c: core file size (blocks, -c unlimited disables core files)
[*]-d: data seg size (kbytes, -d unlimited)
[*]-e: scheduling priority (-e 0 to 20)
[*]-f: file size (blocks, -f unlimited)
[*]-i: max locked memory (kbytes, -i unlimited)
如果想要修改某个限定,可以使用 ulimit 命令加上相应的选项和新的限定值。
例如,要设置最大打开文件描述符数量为 4096,你可以运行 ulimit -n 4096。但是请注意,这些限定通常只影响当前 shell 会话和由该 shell 启动的子进程。它们不会永久地改变系统配置。
我们想要产生core文件的话:ulimit -c选项设置core file的大小
core文件
[*]为什么要有这个文件:我们想通过core来知道进程为什么退出,以及执行到哪行代码退出的
[*]是什么:将进程在内存中的核心数据(与调试有关的)转储到磁盘中形成core、core.pid的文件
[*]作用:最大的作用是方便我们调试了
Core文件是Linux系统下的内核转储文件,当步伐瓦解时由操作系统生成,主要用于对步伐进行调试。
当步伐出现内存越界、段错误(Segmentation Fault)或其他异常情况导致瓦解时,操作系统会中断该进程,并将当前内存状态、寄存器状态、堆栈指针、内存管理信息以及各个函数使用堆栈信息等生存到Core文件中。如许,步伐员就可以通过读取和分析Core文件来找出步伐瓦解的原因和位置,从而进行调试和修复。
Core文件的存在是为了资助步伐员更好地理解和办理步伐瓦解的问题。由于Core文件包罗了步伐瓦解时的详细内存状态信息,因此它对于调试复杂的内存问题、并发问题以及系统调用等问题非常有用。同时,由于Core文件是在步伐瓦解时自动生成的,因此它也可以作为一种自动记载步伐瓦解信息的机制,方便步伐员进行事后分析和排查。
但是,由于Core文件大概包罗大量的内存数据,因此它大概会占用较大的磁盘空间。在不需要进行调试或分析的情况下,可以通过修改操作系统的配置来克制生成Core文件或将其生存到其他位置。
https://i-blog.csdnimg.cn/direct/b095e93306384a86b427f7bc35fa47da.png
#include <iostream>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
using namespace std;
int main()
{
pid_t id = fork();
if (id == 0)
{
// child
int a = 10;
a /= 0;
exit(1);
}
// father
int status = 0;
pid_t rid = waitpid(id, &status, 0);
cout << "exit code:" << ((status >> 8) & 0xff) << endl;
cout << "exit signal:" << (status & 0x7f) << endl;
cout << "core dump:" << ((status >> 7) & 0x1) << endl;
return 0;
}
https://i-blog.csdnimg.cn/direct/d432ea03a4b04b008b04614ac6dd9916.png
今天也是到这里了,(存货太多慢慢发了)。学了网络部门的要赶快做项目了
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]