科林Linux_4 信号

csdn推荐

#include 

信号signal:Linux或Unix系统支持的经典的消息机制,用于处置进程,挂起进程或杀死进程

kill -l    #查看系统支持的信号

1~31 Unix经典信号(软件开发工程师)

32、33信号被系统隐藏,不对用户开放,供NPTL线程库使用

34~64 自定义信号/实时信号(驱动工程师)

Ctrl+C 系统帮你发送了 2)SIGINT信号。终端组合按键产生的信号,杀死唯一的前台终端进程。

一、系统中触发信号的几种方式:

1、终端组合按键触发信号

Ctrl+C SIGINT/2 终止进程Ctrl+SIGQUIT/3 退出进程Ctrl+Z SIGTSTP/20 挂起终端进程

jobs    #查看挂起的作业编号
fg 作业编号    #唤醒到前台

tty 终端 pts 虚拟终端

D (TASK_UNINTERRUPTIBLE) 不可中断的睡眠状态

R (TASK_RUNNING) 正在运行,或在队列中的进程

S (TASK_INTERRUPTIBLE) 可中断的睡眠状态

T (TASK_STOPPED) 停止状态

t (TASK_TRACED) 被跟踪状态

Z (TASK_DEAD - EXIT_ZOMBIE) 退出状态,但没被父进程收尸,成为僵尸状态

W 进入内存交换(从内核2.6开始无效)

X (TASK_DEAD - EXIT_DEAD) 退出状态,进程即将被销毁

< 高优先级

N 低优先级

L 有些页被锁进内存

s 包含子进程

+ 位于前台的进程组;

l 多线程,克隆线程 multi-threaded (using CLONE_THREAD, like NPTL pthreads do)

2、命令触发信号

kill -signo信号 pid进程号    #向任意进程发送任意信号

3、函数触发信号

kill(pid_t pid,int signo);    //向任意进程发送任意信号
raise(int signo);    //向当前进程发送任意信号
abort();    //向当前进程发送固定的SIGABRT/6信号

#include 
#include 
#include 
#include 
int main(int argc,char* argv[]){
	//支持命令,KILL signo pid
	if(argc<3){
		printf("pram errorn");
		exit(0);
	}
	kill(atoi(argv[2]),atoi(argv[1]));
	return 0;
}

4、硬件异常产生信号

违规访问使用硬件,导致信号杀死进程

1)如果进程非法操作内存,系统向其发送SIGSEGV/11杀死进程,错误信号为段错误

    char* str="hello";
    str[0]='H';    //只读内存的写操作

2)如果进程出现cpu运算异常,系统向其发送SIGFPE/8杀死进程,错误为浮点数例外

    int b=0;    
    int a=8/b;    //除0

3)如果进程出现内存访问越界,系统向其发送SIGBUS/7杀死进程,错误为总线错误

    int* ptr=NULL;
	int fd=open("MapFile",O_RDWR);
	int size=lseek(fd,0,SEEK_END);
    ptr=mmap(NULL,size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);    
    //在使用mmap内存映射时,若"MapFile"是内容为空的新文件大小为0,没有与之对应的合法的物理页,mmap不能扩展,发生了越界访问
    //添加错误检测来防止总线错误
    if((ptr=mmap(NULL,size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0))==MAP_FAILED){//内存映射
		perror("mmap failed");
		exit(0);
	}

5、软件异常产生信号

可以使用某个组件,但是如果使用时触发软条件,系统会杀死进程(信号)

1)定时器alarm(10),定时到时,系统向定时进程发送SIGALRM/14信号,默认杀死进程

    alarm(1);    //定时1s后杀死进程,返回“闹钟”

2)匿名管道读端关闭,写端向管道写数据,系统向写端发送SIGPIPE/13信号,杀死进程

二、信号的三大行为与处理动作:

信号的行为可以被修改,默认情况下为默认行为,但是可以改为忽略或捕捉(三选一)

1、默认行为SIG_DFL:默认五种处理动作(五选一)

(1)TERM直接杀死进程(2)CORE杀死进程并且转储核心(3)STOP挂起进程(4)CONT忽略/继续进程(5)IGN忽略

每个信号都有自己的默认动作来处置进程,进程不是被挂起就是被杀死。只有动作为IGN的信号,不会处置进程。

可以通过信号结束进程的输出,分析其动作

2、忽略行为SIG_IGN:忽略行为没有处理动作,无法处置进程

3、捕捉行为SIG_ACTION:可以手写捕捉函数来自定义行为

void sig_do(int n);

信号捕捉可以让自定义的捕捉函数与信号绑定,以后触发此信号,系统就会去执行此函数

可以用来设计实现条件触发的工作和任务,信号触发则执行,否则不执行。

修改信号行为

每个信号都有一个自己的信号行为结构体,修改信号行为要保留其原结构体,便于复位

    struct sigaction nact,oact;    //信号行为结构体
    act.sa_handler=SIG_DFL|SIG_IGN|函数指针;
    act.sa_flags=0;    //默认选项
    act.sa_mask;    //临时屏蔽字
    sigaciton(signo,&nact,&oact);    //替换信号行为结构体,act为新的,oact传出进程原有的

