栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

IPC之信号

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

IPC之信号

信号

信号的机制:A给B发送信号,B收到信号之前执行自己的代码,收到信号后,不管执行到程序的什么位置,都要暂停运行,去处理信号,处理完毕再继续执行。与硬件中断类似–异步模式。但信号是软件层面上实现的中断,早期被称为“软中断”。

信号的特质:由于信号是通过软件方法实现,其实现手段导致信号有很强的延时性。但对于用户来说,这个延迟时间非常短,不易察觉。

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

与信号相关的时间和状态

产生信号:

1、 按键产生

2、 系统调用产生,如kill,raise, aborrt。

3、 软件条件产生,如定时器。

4、 硬件异常产生, 如非法访问内存(段错误), 除0(浮点数例外),内存对齐出错(总线错误)

5、 命令产生,如:kill命令

递达:递送并且到达进程。

未决:产生和递达之间的状态。主要由于阻塞(屏蔽)导致该状态。

信号的处理方式:

1、 执行默认动作

2、 忽略(丢弃)

3、 捕捉(调用户处理函数)

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

阻塞信号集:将某些信号加入集合,对他们设置屏蔽,当屏蔽X信号后,再收到该信号,该信号的处理将推后。

未决信号集:1、 信号产生,未决信号集中描述该信号的位立刻翻转为1,表信号处于未决状态。当信号被处理对应位翻转为0.这一时刻非常短暂。2、 信号产生后由于某些原因(阻塞)不能抵达。这类信号的集合为未决信号集。在屏蔽解除前,信号一直处于未决状态。

阻塞信号集和未决信号集本质都是位图。

信号的编号:使用kill -l命令查看当前系统可使用的信号有哪些。不存在编号为0的信号。其中1-31号信号称之为常规信号,34-64称之为实时信号。驱动编程与硬件相关。名字上区别不大。而前32个名字各不相同。

信号四要素

信号使用前必须先确定信号四要素

1、 编号 2、 名称 3、 事件 4、 默认处理动作 可通过man 7 signal 查看帮助文档。

kill函数产生信号

kill命令产生信号:kill -SIGKILL pid ps ajx

kill函数:给指定进程发送指定信号。 传复数杀死组内所有进程。

pid > 0 : 发送信号给指定的进程。

pid == 0 : 发送信号给与调用kill函数进程属于同一进程组的所有进程。

pid < 0 : 取|pid|发给对应进程组。

pid = -1 : 发送给进程有权限发送的系统中的所有进程。能发的全部可以发送,全杀。

进程组:每个进程都属于一个进程组,进程组是一个或多个进程集合,他们相互关联,共同完成一个实体任务,每个进程组都有一个进程组长,默认进程组ID与进程组长ID相同。

权限保护:super用户可以发送信号给任意用户,普通用户是不能向 系统用户发送信号的。kill -9 是不可以的。同样,普通用户也不能向其他普通用户发送信号,终止其进程。只能向 自己创建的进程发送信号。普通用户基本规则是:发送者实际或有效用户ID==接收者实际或有效用户ID。

练习:循环创建5个子进程,父进程用kill函数终止任意子进程。

alarm函数

设置定时器,在指定seconds后,内核会给当前进程发送14)SIGALRM 信号。进程收到该信号,默认动作终止。 每个进程有且只有唯一一个定时器。

unsigned int alarm(unsigned int seconds); 返回0或剩余的秒数,无失败。

定时与进程状态无关,就绪、运行、挂起、终止、僵尸,无论进程处于何种状态,alarm都计时。

编写程序,测试使用的计算机1秒钟能数多少个数。

使用time命令查看程序执行的时间。程序运行的瓶颈在于IO,首选优化IO。

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

setitimer函数

设置定时器。可代替alarm函数。精度微妙us,可以实现周期定时。

int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value); 成功:0;失败:-1,设置errno。

参数:which 指定定时方式

1.自然定时: ITIMER_REAL -> 14)SIGLATM 计算自然时间。

2.虚拟空间计时(用户空间):ITIMER_VIRTUAL -> 26)SIGVTALRM 只计算进程占用cpu的时间

3.运行时计时(用户+内核): ITIMER_PROF -> 27)SIGPROF 计算占用cpu及执行系统调用的时间。

其余发信号函数: int raise(int sig); void abort(void);

new_value:定时秒数

类型:

struct itimerval {
	struct timeval {
	time_t tv_sec; 
	suseconds_t tv_usec; 
	}it_interval;---> 周期定时秒数

