IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> Linux中的信号 -> 正文阅读

[系统运维]Linux中的信号

Linux中的信号

信号的概念及机制

每个进程收到的所有信号,都是由内核负责发送并由内核进行处理

与信号相关的事件和状态

产生信号:
  1. 按键产生:如:Ctrl+C、 Ctrl+z、 Ctrl+\
  2. 系统调用产生,如:kill、raise、abort
  3. 软件条件产生,如:定时器alarm
  4. 硬件异常产生,如:非法访问内存(段错误)、除0(浮点数例外)、内存对齐出错(总线错误)
  5. 命令产生,如:kill命令
递达:

递送并且到达进程

未决:

信号产生与递达之间的状态,主要是由于阻塞(或屏蔽)而导致

信号的处理方式:
  1. 执行默认动作,信号的处置为SIG_DFL(default);
  2. 忽略(丢弃),把信号的处置设置为SIG_IGN(ignore)来忽略,但有两个信号不能被忽略:SIGKILL和SIGSTOP
  3. 捕获(调用用户的处理函数signal handler来捕获catching),但有两个信号不能被捕获:SIGKILL和SIGSTOP

Linux 内核的进程控制块 PCB 是一个结构体,task_struct, 除了包含进程 id,状态,工作目录,用户 id,组 id,文件描述符表,还包含了信号相关的信息,主要指阻塞信号集和未决信号集。

阻塞信号集(信号屏蔽字):

将某些信号加入集合,对它们设置屏蔽,例如屏蔽x信号后,再接收到该信号,则不对该信号进行处理,直至该信号解除屏蔽。

未决信号集:
  1. 信号产生时,未决信号集中该信号对应的描述位立刻翻转为1,表明该信号处于未决状态;信号被处理后,该描述位则立刻翻转为0。通常情况下,未决状态十分短暂;
  2. 信号产生后由于某些原因(主要是阻塞)而不能递达,此类信号组成的集合为未决信号集。在屏蔽解除前,信号将一直处于未决状态。
示意图:

未决信号集与信号屏蔽字

信号的编号

可使用kill-l查看当前系统可使用的信号:

在这里插入图片描述

信号4要素

  1. 编号;
  2. 名称;
  3. 事件;
  4. 默认处理动作。

可通过man 7 signal 查看帮助文档获取。 也可查看/usr/src/linux-headers-3.16.0-30/arch/s390/include/uapi/asm/signal.h

在这里插入图片描述

部分signal有三个value,,第一个值通常对 alpha 和 sparc 架构有效,中间值针对 x86、arm和其他架构,最后一个应用于 mips 架构。一个‘-’表示在对应架构上尚未定义该信号。

默认动作:
  1. Term:终止进程;
  2. Ign:忽略信号(默认及时对该种信号进行忽略操作);
  3. Core:终止进程,生成Core文件。(查验进程死亡原因,用于gdb调试);
  4. Stop:停止(暂停)进程;
  5. Cont:继续运行进程。

注意:9)SIGKILL和19)SIGSTOP信号不允许被忽略和捕捉。

Linux常用信号一览表

1) SIGHUP: 当用户退出 shell 时,由该 shell 启动的所有进程将收到这个信号,默认动作为终止进程 
2) SIGINT:当用户按下了<Ctrl+C>组合键时,用户终端向正在运行中的由该终端启动的程序发出此信号。默认动作为终止进程。 
3) SIGQUIT:当用户按下<ctrl+\>组合键时产生该信号,用户终端向正在运行中的由该终端启动的程序发出些信号。默认动作为终止进程。 
4) SIGILL:CPU 检测到某进程执行了非法指令。默认动作为终止进程并产生 core 文件;
5) SIGTRAP:该信号由断点指令或其他 trap 指令产生。默认动作为终止里程 并产生 core 文件。 
6) SIGABRT: 调用 abort 函数时产生该信号。默认动作为终止进程并产生 core 文件。 
7) SIGBUS:非法访问内存地址,包括内存对齐出错,默认动作为终止进程并产生 core 文件。 
8) SIGFPE:在发生致命的运算错误时发出。不仅包括浮点运算错误,还包括溢出及除数为 0 等所有的算法错误。
默认动作为终止进程并产生 core 文件。 
9) SIGKILL:无条件终止进程。本信号不能被忽略,处理和阻塞。默认动作为终止进程。它向系统管理员提供了
可以杀死任何进程的方法。 
10) SIGUSE1:用户定义 的信号。即程序员可以在程序中定义并使用该信号。默认动作为终止进程。 
11) SIGSEGV:指示进程进行了无效内存访问。默认动作为终止进程并产生 core 文件。 
12) SIGUSR2:另外一个用户自定义信号,程序员可以在程序中定义并使用该信号。默认动作为终止进程。 
13) SIGPIPE:Broken pipe 向一个没有读端的管道写数据。默认动作为终止进程。
14) SIGALRM: 定时器超时,超时的时间 由系统调用 alarm 设置。默认动作为终止进程。 
15) SIGTERM:程序结束信号,与 SIGKILL 不同的是,该信号可以被阻塞和终止。通常用来要示程序正常退出。
执行 shell 命令 Kill 时,缺省产生这个信号。默认动作为终止进程。 
16) SIGSTKFLT:Linux 早期版本出现的信号,现仍保留向后兼容。默认动作为终止进程。 
17) SIGCHLD:子进程状态发生变化时,父进程会收到这个信号。默认动作为 忽略这个信号。 
18) SIGCONT:如果进程已停止,则使其继续运行。默认动作为继续/忽略。 
19) SIGSTOP:停止进程的执行。信号不能被忽略,处理和阻塞。默认动作为暂停进程。 