#include 
#include 
#include 
#include 
void sig_do(int n){
	printf("已经成功捕捉SIGINT,signo=%dn",n);
}
int main(){
	//void(*sa_handle)(int)
	struct sigaction act,oact;
	act.sa_handler=sig_do;
	act.sa_flags=0;
	sigemptyset(&act.sa_mask);//初始化
	sigaction(SIGINT,&act,&oact);//替换信号行为
	while(1)
		sleep(1);
	return 0;
}

三、三种让信号生效的方法:

1、信号忽略,将信号设置为忽略行为

2、信号捕捉,对信号进行捕捉设定,绑定捕捉函数

3、信号屏蔽:阻塞信号的传递

如果信号通过未决信号集,系统会将未决中对应的位设置为1,标记未决同时避免相同的信号同时处理。

如果未决为1,对应的信号直接丢弃,不处理

信号的处理不支持排队(经典信号不支持排队队列,但是自定义信号可以),最大处理1个

驱动开发软件需要支持排队,因为每个信号与功能绑定,功能需要排队处理。经典信号目的是为了杀死进程,处理一次即可,无需排队。

信号通过屏蔽字,从未决态切换为递达态,系统将未决的1设置回0。系统会对正在处理的信号的屏蔽字临时设为1,等待信号处理完将屏蔽字设回0(避免如果捕捉函数中使用全局资源,信号多次执行出现异常)相同信号同时触发,最大可以处理两个,一个正在处理,一个被屏蔽,其他被丢弃。

如果将某个信号对应的屏蔽字位设置为1,该信号被阻塞,不允许递达。

用户可以自行设置信号屏蔽字,实现阻塞信号的效果。

屏蔽方式与忽略与捕捉有很大的不同。忽略与捕捉吸纳后已经递达了并处理完成了,但是屏蔽属于延迟处理,信号没有消失。

信号屏蔽设置:

    sigset_t set;    //信号集类型
    sigemptyset(&set);    //初始化0
    sigfillset(&set);    //初始化1
    sigaddset(&set,signo);    //将某个特定信号位置设置为1
    sigdelset(&set,signo);    //将某个信号位设置0
    int sigismember(&set,signo);    //返回特定信号位的位码
    sigprocmask(SIG_SETMASK,&newset,&oldset);    //设置替换进程的信号屏蔽字

#include
#include
#include
#include
int main(){
	sigset_t set,oset;
	sigemptyset(&set);
	sigaddset(&set,SIGQUIT);
	sigprocmask(SIG_SETMASK,&set,&oset);
	while(1)
		sleep(1);
	return 0;
}

四、高级信号与普通信号

高级信号:只要发出必然递达,无法屏蔽捕捉忽略

SIGKILL/9 必然杀死

SIGSTOP/19 必然挂起

硬件异常的信号SIGSEGV SIGFPE SIGBUS,用户发出触发可以屏蔽,但是系统触发不可屏蔽

查看当前进程实时的信号情况,应该查看未决信号集

    sigpending(&pset);    //调用此函数,系统会传出当前进程的未决信号集

#include
#include
#include
#include
int main(){
	sigset_t set,oset,pset;
	sigemptyset(&set);
	sigaddset(&set,SIGINT);//2
	sigaddset(&set,SIGQUIT);//3
	sigaddset(&set,SIGSEGV);//11
	sigaddset(&set,SIGALRM);//14
	sigprocmask(SIG_SETMASK,&set,&oset);
	while(1){
		sigpending(&pset);//传出进程未决
		for(int signo=1;signo<32;signo++)
			printf("%d",sigismember(&pset,signo));
		printf("n");
		sleep(1);
	}
	return 0;
}

信号的处理不是实时的

系统发出的信号在内核层,而程序运行在用户层

1.进程执行于用户层,串行执行主函数代码

2.信号发送到内核层,等待处理

3.满足切换条件,切换到内核层

触发上下文切换(cpu权限切换)的三种事件:系统调用、软件中断(时间片完)、异常

4.完成调用、处置中断、处置异常

5.在返回用户层前,检查是否有未递达信号,如果有则处理

6.发现捕获函数在用户层使用,系统使用指令直接执行捕捉函数,避免切换(执行捕捉函数,系统并没有降低权限,避免了开销)

7.执行完毕通过SIG_RETURN指令返回内核层

8.执行上下文切换,返回用户空间

9.主函数从暂停的位置继续执行

一般情况下,主函数率先执行,但是执行过程中触发信号,系统调用捕捉函数,捕捉函数永远比主函数先执行完

主函数运行时使用当前进程的时间片,捕捉函数调用时主函数暂停使用当前进程的时间片(一个进程的时间片资源只有一份)

文章来源:https://blog.csdn.net/RedGown_ovo/article/details/138743236



微信扫描下方的二维码阅读本文

© 版权声明
THE END
喜欢就支持一下吧
点赞13 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容