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

网站生成app客户端wordpress版权代码

网站生成app客户端,wordpress版权代码,虾米WordPress,示范校建设验收网站Ciallo&#xff5e;(∠・ω< )⌒☆ ~ 今天&#xff0c;我将和大家一起学习 linux 的信号~ ❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️ 澄岚主页&#xff1a;椎名澄嵐-CSDN博客 Linux专栏&#xff1a;★ Linux ★ _椎名澄嵐的博客-CSDN博客 ❄️❄️❄️❄️…

Ciallo~(∠・ω< )⌒☆ ~ 今天,我将和大家一起学习 linux 的信号~

❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️

澄岚主页:椎名澄嵐-CSDN博客

Linux专栏:★ Linux ★ _椎名澄嵐的博客-CSDN博客

❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️


目录

壹  信号基础概念

贰  信号产生

2.1 键盘产生信号

2.2 系统调用产生信号

2.2.1 kill

2.2.2 raise

2.2.3 abort

2.3 调用系统命令向进程发信号kill

2.4 异常产生信号

2.5 软件条件产生信号

叁  信号保存

3.1 相关概念

3.2 在内核中的表示

3.3 sigset_t

3.4 信号集操作函数

3.4.1 sigprocmask

3.4.2 sigpending

3.4.3 整合代码

肆  信号处理

4.1 初步结论

4.2 操作系统是怎么运行的

4.2.1 硬件中断

4.2.2 时钟中断

4.2.3 软中断

4.3 内核态和用户态

4.4 sigaction

伍  可重入函数

陆  volatile

柒  SIGCHLD信号

~  完  ~


壹  信号基础概念

信号是一种给进程发送的,用来进行事件异步通知的机制。
信号的产生相对于进程的运行是异步的。
信号是发送给进程的。

可以类比于闹钟,红绿灯,上课铃,人的脸色等。人就相当于进程。这些东西会中断人正在做的事情。

信号要点:
1. 进程在信号还没有产生的时候,就知道如何处理了。
2. 处理信号不是立即处理,也可以等一会处理或者等合适的时候处理。
3. OS程序员设计进程的时候,进程已经内置了对于信号的识别和处理方式。
4. 给进程产生信号的的信号源非常多。


贰  信号产生

产生信号的方式很多:

2.1 键盘产生信号

1. 键盘怎么产生信号

ctrl+c 就是给目标进程发送2号信号的,相当一部分的信号处理方法是让自己终止。

信号查看:每个信号都有对应的数字和宏。34-64为实时信号(需要立即处理),1-31为普通信号(可以不被立即处理)。

进程收到信号后,会在合适的时候处理信号,处理三选一:默认处理动作,自定义信号处理动作,忽略处理。ctrl+c 发送的2号信号的默认处理动作就是默认处理动作。

更改自定义信号处理

#include <signal.h>
typedef void (*sighandler_t)(int); // 函数指针 类型
sighandler_t signal(int signum, sighandler_t handler);
// 第一个参数为1-31 34-64(信号),第二个参数为一个返回值为void的函数指针
#include <iostream>
#include <unistd.h>
#include <signal.h>void handerSig(int sig)//会把signal函数的第一个参数传过来
{std::cout << "获得了一个信号: " << sig << std::endl;
}
int main()
{signal(SIGINT, handerSig);int cnt = 0;while(true){std::cout << "Ciallo!~ " << cnt++ << std::endl;sleep(1);}return 0;
}

man 7 signal 可以查看具体信号:Term为终止,Ign为忽略...

当所有信号都被自定义时:(9号信号无法被自定义捕捉,仍然可以终止进程)

for (int i = 1; i < 31; i++)signal(i, handerSig);

2. 那什么是目标进程呢~  
进程有前台进程和后台进程
#  ./XXX         为前台进程     键盘产生的信号只能发给前台进程
#  ./XXX &      为后台进程     CTRL+C不做处理
命令行shell进程默认就是前台进程

后台进程无法从标准输入(键盘)中获取内容,前台进程可以(前台进程本质就是要从键盘获取数据,组合键也是键盘输入)。但都可以向标准输出打印。前台进程只有一个,后台进程可以很多个。屏幕相当于共享资源。