	struct timeval {
	time_t tv_sec;
	suseconds_t tv_usec;
	}it_value; ---> 第一次定时秒数
};

old_value:传出参数,上次定时剩余时间。

e.g.

struct itimerval new_t;
struct itimerval old_t;

new_t.it_interval.tv_sec = 0;
new_t.it_interval.tv_usec = 0;
new_t.it_value.tv_sec = 1;
new_t.it_value.tv_usec = 0;

int ret = setitimer(&new_t, &old_t); 定时 1 秒

返回值:

成功: 0

失败: -1 errno

setitimer()函数
alarm函数会建立一个定时器,在seconds秒后,给当前进程发送SIGALRM信号
setitimer也可以创建定时器,和alarm函数相比,最显著的区别就是它可以指定到微秒,而且可以循环发送
man 2 setitimer
 
#include 
#include 
#include 
#include 
#include 

void sigHandler(int sig){
	printf("[%d] caught signal : #%d: %sn",time(NULL),sig,strsignal(sig));
}
 
int main()
{
	//打印开始的时间戳
	printf("[%d] startn",time(NULL));
	
	//注册SIGALRM信号处理函数,setitimer函数会不停的发送SIGALRM 信号
	signal(SIGALRM,sigHandler);
	struct itimerval it,oldit;
	
	//第三秒时发送一次SIGALRM 信号
	it.it_interval.tv_sec = 3;
	it.it_interval.tv_usec = 0;
	
	//以后每隔一秒发送一次SIGALRM信号
	it.it_value.tv_sec = 1;
	it.it_value.tv_usec = 0;
	
	setitimer(ITIMER_REAL,&it,NULL);
	
	while(1);
	return 0;
}
信号集操作函数

内核通过读取未决信号集来判断信号是否应被处理。信号屏蔽字 mask 可以影响未决信号集。而我们可以在应用程序中自定义 set 来改变 mask。已达到屏蔽指定信号的目的。

信号集操作函数:

sigset_t set; 自定义信号集。

sigemptyset(sigset_t *set); 清空信号集

sigfillset(sigset_t *set); 全部置 1

sigaddset(sigset_t *set, int signum); 将一个信号添加到集合中

sigdelset(sigset_t *set, int signum); 将一个信号从集合中移除

sigismember( const sigset_t *set, int signum); 判断一个信号是否在集合中。 在: 1,
不在: 0,出错: -1;

sigset_t 类型的本质是位图。但不应该直接使用位操作,而应该使用上述函数,保证跨系统操作有效。

设置信号屏蔽字和解除屏蔽:

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

SIG_UNBLOCK: 取消阻塞

SIG_SETMASK: 用自定义 set 替换 mask。

set: 自定义 set

oldset:旧有的 mask。

查看未决信号集:

int sigpending(sigset_t *set);

set: 传出的 未决信号集。

sigprocmask函数

用来屏蔽信号、解除屏蔽。其本质是读取或修改进程的信号屏蔽字。

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

sigpending函数

读取当前进程的未决信号集

int sigpending(sigset_t *set);set传出参数

kill -l可以显示信号列表

设置信号阻塞

#include
#include 
#include 
#include 
#include 
#include 
#include 

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(){
	sigset_t set, oldset, pedset; //定义一个信号集
	int ret = 0;

	sigemptyset(&set);//清空信号集
	sigaddset(&set, SIGINT);//设置2号信号为1
	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);
	sleep(1);
	}

	return 0;
}

运行结果为:

ymyy@ymyy-virtual-machine:~/systemcode$ ./sigsfunc 
0000000000000000000000000000000
0000000000000000000000000000000
0000000000000000000000000000000
0000000000000000000000000000000
0000000000000000000000000000000
0000000000000000000000000000000
0000000000000000000000000000000
0000000000000000000000000000000
0000000000000000000000000000000
^C0100000000000000000000000000000
0100000000000000000000000000000
0100000000000000000000000000000
0100000000000000000000000000000
0100000000000000000000000000000
0100000000000000000000000000000
2号信号被阻塞,即 ctrl+c 的信号被阻塞, 9号和19号信号不能忽略捕捉,不能设置阻塞。

设置信号捕捉

signum: 带捕捉信号
handler: 捕捉信号后的操纵函数

signal 函数: 注册一个信号捕捉函数:

typedef void(*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler); 该函数由ANSI定义,由于历史原因在不同版本的unix和不同版本的linux中可能由不同的行为。因此尽量使用 sigaction 函数
void(*signal(int signum, void(*sighandler_t)(int)))(int);


