信号是Linux unix系统下经典的消息机制,系统利用信号完成处置进程(杀死与挂起)
kill -l 查看系统下支持的信号
1~31Unix经典信号(软件开发) 34~64自定义信号(驱动层开发) 32 33系统隐藏信号(NPTL)系统预留给线程使用。
Linux系统下发送信号的几种方式
1.终端组合按键触发信号
ctl+c(SIGINT(2))
ctl+\(SIGQUIT(3))
ctl+z(SIGTSTP(20))(jobs查看挂起进程,fg 序号 运行到前台 bg 序号 运行到后台)
这三个信号的目标都是唯一的终端前台进程。
核心已转储:
段错误(核心已转储):当进程异常退出,系统可以将异常信息存储在core文件中 后序使用gdb快速定位异常。
默认情况下系统不允许生成core文件,可以使用ulimit 命令允许生成。
调试:gcc 名 -g -o 名 gdb 名
改成4096或者4096的整数倍。
这样就会有core文件了 然后gdb 名 core就ok了,简单的能用,特别复杂的错误不行。
2.命令触发信号
kill -signo pid//向任意进程发送任意信号
3.函数触发信号
kill(pid_t pid int signal);//向任意进程发送任意信号
raise(int signo);//向调用进程发送任意信号
abort();//向调用进程发送SIGABRT(6)信号
用kill函数实现kill命令。
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>
#include<string.h>
int main(int argc,char** argv){
if(argc<3){
printf("try agin\n");
exit(0);
}
kill(atoi(argv[2]),atoi(argv[1]));
return 0;
}
4.硬件异常触发信号
系统向用户进程发送信号。
段错误(核心已转储)用户违法访问内存引发异常,系统会向用户发送SIGSEGV(11)杀死目标。
总线错误(核心已转储)内存访问越界,系统发送SIGBUS(7)信号杀死目标。
浮点数例外(核心已转储)如果cpu出现计算异常,系统发送SIGFPE(8)杀死目标进程。
5.软条件触发信号
(1).alarm(20)定时器,定时到时系统向定时进程发送SIGALRM(14)通知其到时。但是这个信号默认杀死进程。
(2)管道读端关闭,写端向管道写数据,系统会发送SIGPIPE(13),杀死写端进程。
unsigned int alarm(unsigned int seconds)//定时函数,返回值是之前设置的闹钟的剩余秒数。
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>
int main()
{
int a = alarm(5);
printf("%d\n",a);
sleep(2);
a=alarm(2);
printf("%d\n",a);
while(1){
}
return 0;
}
信号的三种行为和五种默认处理动作
默认行为SIG_DFL,忽略行为SIG_IGN捕捉行为SIG_ACTION
五种处理动作:TREM(杀死进程)CORE(杀死进程核心转储)STOP(挂起进程)CONT(继续进程)IGN(忽略信号)
每个信号都被设置了自身的行为与动作,后续可以通过修改改变信号行为。
行为的优先级大于动作。
如果将某个信号的行为改为忽略,此信号失效(无法处置进程)
捕捉函数,用户自定义行为,可以对某个信号设置与函数的绑定,信号触发调用对应函数。捕捉也可以让信号失去原有效果,无法杀死或挂起进程。
struct sigaction act;//信号行为结构体
act.sa_handler=sig;//用户设置信号行为(void sig(int)用户自定义行为)
act.sa_flags=0;//默认选项
sigemptyset(&act.sa_mask);//初始化临时屏蔽字:如果不同信号绑定一个捕捉函数,捕捉函数中有对全局资源的访问,那么两个信号同时递达,引发冲突异常。可以采用临时屏蔽字避免此情况的发生,在某个信号执行捕捉函数时临时屏蔽其他信号。
sigaction(SIGINT,&act,&oldact|NULL);//修改信号行为,传入新结构体,传出原有行为结构体。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
void sig(n)
{
printf("你好 %d\n",n);
}
int main()
{
struct sigaction act,oct;
act.sa_handler=sig;
act.sa_flags=0;
sigemptyset(&act.sa_mask);
sigaction(SIGINT,&act,&oct);
while(1) sleep(1);
return 0;
}
信号的传递过程
如果信号无法通过未决信号集,信号直接丢弃。
信号通过未决信号集,系统会将对应位翻转为1,表示待处理。
信号从未决态转换为递达态,系统会将未决信号集对应位设置为0.
用户可以自行设置屏蔽字,实现阻塞信号的目的。
经典信号不支持排队。
要想查看进程中信号的实时屏蔽情况,应该查看哪个信号集?
查看未决信号集,用户可以读取未决信号集。
相关函数
sigset_t set//信号集类型
sigemptyset(&set)//初始化函数,将所有位初始化0
sigfillset(&set)//初始化函数,将所有位初始化1
sigaddset(&set,int signo)//将set信号集中signo对应的信号位设置为1。
sigdelset(&set,int signo)//将set信号集中signo对应的信号位设置0。
bitcode=sigismember(&set,int signo);//查看信号集中的某一位是0或1直接返回。
sigprocmask(SIG_SETMASK(直接覆盖原有屏蔽字), &newset,&oldset);//使用自定义信号集替换进程原有屏蔽字,实现阻塞信号,传入替换的屏蔽字传出原有的屏i蔽字
SIG_BLOCK默认和自定义两个信号集进行位或运算得出新信号集合
SIG_UNBLOCK默认和自定义两个信号集进行取反求与运算得出新信号集合
sigpending(&pset)//可以将进程的未决信号集传出到pset变量中。
sigpending+sigismember可以遍历查看未决信号集。
屏蔽信号2:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>
int get_sig(sigset_t sig)
{
int i;
for(i=1;i<=31;i++){
if(sigismember(&sig,i))
putchar('1');
else putchar('0');
}
putchar('\n');
return 0;
}
int main()
{
sigset_t sig1,sig2,sig3;
sigemptyset(&sig1);
sigaddset(&sig1,SIGINT);
sigaddset(&sig1,SIGQUIT);
sigprocmask(SIG_SETMASK,&sig1,&sig2);
while(1){
sigpending(&sig3);
get_sig(sig3);
sleep(1);
}
return 0
}
屏蔽和忽略很像但是不一样,忽略是已经递达了,屏蔽是没递达。
输出未决信号集:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>
int get_sig(sigset_t sig)
{
int i;
for(i=1;i<=31;i++){
if(sigismember(&sig,i))
putchar('1');
else putchar('0');
}
putchar('\n');
return 0;
}
int main()
{
sigset_t sig1,sig2,sig3;
sigemptyset(&sig1);
sigaddset(&sig1,SIGINT);
sigaddset(&sig1,SIGQUIT);
sigprocmask(SIG_SETMASK,&sig1,&sig2);
while(1){
sigpending(&sig3);
get_sig(sig3);
sleep(1);
}
return 0
}
高优先级信号
如果所有的信号都失效,那么系统安全收到威胁,需要一些高级信号为系统提供保障。
sigkill(9),sigstop(19),只要发出必然递达,无法被屏蔽捕捉忽略。
临时屏蔽
如果一个信号在执行捕捉函数,系统会临时屏蔽此信号,避免相同信号递达调用捕捉函数引发冲突。
也就是说如果一个信号递达了然后系统会把屏蔽字临时设为1,下一个信号在未决态,所以第三个第四个后续的信号都被丢弃了。当第一个信号处理完毕,临时屏蔽解除,第二个信号可以递达处理。
经典信号不支持排队,但是因为临时屏蔽的关系可以排队一个。
后面的自定义信号支持排队处理。
信号处理优先级
操作系统发信号给内核层。
(1)进程在用户空间串行执行主函数代码
(2)信号抵达进程等待处理
(3)进程需要通过系统调用,中断,异常方式进行层级转换
(4)完成首要任务,完成调用,处置中断或处置异常
(5)上述任务完毕,在返回用户空间前检测是否有未处理信号,有则处理
(6)cpu保留内核级权限到用户空间执行捕捉函数
(7)执行完捕捉函数后,执行一条1SIG_RETURN返回内核
(8)返回用户空间
(9)从main被暂停的位置继续执行代码
信号的优先级比较低,信号一定会被处理。
一般情况下永远是主函数先执行,在执行过程中触发信号,系统调用捕捉函数,捕捉函数会比主函数先执行完。
捕捉函数执行时,占用进程本身的时间片资源,不会额外分配资源,捕捉函数执行时,主函数暂停。
捕捉与主函数使用全局资源,引发异常。
可重入与不可重入函数
函数的参数有全局或静态资源,这类函数称为不可重入函数,主函数与捕捉函数同时调用时引发冲突。
可重入函数,只要使用局部数据,不与其他人共享资源,这类函数都是安全的,信号就可以放心使用。
系统函数也开发了对应的可重入版本,例如readdir_r,strtok_r等,信号可以放心使用。
主动回收 阻塞/非阻塞
主动回收是不合理的,应该采用被动回收方案,如果子进程结束,系统给父进程发送通知,父进程完成回收。
子进程变僵尸进程,父进程用wait()等待系统发信号SIGCHLD(处理动作为IGN忽略)通知父进程回收。阻塞,父进程自己业务一点不跑。
非阻塞跑50%,所以我们想被动回收希望父进程更自由点跑80%业务。
我们想被动回收通知回收,父进程要有信号捕捉设定,和信号捕捉函数,在子进程创建之前完成捕捉设定,捕捉函数回收次数,如果按信号数量决定回收次数那么信号不支持排队会被丢弃,会产生漏回收,导致大量僵尸,一个信号回收多次,将当前可回收的全部回收即可。
#include<signal.h>
#include<sys/wait.h>
void jobs()
{
while(1)
{
printf("I am running\n");
sleep(1);
}
}
void sig()
{
pid_t pi;
while((pi=waitpid(-1,NULL,WNOHANG))>0){
printf("waitpid sucess,pid %d\n",pi);
}
}
int main()
{
pid_t pid;
int i;
for(i=0;i<10;i++){
pid=fork();
if(pid==0)break;
}
struct sigaction act,oldact;
act.sa_handler=sig;
act.sa_flags=0;
sigemptyset(&act.sa_mask);
sigaction(SIGCHLD,&act,&oldact);
if(pid>0)
{
jobs();
}
else if(pid==0)
{
printf("son %d\n",getpid());
exit(i);
}
else exit(0);
return 0;
}
信号实现外部控制
我们想实现暂停和继续,首先我们得有一个脚本程序,它想获得调用的目标进程就得有进程的pid,进程的pid可以存个配置文件里对应上,然后脚本程序发信号,不希望用信号SIGSTOP(19),SIGCONT(18),因为挂起会将数据交换到外存,我们用开发可用的信号SIGUSER1,SIGUSER2,目标进程捕捉SIGUSER1,调用Pause()暂停,捕捉SIGUSER2,不编码产生空调用,因为默认情况下这两个信号是杀死进程,Pause()是接收到信号就继续了。
使用信号实现进程间通信
父子进程交叉报数,数据为通信数据,相互传递,处理和打印。
信号选择:SIGUSER1,SIGUSER2
sigqueue(pid_t pid,int signo,union sigval);//给目标发信号发联合体
联合体的两个成员:union sigval val; val.sival_int//整型, val.sival_ptr;//void*指针
act.sa_sigaction(int n,siginfo_t* info,void*arg)
info->si_int=1//接收整型数据
info->si_ptr=0x010//接收指针数据
act.sa_flags=SA_SIGINFO;
捕捉设定做到:报数,自增,回传
父进程屏蔽SIGUSER1,而后创建子进程,子进程继承屏蔽字,出生即屏蔽SIGUSR1信号,而后子进程完成捕捉设定,解除屏蔽即可。
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<signal.h>
pid_t sonpid;
void parent_sig(int n,siginfo_t* info,void* arg)
{
printf("parent %d %d\n",getpid(),info->si_int);
union sigval val;
val.sival_int=++(info->si_int);
sigqueue(sonpid,SIGUSR1,val);
usleep(100000);
}
void son_sig(int n,siginfo_t* info,void*arg)
{
printf("son %d %d\n",getpid(),info->si_int);
union sigval val;
val.sival_int=++(info->si_int);
sigqueue(getppid(),SIGUSR2,val);
usleep(100000);
}
int main()
{
sigset_t psig;
sigemptyset(&psig);
sigaddset(&psig,SIGUSR1);
sigprocmask(SIG_SETMASK,&psig,NULL);
pid_t pid;
pid=fork();
if(pid>0)
{
sonpid=pid;
struct sigaction act;
act.sa_sigaction=parent_sig;
act.sa_flags=SA_SIGINFO;
sigemptyset(&act.sa_mask);
sigaction(SIGUSR2,&act,NULL);
union sigval val;
val.sival_int=1;
sigqueue(sonpid,SIGUSR1,val);
while(1)
sleep(1);
}
else if(pid==0)
{
struct sigaction act;
act.sa_sigaction=son_sig;
act.sa_flags=SA_SIGINFO;
sigemptyset(&act.sa_mask);
sigaction(SIGUSR1,&act,NULL);
sigprocmask(SIG_SETMASK,&act.sa_mask,NULL);
while(1)sleep(1);
}
else{
perror("fork error\n");
exit(0);
}
}