jobs       // 查看所有后台进程
fg 任务号  // 后台转前台
ctrl Z     // 把进程切换到后台
bg 任务号  // 让后台进程恢复运行

3. 信号不是立即处理的,需要记录下来,等待合适的时候处理。记录在哪,怎么记录:

task_struct 中的 unsigned int sigs;把这个整数当成位图:
比特位位置为信号编号。
比特位内容为是否收到。
比如0000.......0010为收到了一个二号信号。

发送信号的本质就是:向目标进程写信号,就是修改位图,pid和信号编号。
task_struct属于OS内的数据结构对象,修改位图就是修改内核数据,而内核数据只有OS才能改,所以不管信号怎么产生,必须让给OS发送,OS必须提供系统调用,kill就是系统调用。

2.2 系统调用产生信号

系统调用也可以产生信号。

2.2.1 kill

NAMEkill - send signal to a process
SYNOPSIS#include <sys/types.h>#include <signal.h>int kill(pid_t pid, int sig);
// mykill.cc
#include <iostream>
#include <sys/types.h>
#include <signal.h>
// ./mykill signumber pid
int main(int argc, char *argv[])
{if (argc != 3){std::cout << "./mykill signumber pid" << std::endl;}int signum = std::stoi(argv[1]);pid_t target = std::stoi(argv[2]);int n = kill(target, signum);if (n == 0){std::cout << "send " << signum << " to " << target << "success ~" << std::endl;return 0;}return 0;
}


2.2.2 raise