#include
#include 
#include 
#include 
#include 

void sig_catch(int signal){
	printf("catch you!! %dn", signal);
	return;
}

int main(){
	signal(SIGINT, sig_catch);

	while(1);

	return 0;
}

sigaction函数

修改信号处理动作(注册一个信号捕捉函数)

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

#include
#include 
#include 
#include 
#include 

void sig_catch(int signal){//回调函数,内核帮忙调用
	printf("catch you!! %dn", signal);
	return;
}

int main(){
	struct sigaction act, oldact;
	act.sa_handler = sig_catch;//设置捕捉函数
	sigemptyset(&(act.sa_mask));//清空屏蔽字,只在sig_catch工作时有效
	act.sa_flags = 0;//设置默认属性

	sigaction(SIGINT, &act, &oldact);//注册信号捕捉函数

	while(1);

	return 0;
}

信号捕捉函数的特性:

1.进程正常运行时,默认PCB中有一个信号屏蔽字,它决定了进程自动屏蔽那些信号。当注册了某个信号捕捉函数,捕捉到该信号以后,要调用该函数。而该函数有可能执行很长时间,在此期间会再次收到该信号从而会陷入死循环,因此设置了sa_mask,在函数执行期间,sa_mask会清空屏蔽字,屏蔽本信号,保证信号捕捉函数不会陷入死循环。

2.阻塞的常规信号不支持排队,多次的信号内核只处理一次。

内核实现信号的捕捉解析:sys_sigreturn() 只干一件事,执行流程从用户空间切换到内核空间,内核空间发挥用户模式,从主控制流程中上次被中断的地方继续向下执行。

SIGCHLD信号

产生条件:

子进程终止时

子进程接收到 SIGSTOP 信号时。

子进程处在停止态,接收 SIGCONT 后唤醒

借助 SIGCHLD 信号回收子进程

子进程结束运行,其父进程会收到SIGCHLD信号。该信号的默认处理动作是忽略。可以捕捉该信号,在捕捉函数中完成子进程的回收。

#include
#include 
#include 
#include 
#include 
#include 
#include 

void catch_child(int signo){

	pid_t wpid;
	int status;

//	while(wpid = wait(NULL) != -1)
	while((wpid = waitpid(-1, &status, 0)) != -1){
		if (WIFEXITED(status))
			printf("catch child id %d, return = %dn", wpid, WEXITSTATUS(status));
	}

	return;
}

int main(int argc, char *argv[]){
	pid_t pid;
	//阻塞 防止父进程还未注册成功子进程就结束
	sigset_t set;
	sigemptyset(&set);
	sigaddset(&set, 	SIGCHLD);
	sigprocmask(SIG_BLOCK, &set, NULL);
	int i;
	for(i = 0; i < 5; i++){
		if((pid = fork()) == 0){
			break;
		}
	}
   //接触阻塞
   sigprocmask(SIG_UNBLOCK, &set, NULL);
	if(i == 5){
		struct sigaction act;
		act.sa_handler = catch_child;
		sigemptyset(&act.sa_mask);
		act.sa_flags = 0;

		sigaction(SIGCHLD, &act, NULL);

		printf("i am parent, pid = %dn", getpid());

		while(1);
	}else{
		printf("i am child, pid = %dn", getpid());
		return i;
	}
	return 0;
}

运行结果为:

ymyy@ymyy-virtual-machine:~/systemcode$ ./catch_child 
i am child, pid = 115630
i am child, pid = 115631
i am child, pid = 115632
i am child, pid = 115633
catch child id 115630, return = 0
catch child id 115631, return = 1
i am child, pid = 115634
catch child id 115632, return = 2
catch child id 115633, return = 3
catch child id 115634, return = 4
i am parent, pid = 115629
中断系统调用

系统调用可分为两类:慢速系统调用和其它系统调用

慢速系统调用:可能会使进程永远阻塞的一类。如果在阻塞期间收到一个信号,该系统调用就被中断,不再继续执行;也可以设定系统调用是否重启。如,read,write,pause,wait…

其它系统调用: getpid, getppid, frok

慢速系统调用被中断的相关行为,实际上就是pause的行为:如read

1、想中断 pause,信号不能被屏蔽

2、信号的处理方式必须是捕捉

3、中断后返回-1,设置errno为EINTR,表示信号被中断

可修改sa_flags 参数来设置被信号中断后系统调用是否重启。SA_INTERRURT 不重启。SA_REATART重启

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/991915.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号