通辽网站建设公司,wordpress的注册文件,网站后台html页面,北京网站制作推广目录 前言
一、初识信号
二、信号的概念
三、信号的发送与捕捉
3.1 信号的发送
3.1.1 kill 命令
3.1.2 kill 函数
3.1.3 raise函数
3.1.4 abort函数
3.2 信号的捕捉
3.2.1 signal函数
3.2.2 sigaction函数
3.2.3 图示
四、信号的产生
4.1 硬件异常产生信号
4.2 …目录 前言
一、初识信号
二、信号的概念
三、信号的发送与捕捉
3.1 信号的发送
3.1.1 kill 命令
3.1.2 kill 函数
3.1.3 raise函数
3.1.4 abort函数
3.2 信号的捕捉
3.2.1 signal函数
3.2.2 sigaction函数
3.2.3 图示
四、信号的产生
4.1 硬件异常产生信号
4.2 软件条件产生信号
五、Core dump
5.1 core dump介绍
5.2 core dump作用 六、阻塞信号
6.1 相关概念
6.2 在内核中的表示
6.3 sigprocmask
6.4 sigpending
七、可重入函数 前言 在日常生活中有许多方面都涉及到了信号的知识例如信号弹、上下课铃声、红绿灯、闹钟等等。我们可以仔细想想由信号引发的几个问题
我们是如何认识这些信号的----有人教随后记住了即使现在没有信号产生我们也知道信号产生之后应该做什么信号产生了我们可能并不会立即处理这个信号因为我们可能在做一些更重要的事情由此可以得出信号产生后到信号处理之间其实会有一段时间窗口而在这个时间窗口内我们必须记住信号的到来。 而在计算机当中执行的主体就是进程也就是说进程必须能够识别并处理信号这是属于进程内置功能的一部分。同样的一个进程由信号产生到信号开始被处理就一定会有时间窗口而进程具有临时保存哪些信号已经发生了的能力。 一、初识信号 在linux中运行某一个进程时我们可以随时按下 ctrlc 来杀掉前台进程如下图一个死循环的输出我们用ctrlc使进程退出 它为什么能够杀掉前台进程呢 在Linux中的一次登录中一个终端一般会配上一个bash每一个登录只允许一个进程是前台进程可以允许多个进程是后台进程。因此如果我们运行时在进程名后面加上则代表让它在后台运行这个时候我们使用ctrlc就没有用了必须使用kill -9 pid 号来杀掉进程。 所以正常我们在运行一个进程时在对bash进行输入命令就没有用了因为这个进程运行时就成为了前台进程。
kill -l 可以查看操作系统拥有的信号 ctrl c 的本质是被进程解释成为收到了2号信号SIGINT需要注意的是不同的操作系统可能对信号的编号有所不同因此在跨平台开发时应当注意信号编号的兼容性。 普通信号可以不立即处理实时信号必须立即处理。
信号的处理方式 1. 忽略此信号。 2. 执行该信号的默认处理动作。 3. 自定义提供一个信号处理函数要求内核在处理该信号时切换到用户态执行这个处理函数这种方式称为捕捉 (Catch)一个信号。 二、信号的概念 信号是 Linux 操作系统中用于进程间通信、处理异常等情况的一种机制。它是由操作系统向一个进程或者线程发送的一种异步通知用于通知该进程或线程某种事件已经发生需要做出相应的处理。 信号的产生和我们自己的代码的运行是异步的这意味着信号的产生与代码的执行没有直接的关联信号属于软中断。 三、信号的发送与捕捉
3.1 信号的发送
在 Linux 中进程可以通过向其他进程或自身发送信号的方式进行通信或处理异常情况。下面介绍几种常见的发送信号的方法。
3.1.1 kill 命令
kill [-signal] PID
其中-signal 可选参数表示要发送的信号类型如果省略该参数则默认发送 SIGTERM 信号。PID 表示接收信号的进程 ID。
例如要向进程 ID 123 发送 SIGINT 信号可以执行以下命令
kill -SIGINT 123
3.1.2 kill 函数
我们也可以使用系统调用的一些函数来发送信号 其中pid 表示接收信号的进程 IDsig 表示要发送的信号类型。如果函数调用成功则返回 0否则返回 -1 并设置 errno。
例如要向进程 ID 123 发送 SIGINT 信号可以执行以下代码
#include signal.h
#include unistd.hint main() {pid_t pid 123;int sig SIGINT;if (kill(pid, sig) -1) {perror(kill);return 1;}return 0;
}
3.1.3 raise函数
raise 函数是一个简单的发送信号的函数可以用来向当前进程发送信号。raise 函数的原型如下 其中sig 表示要发送的信号类型。如果函数调用成功则返回 0否则返回 -1 并设置 errno。
例如要向当前进程发送 SIGTERM 信号可以执行以下代码
#include signal.hint main() {int sig SIGTERM;if (raise(sig) -1) {perror(raise);return 1;}return 0;
}
3.1.4 abort函数
abort函数的作用是引起一个正常函数的终止它会给自己发送一个6号信号SIGABRT 3.2 信号的捕捉 在上文提到过信号是可以被自定义捕捉的下面介绍几种常见的捕捉信号的方法。
3.2.1 signal函数
signal 函数可以用来注册信号处理函数。signal 函数的原型如下 其中sig 表示要注册的信号类型handler 是一个函数指针指向信号处理函数。signal 函数返回一个函数指针指向之前注册的信号处理函数。如果注册信号处理函数失败则返回 SIG_ERR。
例如要注册 SIGINT 信号的处理函数自定义处理函数名称为“sigcb”, 在sigcb当中完成打印触发本次事件的信号值可以执行以下代码
#includeiostream
#includesignal.h
#include unistd.h
using namespace std;
void sigcb(int signo)
{cout process get a SIGINT signal: signo endl;// exit(1);
}
int main()
{if (signal(SIGINT, sigcb) SIG_ERR) {perror(signal);return 1;}while(true){coutI am a process,pid: getpid()endl;sleep(1);}return 0;
}
当我们按下ctrlc时可以看到程序输出最后我们使用ctrl\退出程序运行结果如下 3.2.2 sigaction函数
在Linux中sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回 -1。 其中sig 表示要注册的信号类型act 是一个指向 struct sigaction 结构体的指针表示新的信号处理函数和信号处理选项oldact 是一个指向 struct sigaction 结构体的指针用于获取之前注册的信号处理函数和信号处理选项。
struct sigaction 结构体的定义如下
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_sigaction 字段指定一个信号处理器函数这个函数包含三个参数一个整数表示信号编号一个指向siginfo_t结构体的指针和一个指向void类型的指针。sa_mask字段指定了在执行信号处理函数期间要阻塞哪些信号。后面两个字段本章不做详细解释。
例如要注册 SIGINT 信号的处理函数自定义处理函数名称为“sigcb”, 在sigcb当中完成打印触发本次事件的信号值可以执行以下代码
#includeiostream
#includesignal.h
#include unistd.h
using namespace std;
void sigcb(int signo)
{cout process get a SIGINT signal: signo endl;// exit(1);
}
int main()
{struct sigaction newact {newact.sa_handler sigcb};struct sigaction oldact;if (sigaction(SIGINT, newact, oldact) -1) {perror(sigaction);return 1;}while(true){coutI am a process,pid: getpid()endl;sleep(1);}return 0;
}
3.2.3 图示
当我们的进程从内核态返回到用户态的时候进行信号的检测和处理 四、信号的产生
4.1 硬件异常产生信号 硬件异常产生信号指硬件发现进程的某种异常而硬件是被操作系统管理。硬件会将异常通知给系统系统就会向当前进程发送适当的信号。例如当前进程执行了除以0的指令CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号发送给进程。再比如当前进程访问了非法内存地址MMU会产生异常内核将这个异常解释为SIGSEGV信号发送给进程。
4.2 软件条件产生信号 alarm函数相当于设置一个闹钟告诉内核多少秒后发送一个SIGALRM信号给当前进程它的返回值是一个闹钟的剩余时间。
#includeiostream
#includesignal.h
#include unistd.h
using namespace std;
int main()
{int n0;alarm(1); //1秒后给进程发送SIGALRM信号while(true){coutnendl;n;}return 0;
} 五、Core dump
5.1 core dump介绍 当一个进程要异常终止时可以选择把进程的用户空间内存数据全部保存到磁盘上文件名通常是core这叫做Core Dump。但默认云服务器上面的core功能是被关闭的我们可以使用ulimit-a 查看 ulimit-c 字节数 设置core文件大小 子进程的status可以当作位图来看因此我们可以手写一段代码来提取出Core Dump的值原理就是通过使用(status 8) 0xFF 获取子进程的退出码高8位通过使用(status 0x7F)获取子进程的退出信号低7位最后使用((status 7) 1) 表达式用于判断是否发生了核心转储第8位即core dump标志。
#include iostream
#include unistd.h
#include signal.h
#include sys/types.h
#include sys/wait.husing namespace std;int main()
{pid_t id fork();if(id 0){//childint cnt 500;while(cnt){cout i am a child process, pid: getpid() cnt: cnt endl;sleep(1);cnt--;}exit(0);}// fatherint status 0;pid_t rid waitpid(id, status, 0);if(rid id){cout child quit info, rid: rid exit code: ((status8)0xFF) exit signal: (status0x7F) core dump: ((status7)1) endl; }return 0;
} 对于8号信号默认云服务器上面的core功能是被关闭的可以看到它的core dump为0使用ulimit -c 设置文件大小以后再运行可以看到core dump标志位变为1并且生成了core.pid号的文件 5.2 core dump作用 假设我们写一段除0的错误代码
#include iostream
using namespace std;int main()
{int a 10;int b 0;a / b;cout a a endl;return 0;
} 运行后可以结合gdb来进行事后调试 六、阻塞信号
6.1 相关概念
实际执行信号的处理动作称为信号递达(Delivery)。信号从产生到递达之间的状态,称为信号未决(Pending)。进程可以选择阻塞 (Block)某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞才执行递达的动作。注意阻塞和忽略是不同的只要信号被阻塞就不会递达而忽略是在递达之后可选的一种处理动作。
6.2 在内核中的表示
在task_struct结构中信号的构成实质是两个位图和一个数组我们看的顺序也是横着从左往右看。 每个信号都有两个标志位分别表示阻塞(block)和未决(pending)还有一个函数指针表示处理动作。信号产生时内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图的例子中SIGHUP信号被阻塞未产生过当它递达时执行默认处理动作。 SIGINT信号产生过但正在被阻塞所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号因为进程仍有机会改变处理动作之后再解除阻塞。 SIGQUIT信号未产生过一旦产生SIGQUIT信号将被阻塞它的处理动作是用户自定义函数sighandler。 当一个信号被阻塞时它仍然可以被发送到进程并且会被添加到未决信号集合中。阻塞仅仅阻止信号的传递即阻止信号的处理但不阻止信号的接收。如果一个信号被设置为忽略那么即使该信号被发送到进程它也不会被添加到未决信号集合中因为忽略的信号不会对进程产生任何影响。
6.3 sigprocmask 函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集/BLOCK表)成功返回0出错返回-1。 how参数 sigset_t 称为信号集这个类型可以表示每个信号的“有效”或“无效”状态它用来存储未决和阻塞标志。
set参数指向一个信号集的指针这个信号集指定了要阻塞或解除阻塞的信号。如果 set 是一个空指针NULL则 how 参数没有效果指向一个信号集的指针这个信号集指定了要阻塞或解除阻塞的信号是一个输入型函数。如果 set 是一个空指针NULL则 how 参数没有效果。
oldset参数如果不是空指针NULL则进程的当前信号屏蔽字会被存储在 oset 指向的位置。如果 oset 是空指针则不返回当前的信号屏蔽字是一个输出型参数。 6.4 sigpending sigpending函数读取当前进程的未决信号集通过set参数传出即把调用进程所对应的pending表带出来调用成功则返回0出错则返回-1。 例如可以使用下段代码屏蔽2号信号并通过打印pending表来观察
#includeiostream
#includeunistd.h
#includesignal.h
using namespace std;void PrintPending(sigset_t pending)
{for(int signo31;signo1;signo--){//存在打印1否则为0if(sigismember(pending,signo)){cout1;}else{cout0;}}coutendl;
}
int main()
{//阻塞2号新号 --数据预备sigset_t bset,oset;//函数sigemptyset初始化set所指向的信号集使其中所有信号的对应bit清零,表示该信号集不包含任何有效信号。sigemptyset(bset); sigemptyset(oset); //函数sigaddset在该信号集中添加2号信号sigaddset(bset,2); //----系统调用将数据设置进内核----sigprocmask(SIG_SETMASK,bset,oset);//重复打印pending信息便于观察sigset_t pending;while(true){//获取int n sigpending(pending);if(n0) continue;//打印PrintPending(pending);sleep(1);}return 0;
} 注意9号和19号信号是无法被屏蔽的 七、可重入函数
如果一个函数在被重复进入的情况下不会出错则是可重入函数否则是不可重入函数。
如果一个函数符合以下条件之一则是不可重入的:
调用了malloc或free,因为malloc也是用全局链表来管理堆的。 调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。