建设用地规划许可证去哪个网站查,一个外贸网站要多大的空间比较好,中石化网站群建设,python人网站开发案例目录 1.信号的概念
2.信号的产生
3.信号的保存
4.信号的捕捉
信号的其它内容#xff1a;
SIGCHLD信号 1.信号的概念
在Linux中#xff0c;信号是一种用于进程之间通信的基本机制。它是一种异步事件通知#xff0c;用于通知进程发生了某些事件。如下是一些常见的Linux信…目录 1.信号的概念
2.信号的产生
3.信号的保存
4.信号的捕捉
信号的其它内容
SIGCHLD信号 1.信号的概念
在Linux中信号是一种用于进程之间通信的基本机制。它是一种异步事件通知用于通知进程发生了某些事件。如下是一些常见的Linux信号类型
SIGINT (2)中断进程通常由终端产生例如用户按下CtrlC。 SIGKILL (9)立即终止进程无法被捕获或忽略。 SIGTERM (15)请求终止进程可以被捕获或忽略。 SIGQUIT (3)请求进程退出并生成核心转储文件可以被捕获或忽略。 SIGSTOP (17)暂停进程的执行无法被捕获或忽略。 SIGCONT (19)恢复进程的执行无法被捕获或忽略 这些信号在进程控制、异常处理和进程间通信中扮演着重要角色。请注意信号只是通知进程发生了什么事件并不传递任何数据。进程对不同信号有不同的处理方式可以指定处理函数、忽略或保留系统的默认值。信号机制在Linux编程中非常重要帮助实现进程之间的协作和控制。
2.信号的产生
先举两个样例
eg1
首先我们编写一个死循环代码编译运行后我们的命令行就不再有用了现在是前台程序只运行当前的程序当我们编译时加上使他成为后台程序此时的命令行也可以继续使用 程序在运行的时候前台程序只能有一个后台程序可以有多个。后台程序在运行时我们的键盘可以输入数据指令可以运行。
一般操作系统会自动根据情况把shell程序提到前台或者后台。下面的指令对shell无效。 前后台程序切换 ./可执行 把程序放到后台 jobs 查看后台任务 fg number任务编号 把任务放到前台 ctrlz 再加 bg number 把后台任务转到前台 ctrl\ 默认终止 ctrl z 暂停程序先放到后台 而这就是信号的产生除此之外操作系统知晓键盘的输入也是一种信号
eg2:当键盘的某个按钮被按下的时候就会产生高电平信号间接给cpucpu得知了之后某个按钮的高电平发生中断就产生对应的数据。
而信号的产生就是用软件来模拟中断行为。我们的指令都是发出信号
例如接口signal 可以发出我们需要的信号。
如下一段代码
#includeiostream
#includeunistd.h
#include signal.h
#include stdlib.hvoid handler(int signo)
{std::cout获得一个signo信号std::endl;exit(1);
}int main()
{signal(2,handler);while(true){std::coutpid:getpid(),i am running......std::endl;sleep(1);}return 0;
} 再运行的时候我们ctrlz此时退出进程就会获得一个为2的信号。
因此信号的产生可以通过键盘发出对于我们的linux也是有许多信号的kill -l 其中没有0号信号从1-31的信号我们把它叫做普通信号没有3233信号从34到64的信号我们把它叫做实时信号。这些信号的本质就是一些函数指针数组对应的下标就与他们的编号有关。
对于普通信号进程是否收到了普通信号操作系统pcb中会用一张位图来表示利用位图中的第几个比特位表示编号0表示没收到1表示收到。
无论信号有多少种都是只能让os来写(写)信号,因为os是进程的管理者。
了解到了信号的接收因此我们在编写程序时就可以直接发送信号之后自动运行对应handler方法例如之前我们使用kill -9杀进程现在我们发送一个为9的信号此时自定义它的处理方法例如只是打印一句话那么我们kill -9的指令就不会再杀掉我们的进程而是打印一句话。
但实际上并不可以操作系统对于某些信号是不可以被自定义捕捉的。
除此以外Linux提供了三种接口供我们产生信号。
方式一通过键盘组合键发送产生信号。
方式二通过函数接口
接口 raise 可以自己给自己发送任意信号 接口 abort 收到信号后终止运行 方式三通过异常 以我们熟知的除零错误为例首先除零错误并不是语言错误而是进程错误再cpu中通过各个寄存器来计算除零此时cpu中还有表示状态的寄存器当发生除零问题后状态寄存器就会产生溢出标记位从而转化为信号就是信号8 SIGFPE 也就是flaot point exception。
当然发出信号也不仅仅可能是因为异常而导致的也有可能是闹钟响了
方式四由软件条件产生信号
alarm接口可以设置闹钟 #includeiostream
#includeunistd.h
#include signal.h
#include stdlib.h
int cnt0;
void handler(int signo)
{std::cout获得一个signo信号alarm is:cntstd::endl;exit(1);
}int main()
{std::coutpid:getpid()std::endl;//本质上就是修改函数指针数组的位置signal(14,handler);//设置1s闹钟,到点了终止进程alarm(1);while(true){//coutcntendl; 可以看出外设是很慢的cnt;}} 操作系统的时间 当我们电脑关机了程序结束了再次重新启动我们会发现时间永远是跟着走的实际上即使关机了在电脑里也会有一个纽扣电池一直给硬件供电固定时间间隔计数再将计数器转换为时间戳给我们的电脑。CMOS周期性的高频的发送时间中断。 3.信号的保存 . 信号其他相关常见概念 实际执行信号的处理动作称为信号递达 (Delivery) 信号从产生到递达之间的状态 , 称为信号未决 (Pending) 。 进程可以选择阻塞 (Block ) 某个信号。 被阻塞的信号产生时将保持在未决状态 , 直到进程解除对此信号的阻塞 , 才执行递达的动作 . 注意 , 阻塞和忽略是不同的 , 只要信号被阻塞就不会递达 , 而忽略是在递达之后可选的一种处理动作 递达就是开始处理信号当信号被记录再为途中时就是信号未决状态阻塞被阻塞的信号一直处在未决状态只有当阻塞取消时才进入递达状态。
阻塞与忽略是有区别的忽略本身没有阻塞而是递达处理了信号效果为忽略而阻塞是没有抵达且没处理。
了解了以上概念因此再管理信号的状态时os就需要维护这三张位图表用来表示阻塞未决递达这三个状态的信号。 比特位的位置代表信号的编号
比特位的内容对特定信号进行阻塞还是屏蔽。
每个信号都有两个标志位分别表示block(阻塞)和pending(未决)其次还有一个函数指针表示要处理的方法。
void handler(int signo)
{coutsigno is signoendl;exit(1);
}
int main()
{//发送2信号signal(2,signo);//把信号的粗粒设置为原来默认的signal(2,SIG_DFL);//当然还可以把信号忽略signal(2,SIG_IGN);std::coutmy pid id:getpid()endl;while(true){couti am running.....endl;sleep(1);}}
由于有这么多信号集操作系统还提供了许多信号及操作接口 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 signo); //对指定信号添加到指定的位图中 int sigdelset(sigset_t *set, int signo); int sigismember const sigset_t *set, int signo); //判定一个信号是否在为位图中 对于block表的修改
sigprocmask 调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集)。 如下代码 int main()
{//例如对2号信号屏蔽coutmy pid isgetpid()endl;//先定义两个信号集位图sigset_t block,oblock;//先对信号集清空sigemptyset(block);sigemptyset(oblock);//其次对2号信号添加到信号集sigaddset(block,2); //当前并没有让操作系统2信号屏蔽只是语言层面的定义sigaddset(oblock,2);sigprocmask(SIG_BLOCK,block,oblock); //真正让操作系统屏蔽、更改信号while(true){sleep(1);}return 0;
} 此时我们再发2号信号就没有作用了ctrlc也无法中断程序。
既然如此那么我们是否可以将一个程序的所有信号屏蔽这样他就有金刚不坏之身谁也干不掉他实际上并是不是所有的信号你都能屏蔽就跟不是所有的信号的处理可以自定义是一样的。
比如说9号信号就无法被屏蔽。
那么pending表的修改接口 sigpending
重要的是获取pending表. 接下来我们用一个整体的实例来认识这些接口
void printpending(const sigset_t pending)
{for(int signo31;signo0;signo--){if(sigismember(pending,signo)){cout1;}else{cout0;}}cout\n;
}
//自定义捕捉
void handler(int signo)
{cout已接受到信号signoendl;//exit(1);}int main()
{//例如对2号信号屏蔽coutmy pid isgetpid()endl;signal(2,handler);//先定义两个信号集位图sigset_t block,oblock;//先对信号集清空sigemptyset(block);sigemptyset(oblock);//其次对2号信号添加到信号集sigaddset(block,2); //当前并没有让操作系统2信号屏蔽只是语言层面的定义sigaddset(oblock,2);sigprocmask(SIG_BLOCK,block,oblock); //真正让操作系统屏蔽、更改信号//下打印pending表int cnt0;sigset_t pending;while(true){sigpending(pending);printpending(pending);sleep(1);cnt;if(cnt5){//直到5S,解除2信号的屏蔽cout解除对2号信号的屏蔽,2号准备抵达endl;sigprocmask(SIG_SETMASK,oblock,nullptr); //设置为旧的信号 }}return 0;
} 运行结果如图 4.信号的捕捉
信号在什么时候去被捕捉处理呢在合适的时候---从内核态返回到用户态的时候进行信号的检测和信号的处理。 内核态内核态是操作系统的一种状态能够大量访问资源 用户态用户态是一种受控的转台能够访问的资源是有限 用户想要访问操作系统只能通过系统调用的方式访问。 首先无论进程如何调度cpu都会找到os我们的进程的所有代码的执行都可以在地址空间中通过跳转的方式进行调用和返回。 那么对于系统的信号的捕捉首先介绍第一个接口sigaction 第三个参数表示把旧的handler表返回给我达尔戈参数就是新的handler的设置第一个参数为信号编号接口的作用是检测和修改信号动作。
返回类型是sigaction的结构体类型其中有五个字段。其中我们比较重点关注的是sa_mask字段
如果在调用信号处理函数时除了当前信号被屏蔽外还希望屏蔽些别的信号此时sa_mask就是需要被额外屏蔽的信号。
以该代码为例
#includesignal.h
#includeunistd.h
#includeiostream
using namespace std;
void print(sigset_t pending);
void handler(int signo)
{cout接收到信号signo......endl;while(true){//获取当前pending列表sigset_t pending;sigpending(pending);print(pending);sleep(1);}
}
void print(sigset_t pending)
{for(int signo31;signo0;signo--){if(sigismember(pending,signo)){cout1;}else{cout0;}}coutendl;
}
int main()
{coutmy pid is getpid()endl;//定义新的与旧的actstruct sigaction act,oact;//设置handler为当前自定义的处理方法act.sa_handlerhandler;sigaction(2,act,oact);while(true) sleep(1);return 0;
}用改接口接受2号信号时和之前一样运行程序第一次我们ctrlc,发出2信号时接收到2好信号但自此之后的2好信号都被屏蔽掉了再次crtlc时信号无法被接受处于未决状态。
例如当我们要修改信号2时这里默认会自动屏蔽信号2如下图 。
信号的其它内容
可重入函数 数被不同的控制流程调用 , 有可能在第一次调用还没返回时就再次进入该函数 , 这称 为重入,insert 函数访问一个全局链表 , 有可能因为重入而造成错乱 , 像这样的函数称为 不可重入函数 , 反之 , 如果一个函数只访问自己的局部变量或参数, 则称为可重入 (Reentrant) 函数。 如果一个函数符合以下条件之一则是不可重入的 : 调用了 malloc 或 free, 因为 malloc 也是用全局链表来管理堆的。 调用了标准 I/O 库函数。标准 I/O 库的很多实现都以不可重入的方式使用全局数据 在这里我们就这样理解住执行流与信号捕捉流是两种不同的流。 关键字volatile volatile 作用保持内存的可见性告知编译器被该关键字修饰的变量不允许被优化对该变量 的任何操作都必须在真实的内存中进行操作。 那么对于信号有什么作用呢 int flag 0;
void handler(int sig)
{printf(chage flag 0 to 1\n);flag 1;
}
int main()
{signal(2, handler);while(!flag);printf(process quit normal\n);return 0;
} 优化情况下键入 CTRL - C ,2 号信号被捕捉执行自定义动作修改 flag 1 但是 while 条件依旧满足 , 进 程继续运行但是很明显flag 肯定已经被修改了但是为何循环依旧执行很明显 while 循环检查的 flag 并不是内存中最新的flag 这就存在了数据二异性的问题。 while 检测的 flag 其实已经因为优化被放在了 CPU寄存器当中。如何解决呢很明显需要 volatile。 实际中在gcc中也是有自带优化的选项。 SIGCHLD信号
我们 早已经了解到子进程在退出的时候是要给父进程发送退出信息的不然父进程还要维护一份没必要的资源而子进程是给父进程发送什么样的信号呢---SIGCHLD
#include stdio.h
#include stdlib.h
#include signal.h
void handler(int sig)
{pid_t id;//收到退出信号 等待子进程while( (id waitpid(-1, NULL, WNOHANG)) 0){printf(wait child success: %d\n, id);}printf(child is quit! %d\n, getpid());
}
int main()
{signal(SIGCHLD, handler);pid_t cid;if((cid fork()) 0){//childprintf(child : %d\n, getpid());sleep(3);exit(1);}while(1){printf(father proc is running\n);sleep(1);}return 0;
} 可以看到子进程退出时时回给父进程发信号的。
在Linux中支持手动忽略信号SIGCHDL,可以不用wait子进程。退出自动回收。