信号的产生

终端按键产生
#  Ctrl+c  ->  2)SIGINT(终止/中断)  "INT"-->interrupt
#  Ctrl+z  ->  20)SIGTSTP(暂停/停止) "T"-->terminal终端
#  Ctrl+c  ->  3)SIGQUIT(退出)  
硬件异常产生
#  除0操作  ->  	8)SIGFPE(浮点数例外)  "F"-->float浮点数
#  非法访问内存  -> 11)SIGSEGV(段错误) 
#  总线错误  ->  	  7)SIGBUS  
kill函数/命令产生
#  命令格式: kill -SIGKILL pid  ( == kill -9 pid)
/*
成功:0
失败:-1,设置errno
失败可能的原因:pid非法,信号非法或用户权限不够等
*/
int kill(pid_t pid, int sig);//pid为进程编号,sig为信号编号,应使用宏名,避免不同操作系统信号编号不一致
/*
pid>0: 发送信号给进程号为pid的进程;
pid=0: 发送信号给与调用 kill 函数进程属于同一进程组的所有进程。 
pid<0: 发送信号给|pid|绝对值所在的进程组;
pid=-1: 发送信号给进程有权限发送的系统中所有进程。 
*/
//普通用户间不能发送信号,普通用户也无法利用kill向root用户发送信号。
举例:循环创建子进程,父进程用kill终止任一子进程

软件条件产生
alarm函数

每个进程都有且仅有一个定时器

alarm函数会利用内核给当前进程发送14)SIGALRM信号,进程收到信号后,默认终止

unsigned int alarm(unsigned int seconds);//返回0或剩余的秒数,不存在失败的情形

alarm(0)会取消定时器

举例:编写程序测试1秒钟能数多少个数
#include <unistd.h>
#include <stdio.h>
int main(int argc, char* argv[]) {
	alarm(1);

	int i=0;
	while(1){
		i++;
		printf("%d\n", i);
	}
	return 0;	
}
使用time命令查看程序执行时间

time ./alarm

在这里插入图片描述

实际执行时间real = 系统时间sys + 用户时间user + 等待时间

setitimer函数

同样可设置定时器,但精度可至微秒,且可循环执行。

int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
/* which:指定定时方式:
			1.ITIMER_REAL:自然时间
            2.ITIMER_VIRTUAL(用户空间):只计算进程占用CPU的时间
            3.ITIMER_PROF(用户+内核):计算占用CPU及执行系统调用的时间
    struct itimerval {
               struct timeval it_interval; // Interval for periodic timer 
               struct timeval it_value;    // Time until next expiration
           };
   struct timeval {
               time_t      tv_sec;         // seconds 
               suseconds_t tv_usec;        // microseconds 
           };
*/
举例:setitimer实现alarm
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <sys/time.h>  /* setitimer() */
#include <signal.h>	   /* signal() */

void sys_err(const char* str) {
	perror(str);
	exit(1);
}

void func() {
	printf("hello  world!\n");
}

int main(int argc, char* argv[]) {

	struct itimerval new_value;
	struct itimerval old_value;
	//捕捉闹钟信号,并执行func()函数规定的操作:SIGALRM默认的行为是终止进程
	signal(SIGALRM, func);
    //设置闹钟时间
	new_value.it_interval.tv_sec=1;
	new_value.it_interval.tv_usec=0;
    //设置循环周期
	new_value.it_value.tv_sec=2;
	new_value.it_value.tv_usec=0;

	/*
	old_value.it_interval.tv_sec=1;
	old_value.it_interval.tv_usec=0;
	old_value.it_value.tv_sec=1;
	old_value.it_value.tv_usec=0;
	*/

	if(setitimer(ITIMER_REAL, &new_value, &old_value) == -1 ){
		sys_err("setitimer error");
	}

	while(1);//阻塞程序,以查看现象

	return 0;	
}