NAMEraise - send a signal to the caller
SYNOPSIS#include <signal.h>int raise(int sig);
int main()
{for (int i = 1; i < 32; i++)signal(i, handerSig);for (int i = 1; i < 32; i++){sleep(1);if (i == 9 || i == 19)continue;raise(i);     // 给进程发i号信号}return 0;
}

可以验证到9号和19号信号不能被自定义。

2.2.3 abort

NAMEabort - cause abnormal process termination
SYNOPSIS#include <stdlib.h>void abort(void);
int main()
{for (int i = 1; i < 32; i++)signal(i, handerSig);int cnt = 0;while(true){std::cout << "Ciallo!~ " << cnt++ << " pid: " << getpid() <<  std::endl;abort();sleep(1);}return 0;
}

可以看到使用abort时,已经被自定义的6号信号会被恢复成默认的终止

2.3 调用系统命令向进程发信号kill

kill -信号号 pid

2.4 异常产生信号

比如当运行的程序发生除零错误时会收到8号信号SIGFPE:

int main()
{for (int i = 1; i < 32; i++)signal(i, handerSig);int cnt = 0;while(true){std::cout << "Ciallo!~ " << cnt++ << " pid: " << getpid() <<  std::endl;// 发生除零异常int a = 10;a /= 0;sleep(1);}return 0;
}

同样,野指针会收到11号信号,SIGSEGV段错误。

在信号的处理动作中,有Core和Term两种终止方式,有什么区别呢~

Term直接退出

Core:核心,会在当前路径下形成一个文件,进程异常退出时,进程在内存中的核心数据从内存拷贝到磁盘, 形成一个文件,叫做核心转储,支持Debug。云服务器上,core和dump功能默认是被禁止的。

可以 ulimit -c 40960 打开此功能~ 若打开了此功能,遇到异常,会终止进程并core dump,在运行崩溃后,再gdb时输入core-file core就可以查看错误~ 叫做事后调试

int main()
{pid_t id = fork();if (id == 0){sleep(2);printf("Ciallo~\n");printf("Ciallo~\n");printf("Ciallo~\n");printf("Ciallo~\n");printf("Ciallo~\n");int a = 10;a /= 0;printf("Ciallo~\n");printf("Ciallo~\n");printf("Ciallo~\n");exit(1);}int status = 0;waitpid(id, &status, 0);printf("signal: %d, exitcode: %d, core dump: %d\n", (status&0x7F), (status>>8)&0xFF, (status>>7)&0x1); return 0;
}

2.5 软件条件产生信号

比如一个管道文件,把读端关闭时,写端就会自动崩溃,发送信号SIGPIPE~
管道文件是一种软件,读端关闭就是软件条件不满足,会终止进程~

再举个例子:

alarm:设置闹钟:
alarm(5): 设定时间5s结束,OS就会给进程发信号~~
alarm(0): 取消闹钟~
函数返回值为0或者以前设定的剩余秒数。

NAMEalarm - set an alarm clock for delivery of a signal
SYNOPSIS#include <unistd.h>unsigned int alarm(unsigned int seconds);
void handerSig(int sig)//会把signal函数的第一个参数传过来
{std::cout << "获得了一个信号: " << sig << std::endl;exit(13);
}
int main()
{for (int i = 0; i <= 32; i++)signal(i, handerSig);alarm(1);//闹钟1秒后响int cnt = 0;while (true){std::cout << "count: " << cnt++ << std::endl; // IO打印效率低cnt++;}return 0;
}

会发现程序一秒内循环的次数很少,是因为IO效率很低,可以更改为下代码:

int cnt = 0;
void handerSig(int sig)
{std::cout << "获得了一个信号: " << sig << "cnt: " << cnt << std::endl;exit(13);
}
int main()
{signal(SIGALRM, handerSig);alarm(1);//闹钟1秒后响while (true)cnt++;return 0;
}

现在想要实现一个程序,进程一直暂停着,一旦收到一个信号,就唤醒一次,完成一些任务,可以这样实现:

#include <iostream>
#include <vector>
#include <functional>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
////////////////////func///////////////////////////////
void Sched()
{std::cout << "进程调度" << std::endl;
}
void MemManger()
{std::cout << "内存管理" << std::endl;
}
void Fflush()
{std::cout << "刷新程序" << std::endl;
}
///////////////////////////////////////////////////////
using func_t = std::function<void()>; // 返回值为void的函数 命名为func_t类型
std::vector<func_t> funcs; // 返回值为void的函数列表// 每隔一秒,完成一些任务
void handerSig(int sig) //会把signal函数的第一个参数传过来
{std::cout << "##############################################" << std::endl;for (auto f : funcs) // 执行函数列表内的函数{f();}std::cout << "##############################################" << std::endl;int n = alarm(1); // 重新设定一个闹钟,成为循环
}
int main()
{funcs.push_back(Sched);funcs.push_back(MemManger);funcs.push_back(Fflush);// 遇到SIGALRM信号,执行handerSig,handerSig中又会设置下一个闹钟signal(SIGALRM, handerSig); alarm(1); // 初始闹钟while (true){// 进程一直暂停,一旦收到一个信号,唤醒一次pause();}return 0;
}

操作系统就是这种类型的程序,进程暂停着,以信号作为驱动。

操作系统中又很多闹钟,可以将这些闹钟管理起来,链表结构体内容为:

struct timer_list {struct list_head entry; // 闹钟列表起始unsigned long expires; // 最短的闹钟剩余时间void (*function)(unsigned long); // 收到信号后执行的任务unsigned long data;struct tvec_t_base_s *base;
};

用堆结构将闹钟剩余时间最短的闹钟,成为堆头,保存在结构体中,到时间后给进程发SIGALRM信号。

所以闹钟属于软件条件的一种。


叁  信号保存

3.1 相关概念

实际执行信号的处理动作称为信号递达(Delivery)
信号递达有三种自定义,默认,忽略。
信号从产生到递达之间的状态,称为信号未决(Pending)。信号还在位图中,未被处理。

进程可以选择阻塞 (Block )某个信号。也叫屏蔽信号。
被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是递达之后可选的一种处理动

3.2 在内核中的表示

pending表是位图,用来保存收到的信号,unsigned int pending有32个比特位对应32种信号,叫做未决表。比特位位置为信号编号。比特位内容为是否收到

block表也是一张位图,用来保存要阻塞的信号,比特位位置为信号编号。比特位内容为是否阻塞

所以pending & (~block) 哪些信号能被递达

handler表是一个函数指针数组,数组下标为信号编号,内容为32种信号对应的函数方法。signal函数就是在修改handler表。把对应的函数地址填入数组。SIG_DFL为默认处理,SIG_IGN为忽略处理。

3张表共同完成了对32个信号的描述。

3.3 sigset_t

每个信号只有一个bit的未决标志, 非0即1, 不记录该信号产生了多少次,阻塞标志也是这样表示的。因此, 未决和阻塞标志可以用相同的数据类型sigset_t来存储, sigset_t称为信号集, 这个类型可以表示每个信号的“有效”或“无效”状态, 在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞, 而在未决信号集中“有 效”和“无效”的含义是该信号是否处于未决状态。下一节将详细介绍信号集的各种操作。阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。

3.4 信号集操作函数

#include <signal.h>
// 位图全置0 , 1
int sigemptyset(sigset_t *set);  
int sigfillset(sigset_t *set);
// 把位图第几号信号置1 ,0
int sigaddset(sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
// 判断位图第几号信号是否为1
int sigismember(const sigset_t *set, int signo);

3.4.1 sigprocmask

此函数用于更改或设置block表:

NAMEsigprocmask, rt_sigprocmask - examine and change blocked signals
SYNOPSIS#include <signal.h>int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
RETURN VALUEsigprocmask() returns 0 on success and -1 on error. 

第一个参数有下三种:

  • SIG_BLOCK:想要阻塞哪位就在set中把哪位置1
  • SIG_UNBOLOCK:想要取消阻塞哪位就在set中把哪位置1
  • SIG_SETMASK:把原BLOCK表替换为set,最方便

第三个参数为输出型参数,返回旧的BLOCK表,以防改错~

3.4.2 sigpending

获取当前pending信号集(不修改)

NAMEsigpending, rt_sigpending - examine pending signals
SYNOPSIS#include <signal.h>int sigpending(sigset_t *set);
RETURN VALUEsigpending() returns 0 on success and -1 on error. 

pending表的修改就是上5种信号产生的方式~

3.4.3 整合代码

#include <iostream>
#include <cstdio>
#include <vector>
#include <functional>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>void PrintPending(sigset_t pending)
{printf("我是一个进程( %d ), pending: ", getpid());for (int signo = 31; signo >= 1; signo--){if (sigismember(&pending, signo)){std::cout << "1";}else{std::cout << "0";}}std::cout << std::endl;
}void handler(int sig)
{std::cout << "#######################################" << std::endl;std::cout << "递达" << sig << "信号 ~"<< std::endl;sigset_t pending;int m = sigpending(&pending);PrintPending(pending);// 若是0000 0010 处理完2号才置0// 若是0000 0000 处理前2号就置0std::cout << "#######################################" << std::endl;
}int main()
{signal(SIGINT, handler);// 1. 屏蔽2号信号, block信号集2号位置1sigset_t block, oldblock;sigemptyset(&block);sigemptyset(&oldblock);sigaddset(&block, SIGINT); // 到此只改了变量, 需要更新到进程中// for (int i = 0; i <= 31; i++) // 会屏蔽所有信号, 除9外// {//     sigaddset(&block, i);// }int n = sigprocmask(SIG_SETMASK, &block, &oldblock);(void)n;// 4. 重复获取打印int cnt = 0;while (true){// 2. 获取pending信号集sigset_t pending;int m = sigpending(&pending);// 3. 打印PrintPending(pending);if (cnt == 10){// 5. 恢复对2号信号的阻塞, 会直接递达2号信号// 若不对2号信号做处理, 就会按默认直接终止进程std::cout << "解除对2号信号的屏蔽" << std::endl;sigprocmask(SIG_SETMASK, &oldblock, nullptr);}sleep(1);cnt++;}return 0;
}

若不改变处理动作:

最后结果:

可以发现在递达前,pending表的对应位置就置0了。


肆  信号处理

到这里会有两个问题:
信号处理有三种动作,是怎么被处理的?
信号在合适的时间被处理,什么是合适的时间?

4.1 初步结论

信号处理的方式如下:

在执行main函数时,会因为系统调用等原因从用户态进入内核态。在内核处理完后就会用do_signal函数检查是否有待处理的信号(三个表中pending位为1,block位为0),如果有就会进行处理:
默认处理:如2号信号,内核直接终止进程。
忽略处理:返回用户态跳转的位置继续运行。
自定义处理:从内核态返回用户态,以用户身份执行自定义的函数,执行完后调用系统调用sigreturn返回内核态,在内核态执行sys_sigreturn后返回用户态跳转的位置继续运行。

自定义处理的整个流程是一个无限大符号的样子:

流程中会有四次身份切换在**态只能以**身份执行,防止自定义函数中有非法操作,内核权限又很高。中间的交点是检查是否有待处理的信号的时间节点。

所有的进程都会被调度,当一个进程的时间片耗尽时,会被操作系统拿下,这时就会进入内核态被检查信号~

4.2 操作系统是怎么运行的

4.2.1 硬件中断

在硬件视角,会有一个中断控制器连接着许多外设,每当外设准备好了,就会发送硬件中断,替换中断号n,中断控制器被激活会通知CPU,CPU得知有中断会到中断控制器获取中断号。
在软件方面,操作系统中会有一张中断向量表IDT,其中维护着外设设备对应的函数指针数组,数组下标对应中断控制器中的中断号n,CPU得知那个外设设备准备好了,就执行那个中断服务。

操作系统不关心外设设备是否准备好,而是准备好会叫操作系统。

发中断->发信号  保存中断->记录信号 
中断号->信号编号  处理中断->处理信号+自定义捕捉
软件中的信号就是在模仿硬件中的中断的思想 ~

当程序发生除零错误时,CPU内部会有一个溢出标志位EFLAGS,此标志位有效时,成为一种CPU内部触发的中断,在中断向量表中选择并执行中断服务处理异常,给目标进程发信号。

其他野指针,重复释放,缺页中断等都会被转化成硬件中断,执行对应的方法。

4.2.2 时钟中断

操作系统没有硬件中断要处理的时候,在干嘛呢~
答案是一直暂停着但它还有调度进程等功能,就需要有一个时钟源,以固定的频率向CPU发中断~执行对应的shedule函数了。
所以操作系统本质是在硬件驱动下进行调度的操作系统是一个基于中断进行工作的软件。

时钟源后来被集成到CPU中,成为了主频。

4.2.3 软中断

CPU内部可以让软件触发中断吗~
在x86_64下CPU的指令集中会有一个int 0x80 或 syscall,可以自动让CPU触发一次中断。

我们以前用的open,fork,umask等系统调用其实不是OS提供的,OS只提供syscall和系统调用号

在操作系统中有一个全部系统调用的函数指针表,每一个系统调用都有唯一的下标,这个下标叫做系统调用号

// 系统调用函数指针表。用于系统调用中断处理程序(int 0x80),作为跳转表。
fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,
sys_write, sys_open, sys_close, sys_waitpid, sys_creat, sys_link,
sys_unlink, sys_execve, sys_chdir, sys_time, sys_mknod, sys_chmod,
sys_chown, sys_break, sys_stat, sys_lseek, sys_getpid, sys_mount,
sys_umount, sys_setuid, sys_getuid, sys_stime, sys_ptrace, sys_alarm,
sys_fstat, sys_pause, sys_utime, sys_stty, sys_gtty, sys_access,
sys_nice, sys_ftime, sys_sync, sys_kill, sys_rename, sys_mkdir,
sys_rmdir, sys_dup, sys_pipe, sys_times, sys_prof, sys_brk, sys_setgid,
sys_getgid, sys_signal, sys_geteuid, sys_getegid, sys_acct, sys_phys,
sys_lock, sys_ioctl, sys_fcntl, sys_mpx, sys_setpgid, sys_ulimit,
sys_uname, sys_umask, sys_chroot, sys_ustat, sys_dup2, sys_getppid,
sys_getpgrp, sys_setsid, sys_sigaction, sys_sgetmask, sys_ssetmask,
sys_setreuid, sys_setregid
};

从用户层面调用open方法时其实干了两件事:
move eax 5 :把函数指针表中的5为下标的sys_open函数地址放到eax寄存器中。
int 0x80或syscall: 把执行地址改到0x80位置。

在内核层面,中断向量表中就有0x80为地址的函数CallSystem,此函数也干了两件事:
获取系统调用号neax寄存器中已经存了目标函数指针的下标5。
调用系统调用方法:sys_call_table[5](); _system_call:call [_sys_call_table+eax*4]

这样就完成了open的系统调用,open是被glibc封装的。(其他的也是如此)

CPU内部的软中断,比如int 0x80或者syscall,我们叫做 陷阱
CPU内部的软中断,比如除零/野指针等,我们叫做 异常

4.3 内核态和用户态

所以系统调用的过程也是在进程地址空间上进行的。只不过从用户区跳转到内核区。
所有函数调用都是地址空间的跳转

下图可知,无论进程如何调度,都能找到操作系统。因为所有进程的内核区经过自同一个内核页表。

用户和内核都在同一个地址空间上了,用户能不能直接访问内核代码呢?
不能~ OS有自保功能只允许系统调用方式进行访问。CPU中的cs寄存器的后两位(CPL当前权限级别)(前面是代码段地址)表示在什么区(00为内核,11为用户,int 0x80 syscall就是在改这两位改变身份的)

用户态:以用户身份,只能访问自己的【0,3GB】
内核态:以内核身份,通过系统调用的方式访问【3,4GB】

4.4 sigaction

NAMEsigaction, rt_sigaction - examine and change a signal action
SYNOPSIS#include <signal.h>int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);// 第一个参数信号编号,第二个参数输入型,第三个参数输出型
RETURN VALUEsigaction() returns 0 on success; on error, -1 is returned, and errno is set to indicate the error.struct sigaction { // 实时信号也可以,不管void     (*sa_handler)(int); // 和signal第二个参数一样void     (*sa_sigaction)(int, siginfo_t *, void *); // 不管sigset_t   sa_mask; // 信号集类型int        sa_flags; // 不管void     (*sa_restorer)(void); // 不管
};

作用同signal,只不过功能更多。

当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞到当前处理结束为止。 如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。

当前处理的信号会被自动屏蔽,那想要处理目标信号的时候屏蔽其他信号就可以使用sa_mask~

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <cstdlib>void handler(int signum)
{std::cout << "hello signal: " << signum << std::endl;while (true){// 不断获取pending表sigset_t pending;sigpending(&pending);for (int i = 31; i >= 1; i--){if (sigismember(&pending, i))std::cout << "1";elsestd::cout << "0";}std::cout << std::endl;sleep(1);}exit(0);
}int main()
{struct sigaction act, oldact;act.sa_handler = handler;sigemptyset(&act.sa_mask);sigaddset(&act.sa_mask, 3);sigaddset(&act.sa_mask, 4);act.sa_flags = 0;sigaction(SIGINT, &act, &oldact); // 捕捉2号信号,234都被屏蔽while (true){std::cout << "Ciallo~" << getpid() << std::endl;sleep(1);}return 0;
}

伍  可重入函数

上图是带头单链表的头插,插入第一个节点的函数进行到一半时,收到了信号捕捉处理,刚好信号捕捉处理中又有insert函数,去插入第二个节点了。最终导致了node2丢失,内存泄漏

insert函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入,insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为不可重入函数,反之,如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant) 函数

不可重入函数:
调用了malloc或free,因为malloc也是用全局链表来管理堆的。
调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。

可重入函数:
函数中只有自己的临时变量。


陆  volatile

以下代码会根据编译的优化等级产生不同的结果:

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <cstdlib>int flag = 0;void handler(int signum)
{std::cout << "更改全局变量 ~ flag: " << flag << "-> 1" << std::endl;flag = 1;
}int main()
{signal(2, handler); // 2号信号处理改成更改flag值~while (!flag); // flag = 0时死循环std::cout << "procress quit normal~" << std::endl;return 0;
}

在O1以上的优化下,flag变量会被优化到CPU的寄存器中,更改时只改变了物理内存中flag的值。导致了flag在物理内存中值为1,CPU中值为0。寄存器覆盖了进程看到变量的真实情况。一直在死循环。

volatile int flag = 0; // 保证内存空间的可见性

在全局变量前加上volatile关键字,可以保证内存空间的可见性,每次从内存中检测变量的值。这样优化到满,也可以正常运行。


柒  SIGCHLD信号

sigchld 编号17,子进程在终止时会给父进程发SIGCHLD信号,该信号的默认处理动作是忽略

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <cstdlib>
#include <sys/wait.h>void WaitAll(int num)
{while (true){pid_t n = waitpid(-1, nullptr, WNOHANG); // -1为任意一个子进程,WNOHANG非阻塞轮询,防止有子进程没退出卡住if (n == 0){break;}else if (n < 0){std::cout << "waitpid error" << std::endl;break;}}std::cout << "父进程收到信号: " << num << std::endl;
}int main()
{// 父进程signal(SIGCHLD, WaitAll);for (int i = 10; i < 10; i++){pid_t id = fork();if (id == 0){// 子进程sleep(3);std::cout << "我是子进程~" << std::endl;if (i <= 6)exit(3);elsepause();}while (true){// 父进程std::cout << "我是父进程~" << std::endl;sleep(1);}}return 0;
}

WNOHANG可以使多个子进程退出一些个,还剩几个子进程的时候不在waitpid处卡住,使父进程不能继续运行。(非阻塞轮询

SIGCHILD的默认处理动作(SIG_DEF)是忽略(ign),子进程退出后会僵尸。
若把SIGCHILD的处理动作改成忽略(SIG_IGN),系统会自动回收子进程,父进程拿不到退出码。

int main()
{// 父进程signal(SIGCHLD, SIG_IGN);for (int i = 10; i < 10; i++){pid_t id = fork();if (id == 0){// 子进程sleep(3);std::cout << "我是子进程~" << std::endl;exit(3);}while (true){// 父进程std::cout << "我是父进程~" << std::endl;sleep(1);}}return 0;
}

~  完  ~

http://www.dtcms.com/a/416352.html

相关文章:

  • 深圳住房和建设局网站故障做一个电商网站需要多少钱
  • 佛山网站建设策划一个网站怎么做镜像站
  • 长春建站推荐南通网站制作计划
  • TDengine 时序函数 DERIVATIVE 用户手册
  • 网站做弹窗asp.net网站开发实例
  • 潍坊市建设一体化平台网站竞价关键词排名软件
  • STM32学习路线开启篇:OLED调试工具
  • 网站怎么做微信扫描登录网站建设网站如何挂到网上
  • AI 算力加速指南:Stable Diffusion 本地部署全维度优化,从 “卡成 PPT” 到 “秒出图像”(三)
  • 安徽六安什么时候解封上海网站建设优化seo
  • 怎样向网站上传照片著名的营销成功的案例
  • 有域名可以自己做网站吗网站开发技术有哪些
  • 上海seo关键词优化湖南企业竞价优化服务
  • 温州企业做网站云虚拟主机做网站
  • 求几个夸克没封的a站2023怎么样让网站快速收录
  • 河北住房建设厅网站首页免费ppt模板大全免费下载网站
  • 哈尔滨专业优化网站个人西安seo外包平台
  • 手机端网站如何优化百度中搜到网站名字
  • 交易网站模板wordpress主题qux_v7.1
  • 国家商用密码SM算法
  • 西安警方通报: 西安自动seo优化
  • 最近做网站开发有前途没来宾网站建设公司
  • 网站续费怎么做分录wordpress 会员投搞
  • 富文本编辑器Tinymce的使用、行内富文本编辑器工具栏自定义class、katex渲染数学公式
  • 台州seo网站排名敦煌手机网站设计
  • 82家合法现货交易所名单宁波专业seo服务
  • 网站没快照热点新闻事件2023
  • linux内核学习(三)---RK3568内核新版本移植(中)
  • 金华品牌网站建设网站图标在哪里做修改
  • 台州做网站联系方式wordpress改地址错误