郑州做网站的专业公司企业网站名备案

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

郑州做网站的专业公司,企业网站名备案,网站做可信认证,开源微信小程序商城文章目录 正文前的知识准备kill 命令查看信号man手册查看信号信号的处理方法 认识信号产生的5种方式1. 工具2. 键盘3. 系统调用kill 向任意进程发送任意信号raise 给调用方发送任意信号abort 给调用方发送SIGABRT信号 4. 软件条件5. 异常 正文前的知识准备 kill 命令查看信号 … 文章目录 正文前的知识准备kill 命令查看信号man手册查看信号信号的处理方法 认识信号产生的5种方式1. 工具2. 键盘3. 系统调用kill 向任意进程发送任意信号raise 给调用方发送任意信号abort 给调用方发送SIGABRT信号 4. 软件条件5. 异常 正文前的知识准备 kill 命令查看信号 在命令行终端输入命令kill -l快速查看信号列表
信号列表中的每一项都由【数字】和【名字】两部分构成但是信号本质上就只是一个【数字】而已【名字】只是一个宏如何证明看源码定义
信号列表中一共有62个信号没有0、32、33号信号信号可以分成实时信号34 ~ 64和非实时信号1 ~ 31后面谈到的所有内容只涉及非实时信号。 man手册查看信号 kill -l只是简单罗列一下OS中有哪些信号而已这种方式不足以了解到更多的知识还有另外一种查看信号的方式——man 手册。 man 7 signal 命令可用于查看 Linux 中有关信号的手册页。这个命令会显示与信号相关的信息其中包含了关于信号的详细信息如信号的编号、名称、含义、默认处理方式等可以更好地理解信号的概念和用法。文章后面的很多地方都会用到表里的内容
信号的处理方法 信号的完整生命周期通常分为3个阶段信号的产生、信号的保存、信号的处理。 本来信号的处理不是放在文章的内容但是为了更好的理解信号产生的现象这里提前了解一下。 信号的处理方法有三种 默认man手册中提到OS为每个信号都设置了一个默认行为当一个进程收到信号时会执行对应的行为。自定义捕捉当进程收到一个信号不执行默认行为转而执行程序员指定的行为这个过程称之为 “自定义捕捉”。忽略进程收到信号之后什么都不做即忽略该信号。 这里介绍一个能够更改进程信号处理方法的系统调用signal。 #include signal.h typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler);说明 使用该方法要包含头文件signal.h。sighandler_t是一个函数指针指向的函数参数个数为1参数类型为int返回值类型为sighandler_t。signum是指某个信号表示对该信号设置自定义处理方法。handler是程序员对信号signum的自定义处理方法。该方法的作用是将进程收到信号signum的处理动作由默认改为程序员自定义的handler方法即当进程收到信号signum时不再执行默认的处理动作转而执行程序员自定义的handler方法来处理处理信号signum。 注意signal被调用时handler方法不会被立即执行而是进程收到信号之后才会被执行如果没有收到对应的信号handler方法永远也不会执行。 认识信号产生的5种方式

  1. 工具 第一种信号产生的手段就是Linux操作系统内置的 kill 工具通过该工具能够做到向任意进程发送任意信号。 LJHhecs-66624:~$ ll /usr/bin/kill -rwxr-xr-x 1 root root 30952 Sep 9 2021 /usr/bin/kill通常kill命令的使用形式是 kill -信号编号 进程ID例如输入命令 kill -9 1234kill 工具会向 ID 为 1234 的进程发送 SIGKILL 信号SIGKILL 信号的默认行为强制终止进程。 再介绍一下2号信号 SIGINT它的默认处理动作的 Termterminate终止即终止一个正在运行的进程。 这里就通过一个实现来验证一下kill命令能否对一个进程发送信号同时还演示一下该如何使用系统调用signal来设置进程的信号处理方法。 实验内容如下 写一个程序死循环向显示器输出 I am working, my pid is 进程 ID。在 1 的基础上通过kill工具发送 2 号信号 SIGINT 来终止该进程验证进程处理信号 SIGINT 的默认动作。在 1 的基础上先调用signal将进程对信号 SIGINT 的处理动作设置为向显示器输出 get a signal: 收到的信号的数字、然后退出再进程执行过程中向该进程发送信号 SIGINT如果输出 “get a signal: 2” 并退出则说明猜测正确。在 1 的基础上调用signal将进程对信号SIGINT的处理动作设置为忽略然后在进程运行过程中向该信号发送信号SIGINT如果信号发送无效则说明猜测正确。 实验第2步 代码 // cpp #include iostream // system call #include sys/types.h #include unistd.h #include signal.husing namespace std;int main() {while (true){cout I am working, my pid is getpid() endl;sleep(2);}return 0; }运行过程
    结论验证成功进程处理信号SIGINT的默认动作确实是终止。 实验第3步 代码 #include iostream #include cstdlib #include sys/types.h #include unistd.h #include signal.husing namespace std;void handler(int signum) {cout get a signal: signum endl;exit(0); }int main() {signal(SIGINT, handler);while (true){cout I am working, my pid is getpid() endl;sleep(2);}return 0; }运行结果
    结论验证成功实现了对信号SIGINT的自定义捕捉。 实验第4步 SIG_IGN的定义 #define SIG_IGN ((__sighandler_t) 1) /
    Ignore signal. */代码 #include iostream #include sys/types.h #include unistd.h #include signal.husing namespace std;int main() {signal(SIGINT, SIG_IGN);while (true){cout I am working, my pid is getpid() endl;sleep(2);}return 0; }运行结果
    结论验证成功实现了对信号SIGINT的忽略操作。
  2. 键盘 CtrlC这个组合键是我们在Linux最常用的组合键之一它能终止正在执行过程中的进程就像下面这样
    对于CtrlC这个组合键我们虽然会用但是却不怎么了解它的原理既然这里提到了它就说明它的原理和信号有关理由是我们输入CtrlC组合键被操作系统解析成SIGINT信号然后发送给了正在运行的进程SIGINT信号的默认动作是终止进程所以死循环被终止了。 详细的原理可以看一下个人写的另外一篇文章《解析Linux键盘组合键产生信号的完整过程从硬件中断到信号发送》 凭什么这么说接下来就要用一个实验来验证这个结论是否正确。 实验内容如下 写一个程序内容是死循环向显示器输出 I am working, my pid is 进程ID同时调用signal设置对信号SIGINT的捕捉方法为向显示器输出 get a signal: 信号数字但是不退出进程执行过程中不断按下ctrl c观察实验现象如果信号数字是2就说明收到了信号SIGINT结论正确。 代码 #include iostream #include cstdlib #include sys/types.h #include unistd.h #include signal.husing namespace std;void handler(int signum) {cout get a signal: signum endl;// exit(0); }int main() {signal(SIGINT, handler);while (true){cout I am working, my pid is getpid() endl;sleep(2);}return 0; }运行结果 这里能够看到在进程给执行过程中连续按下CtrlC自定义的handler方法不断被执行然后向屏幕输出收到了2号信号说明了上面的结论是正确的。 于此同时再分别看一下源码中对信号SIGINT的定义中的注释和man手册对SIGINT的描述 #define SIGINT 2 /* Interactive attention signal. */Signal Standard Action Comment ──────────────────────────────────────────────────────────────────────── SIGINT P1990 Term Interrupt from keyboard“Interactive Interrupt Signal”交互式中断信号强调了它是通过用户的交互操作触发的用于引起程序的注意并中断执行而man手册中 “Interrupt from keyboard” 的注释表明这个“交互操作”指的就是 CtrlC 这个组合键Action 项为 Term 表示默认行为是终止进程。
  3. 系统调用 在第2点时提到shell程序通过系统调用来向目标进程发送信号而系统调用就是产生信号的第3种方式所以接下来说一下都有哪些系统调用可以发送信号说是系统调用但其实这里只会讲到一个系统调用其余两个都是C的库函数不过它们两个的底层都是封装了系统调用而且3者都是通过代码的形式来实现信号发送所以这里就一起介绍。 kill 向任意进程发送任意信号 #include sys/types.h #include signal.h int kill(pid_t pid, int sig);kill 系统调用的功能是向指定的进程发送指定的信号参数pid是目标进程ID参数sig是待发送信号信号发送成功返回值为0信号发送失败返回值为-1同时错误码(errno)被设置。 理论部分完了接下来是实验验证到底 kill 系统调用能不能向一个进程发送信号。 实验内容 使用 kill 系统调用简单实现一个属于自己的 kill 命令即 mykill输入 ./mykill -信号编号 进程ID 能够做到和 kill 命令一样的功能然后启动一个死循环输出 I am working, my pid is 进程ID 的程序在另一个窗口向该该进程发送信号SIGKILL如果死循环进程 Killed说明 kill 系统调用确实能够向一个进程发送信号。 代码 —————./testsig———————– #include iostream #include sys/types.h #include unistd.h #include signal.husing namespace std;int main() {while (true){cout I am working, my pid is getpid() endl;sleep(1);}return 0; }—————./mykill———————— #include iostream #include string #include cstring #include cerrno #include signal.h #include sys/types.husing namespace std;// ./mykill -9 pid int main(int argc, char* argv[]) {if (argc ! 3){cout Usage: argv[0] -signumber pid endl;return 1;}int signumber stoi(argv[1]1);int pid stoi(argv[2]);int n kill(pid, signumber);if (n 0){cout kill error, error message: strerror(errno) endl;return 2;}return 0; }运行结果
    raise 给调用方发送任意信号 #include signal.h int raise(int sig);raise 方法还有下面的 abort 方法严格上来说不算是系统调用但都属于调用函数发送信号的范畴并且比较常见这里也一起研究一下。raise 方法的作用是给调用 raise 的进程发送一个指定的信号 sig发送成功返回0发送失败返回一个非0整数。 使用演示 写一个程序每秒向显示器输出 I am working, my pid is 进程ID3秒后向自己发送信号SIGKILL。 代码 #include iostream #include cstdlib #include sys/types.h #include unistd.h #include signal.husing namespace std;int main() {int cnt 0;while (true){if (cnt 3) {cout raise: send SIGKILL to caller endl;raise(SIGKILL);}cout I am working, my pid is getpid() endl;cnt;sleep(1);}return 0; }运行结果
    abort 给调用方发送SIGABRT信号 #include stdlib.h void abort(void);说明 abort 方法会向调用该方法的进程发送 SIGABRT 信号SIGABRT 的全称是 “Signal Abort”“abort” 的中文意思是 “中止” 或 “放弃”在计算机术语中指的是异常终止程序的行为所以进程处理信号 SIGABRT 的默认动作就是终止进程。 代码验证及使用演示 #include iostream #include cstdlib #include sys/types.h #include unistd.h #include signal.husing namespace std;void handler(int signo) {std::cout get a signo: signo std::endl;exit(0); }int main() {signal(SIGABRT, handler);int cnt 0;while (true){if (cnt 3) {cout abort: send SIGABRT to caller endl;abort();}cout I am working, my pid is getpid() endl;cnt;sleep(1);}return 0; }运行结果
  4. 软件条件 产生信号的第4种方式就是【软件条件】相比起前三种这个看起来就很抽象个人是这样理解的在 “软件条件产生信号” 中“软件” 指的是各种进程或者程序。当某个进程满足了某种条件比如一个错误的操作、一个特定的事件发生或者一个状态的改变它可能会向其他进程、操作系统或者它自己发送一个信号以通知它们发生了某个事件或者需要采取某种行动。 下面来讲一个经典的软件条件的例子13号 管道信号 SIGPIPE 管道是进程间通信的手段之一管道本身是OS提供的基于文件系统实现的一段内核级文件缓冲区进程A先将数据写入缓冲区中进程B来读取这样就实现了两个进程间的通信。 而一个管道只能支持单向通信换言之通信的进程双方得协商谁是管道的写端谁是管道的读端讲这个有意义吗有意义因为管道通信中规定了这样一种情况当管道的读端进程关闭了管道的读端之后OS会强制终止写端进程因为管道数据已经没人读取了再写也已经没有意义。 上面是结论中间的原理是这样的管道本身是OS提供用于进程间通信的资源OS内部有通信需求的进程肯定不止这两个所以OS内肯定会创建很多的管道资源来 为进程提供通信服务而OS作为计算机软硬件资源的管理者它内部肯定会有一个描述管道信息的结构体以及组织管理所有管道的数据结构因此当某个管道的读端进程将管道的读端关闭了OS肯定就会知道并将描述管道信息的结构体对象内的信息做修改当该管道的写端进程尝试写入数据时OS会检查管道的状态如果发现该进程满足 “ 向不满足写入条件的管道进行写入 ” 的条件时OS就会向该进程发送 SIGPIPE 信号通知它写入操作失败该进程需要处理这个信号。 首先来看一下man手册对于 SIGPIPE 信号的描述 Signal Standard Action Comment ──────────────────────────────────────────────────────────────────────── SIGPIPE P1990 Term Broken pipe: write to pipe with no readers; see pipe(7)其次惯例操作实验验证内容如下 pipe_server进程作为管道的读端从管道中读取数据并输出读端进程会在运行3秒后退出pipe_client进程作为管道的写端不停向管道写入数据同时写端进程对SIGPIPE信号做自定义捕捉看是否真的收到了SIGPIPE信号。 代码如下 —————Command.hpp———————— #ifndef COMMAND_HPP #define COMMAND_HPP#include iostream #include string #include cerrno #include cstdlib #include cstring #include sys/types.h #include sys/stat.h #include unistd.h #include fcntl.h #include signal.husing std::cerr; using std::cin; using std::cout; using std::endl; #define Mode 0666class Fifo { public:Fifo(const char *path) : _path(path){umask(0);int n mkfifo(_path.c_str(), Mode);if (n 0){cout mkfifo success endl;}else{cerr mkfifo failed, errno: errno , errmsg: strerror(errno) endl;exit(1);}}~Fifo(){int n unlink(_path.c_str());if (n 0){cout remove fifo success endl;}else{cerr remove fifo failed, errno: errno , errmsg: strerror(errno) endl;exit(1);}}protected:std::string _path; };#endif —————PipeServer.cc———————— #include Command.hppconst char *path fifo;int main() {// 创建管道Fifo fifo(path);// 获取管道读端int rfd open(fifo, O_RDONLY);if (rfd 0){cout open fifo success endl;}else{cerr open fifo failed, errno: errno , errmsg: strerror(errno) endl;exit(1);}// 读数据char buffer[1024]{0};int cnt 4;while (cnt–){ssize_t n read(rfd, buffer, sizeof(buffer) - 1);if (n 0){buffer[n] 0;cout get msg from client: buffer endl;}else if (n 0){cout client quit, me too! endl;break;}else{cerr read from fifo failed, errno: errno , errmsg: strerror(errno) endl;break;}}close(rfd);return 0; } —————PipeClient.cc———————— #include Command.hppvoid handler(int signum) {cout get a signum: signum endl;cout Pipe is broken, now I quit endl;exit(1); }int main() {signal(SIGPIPE, handler);// 获取管道写端int wfd open(fifo, O_WRONLY);if (wfd 0){cout open fifo success endl;}else{cerr open fifo failed, errno: errno , errmsg: strerror(errno) endl;exit(1);}// 发消息const char *inbuffer I am process A and I am client;while (true){ssize_t n write(wfd, inbuffer, strlen(inbuffer));if (n 0){cerr write to fifo failed, errno: errno , errmsg: strerror(errno) endl;break;}sleep(1);}close(wfd);return 0; }运行结果
  5. 异常 了解过进程的都知道一个进程退出只会有以下3种场景 代码运行完毕结果正确。代码运行完毕结果不正确。代码运行过程中进程异常终止。 而异常终止的本质原因是因为进程收到了信号所以第5种产生信号的方式就是异常。 举两个常见的代码异常进程收到信号的例子 代码除零 8) SIGFPE 下面是一段演示代码里面写了除零操作。 #include iostream using namespace std;int main() {int a 10;a / 0;return 0; }运行结果
    运行之后看到程序输出 “Floating point exception (core dumped)” 后就结束了为了确认其是否收到了信号这里使用signal设置自定义捕捉 SIGFPE 信号代码修改如下 #include iostream #include sys/types.h #include unistd.h #include signal.husing namespace std;void handler(int signum) {cout get a signum: signum endl;sleep(1); }int main() {signal(SIGFPE, handler);int a 10;a / 0;return 0; }输出结果
    捕捉了之后确实验证了进程收到了 SIGFPE 信号但同时也看到了一个很奇怪的现象明明代码里从头到尾都没有写循环为什么handler方法会被一直执行 理由信号的处理方法从Core退出被改成的向显示器输出一句话这就导致了原本该退出的进程没有退出进程没有退出除零异常就一直存在进程在被CPU调度时就会一直触发异常OS会不断地向进程发送 SIGFPE 信号进程收到信号并处理就会一直向屏幕输出导致了一种死循环的局面。 野指针 11) SIGEGV 下面是一份演示代码里面写了野指针访问操作。 #include iostream using namespace std;int main() {int *p nullptr;*p 100;return 0; }运行结果
    运行之后看到程序输出 “Segmentation fault (core dumped)” 后直接退出为了验证进程是否真的收到了信号设置自定义捕捉 SIGSEGV 信号观察是否收到信号代码修改如下 #include iostream #include sys/types.h #include unistd.h #include signal.h using namespace std;void handler(int signum) {cout get a signum: signum endl;sleep(1); }int main() {signal(SIGSEGV, handler);int *p nullptr;*p 100;return 0; }运行结果