信号集操作函数

内核通过读取未决信号集来判断信号是否被处理。

信号屏蔽字mask可以影响未决信号集。

我们可以在程序中自定义集合set来改变mask,以达到屏蔽指定信号的目的。

信号集设定函数

sigset_t set;	//typedef unsigned long sigset_t;
int sigemptyset(sigset* set); //将某个信号集清零	成功:0 失败:-1
int sigfillset(sigset* set); //将某个信号集置1	成功:0 失败:-1
int sigaddset(sigset_t *set, int signum);   //将某个信号加入信号集     成功:0;失败:-1 
int sigdelset(sigset_t *set, int signum);   //将某个信号清出信号集      成功:0;失败:-1
int sigismember(const sigset_t *set, int signum);//判断某个信号是否在信号集中  返回值:在集合:1;不在:0;出错:-1   
//sigset_t 类型的本质是位图。但不应该直接使用位操作,而应该使用上述函数,保证跨系统操作有效。 
sigprocmask函数
/*
用来屏蔽信号、解除屏蔽也使用该函数。其本质,读取或修改进程的信号屏蔽字(PCB 中) 
【注】屏蔽信号:只是将信号处理延后执行( 延至解除屏蔽) ;而忽略表示将信号丢处理
成功:0   失败:-1  并设置errno
*/
int sigprocmask(int how, const sigset_t* set, sigset_t* oldset);
/*
	set:传入参数;
	oldset:传出参数,保存旧的信号屏蔽集
	how:对当前信号屏蔽集mask所作的操作:
		1)SIG_BLOCK: set表示待屏蔽的信号集,相当于 mask = mask|set
		2)SIG_UNBLOCK: set表示需要解除屏蔽的信号集,相当于 mask = mask&~set
		3)SIG_SETMASK:set 表示用于替代原始屏蔽及的新屏蔽集。相当于 mask = set 若调用 sigprocmask 解除了对当前若干个信号的阻塞,则在 sigprocmask 返回前,至少将其中一个信号递达。 
		how取前两种方式,比较安全。通常将set的某一信号位位设置为1,然后采用SIG_BLOCK或SIG_UNBLOCK对mask中的该信号位进行屏蔽或解除屏蔽。
*/
sigpending函数
读取当前进程的未决信号集
int sigpending(sigset_t* set);//set为传出参数  成功:0  失败:-1 设置errno
代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>

void sys_err(const char* str) {
	perror(str);
	exit(1);
}
void print_set(sigset_t* set){
	int i;
	for(i=1;i<32;i++){
		if(sigismember(set,i)){
			putchar('1');
		}else{
			putchar('0');
		}
	}
	printf("\n");

}

int main(int argc, char* argv[]) {
	sigset_t set, oldset, pedset;
	int ret=0;

	sigemptyset(&set);
	sigaddset(&set, SIGINT);

	ret=sigprocmask(SIG_BLOCK, &set, &oldset);
	if(ret==-1){
		sys_err("sigprocmask error");
	}

	while(1){
		ret=sigpending(&pedset);
		if(ret==-1){
			sys_err("sigpending error");
		}

		print_set(&pedset);
	}
	
	
	return 0;	
}

信号捕捉

signal函数

该函数是注册一个信号捕捉函数

typedef void (*sighandler_t)(int);//函数指针sighandler_t:参数为int 返回值为void
sighandler_t signal(int signum, sighandler_t handler);
==void (*signal(int signum, void (*sighandler_t)(int))) (int);  

上述函数较老 应尽量避免使用

sigaction函数
//成功:0  失败:-1 设置errno
//act:in   oldact: out
int sigaction(int signum, const struct sigaction* act, struct sigaction* oldact);

struct sigaction {
               void     (*sa_handler)(int);
               void     (*sa_sigaction)(int, siginfo_t *, void *);
               sigset_t   sa_mask;
               int        sa_flags;
               void     (*sa_restorer)(void);
           };
//*	sa_handler:信号捕捉后的处理函数(即注册函数),也可使用SIG_IGN(忽略)或SIG_DFL(默认动作);
//	sa_sigaction:当sa_flags被指定为SA_SIGINFO时,使用该信号处理程序(但很少使用);
//*	sa_mask:调用信号处理函数时,所要屏蔽的信号集合(信号屏蔽字)。注意:仅在处理函数被调用期间屏蔽生效,是临时性设置。 
//*	sa_flags:通常设置为 0,表使用默认属性。 
//	sa_restorer:(该元素已弃用)
代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>

void sys_err(const char* str) {
	perror(str);
	exit(1);
}

void sig_catch(int signo) {
	printf("catch you, %d \n", signo);
}

int main(int argc, char* argv[]) {
	struct sigaction act,oldact;

	act.sa_handler = sig_catch;	//设置回调函数
	sigemptyset(&(act.sa_mask));//清空信号屏蔽字
	act.sa_flags = 0;			//设置标志位

	int ret = sigaction(SIGINT, &act, &oldact);
	if( ret == -1) {
		sys_err("sigaction error");
	}

	while(1);

	return 0;	
}

信号捕捉特性

1. 捕捉函数执行期间,信号屏蔽字 由 mask --> sa_mak, 捕捉函数执行结束后,恢复为mask;
2. 捕捉函数执行期间,本信号自动被屏蔽(sa_flag=0);
3. 捕捉函数执行期间,被屏蔽信号多次发送,解除屏蔽后只处理一次。
内核实现信号捕捉过程:

在这里插入图片描述

SIGCHLD信号

产生条件

  1. 子进程终止时;
  2. 子进程接收到SIGSTOP信号而停止时;
  3. 子进程处于停止态,接收到SIGCONT后唤醒时。

借助SIGCHLD信号回收子进程

代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <errno.h>
#include <pthread.h>

void sys_err(char* str) {
	perror(str);
	exit(1);
}

void catch_child(int signo) {
	int status;
	pid_t wpid;

	/*
	wpid = wait(NULL);
	printf("catch you, %d\n", wpid);
	*/

	while((wpid = waitpid(0, &status, WNOHANG)) > 0) {	//循环回收子进程,因为SIGCHLD信号不支持排队,所以需要循环回收,避免出现僵尸进程
		if(WIFEXITED(status)) {
			printf("child %d exit %d \n",wpid, WEXITSTATUS(status));
		} else if(WIFSIGNALED(status)) {
			printf("child %d cancel signal %d \n", wpid, WTERMSIG(status));
		}
	}

	return;
}

int main() {
	pid_t pid;
	int i;
//阻塞信号
	for(i =0; i<5; i++) {
		if((pid=fork())==0) {	//创建多个子进程
			break;
		} else if(pid < 0) {
			sys_err("fork error");
		}
	}

	if(pid == 0) {
		int n = 1;
		while(n--) {
			printf("child id %d\n", getpid());
			sleep(1);
		}
		return i*i;
	} else if(pid > 0) {
		//sleep(1);
		struct sigaction act,oldact;	//注册信号捕捉函数
		act.sa_handler=catch_child;
		sigemptyset(&(act.sa_mask));
		act.sa_flags=0;
		sigaction(SIGCHLD,&act,&oldact);
//解除阻塞
		while(1) {
			printf("I'm parent: %d\n",getpid());
			sleep(1);
		}
		
	} 
	return 0;
}

SIGCHLD信号注意问题

  1. 子进程继承了父进程的信号屏蔽字和信号处理动作,但并未继承未决信号集;
  2. 须注意注册信号捕捉函数的位置应在父进程中;
  3. 应在fork之前阻塞SIGCHLD信号,注册完捕捉函数之后解除阻塞,避免父进程来不及捕捉子进程的SIGCHLD信号。

中断系统调用

  1. 慢速系统调用:可能会使进程永久调阻塞的调用,如:read,write,wait……
  2. 其他系统调用:如:getpid,fork……

慢速系统调用被中断时,可利用修改sa_flags参数来设置被信号中断后系统调用是否重启。设置为SA_INTERRUPT表示不重启,SA_RESET表示重启。

扩展:sa_flags还有其他参数,在捕捉到信号后,在执行捕捉函数期间,不希望自动阻塞该信号,可将sa_flags设置为SA_NODEER,除非sa_mask中包含该信号(将其阻塞)。

,&act,&oldact);
//解除阻塞
while(1) {
printf(“I’m parent: %d\n”,getpid());
sleep(1);
}

} 
return 0;

}


#### SIGCHLD信号注意问题

1. 子进程继承了父进程的信号屏蔽字和信号处理动作,但并未继承未决信号集;
2. 须注意注册信号捕捉函数的位置应在父进程中;
3. 应在fork之前阻塞SIGCHLD信号,注册完捕捉函数之后解除阻塞,避免父进程来不及捕捉子进程的SIGCHLD信号。

### 中断系统调用

1. 慢速系统调用:可能会使进程永久调阻塞的调用,如:read,write,wait……
2. 其他系统调用:如:getpid,fork……

慢速系统调用被中断时,可利用修改sa_flags参数来设置被信号中断后系统调用是否重启。设置为SA_INTERRUPT表示不重启,SA_RESET表示重启。

扩展:sa_flags还有其他参数,在捕捉到信号后,在执行捕捉函数期间,不希望自动阻塞该信号,可将sa_flags设置为SA_NODEER,除非sa_mask中包含该信号(将其阻塞)。

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2021-09-09 12:11:03  更:2021-09-09 12:12:22 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 14:10:04-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码