【笔记】Linux应用编程的信号机制

上一节的一点尾巴: 使用alarm+pause实现sleep功能

pause的作用是将内核挂起,减小资源使用。但是当接受到信号的时候就会被唤醒。

alarm(3);
pause();

等同于sleep(3);

alarm(3);
signal(SIGALRM , SIG_IGN);	//SIGNAL_ignore:处理方式,忽略这个信号	
pause();	

等同于不占用资源的while(1)


5.1 什么是信号

正式的程序都会接触信号的,我们学到这些内容是比较重要的。
学习一下api,并且理解信号。

【信号】信号是 内容受限的一种异步通信机制。 内容受限:只能发信号,不可以发字符、文字、图像等
信号的目的:用于通信。
信号的特点:异步性。
信号的本质:是int型数字编号。

信号给人的感觉像是 软件中断。发什么信号,怎么发,谁发,是完全不知道的。

信号的举例:
用户发出:CTRL+C kill 按键
内核发出:硬件发生异常后(比如除以0导致出错),操作系统内核发出信号
条件满足发出:如alarm时间到产生SIGALARM信号。管道读端关闭,尝试写会产生SIGPIPE信号

信号的接受者处理方法:
忽略信号
捕获信号 信号绑定一个函数
默认处理 当前进程没有明显的管这个函数,忽略信号或者终止进程


5.2常用信号
系统定义好的信号:
SIGINT 2 重要!CTRL+C送给前台的每个进程 Signal interrupt
SIGABRT 6 调用abort函数,进程异常终止
SIGPOLL 8
SIGIO 8 重要!指示一个异步事件 高级io提及
SIGKILL 9 重要!KILL -9 ,杀死一个进程的终极方法,他不能被拦截,但需要一定的权限。
SIGSEGV 11 无效存储访问时OS的信号。访问了不该访问的内容。
SIGPIPE 13 重要!管道和socket
SIGCHILD 17 重要!子进程终止,父进程收到这个信号就可以回收

没有事先定义好的信号有两个:
SIGUSR1 10
SIGUSR2 12 功能是由用户自己决定的


5.3 进程对信号的处理

signal() 函数
signal(int signum,handler)
处理方法 返回值也是sighandler_t处理方法,出错会返回SIGERR,没出错不变

注意sighandler_t可能找不到,这个时候去自己 typedef,man手册里面有,其实就是int

【用signal函数处理SIGINT信号】

include
int main()
{
	ptf(before while1)
	while(1)
	{

	}	
	ptf(after while1)	 	
	ret0;
}

//这个时候ctrl+C会接收SIGINT,默认情况是打断进程的执行,即终止进程。
我们有什么办法通过CTRL+C只打断while1呢?

【用signal函数绑定一个处理方法】。注意在不同linux发行版的 移植性不是很好,可能出问题。

inc
void fun(int sig)
{
	printf(处理signal: ? , sig)
}

int main()
{
	signal(SIGINT , 函数指针fun)
	ptf(before while1)
	while(1)
	{

	}	
	ptf(after while1)	 	
	ret0;
}

这个时候ctrl+C,程序不会终止,而是会打印出一句,处理signal:2!没法杀死他了嘿。kill -9或者关闭终端。
进程收到信号时候会执行fun函数。函数的参数就是signal编号。

这里的sig参数,因为与signal绑定了,他是为了100%保证没有错误。比如说多个信号绑定了这个函数。
类比:独立中断和共享中断。

关于处理方法
signal(SIGINT , SIG_DFL) 设置为默认处理。。。额好吧这一句是完全多余的
signal(SIGINT , SIG_IGN) 设置为忽略处理,ctrlC之后无效果,只好强制结束了
signal(SIGKILL , SIG_IGN) 不会成功的,kill在设计的时候指定了不可忽略。Invalid arguement!

signal有个问题,你要建立一个捕获函数的时候是有移植问题的,可能不兼容。
解决方式是使用sigaction(signum,const传参_结构体指针action,输出型参数_结构体指针action)
可以一次得到设置新的捕获函数和旧的捕获函数,也可以用NULL跳过其中的某一个设置。


6.1 Linux 高级IO
Linux一切皆是文件。
高级IO里面也是读文件和写文件,高级IO要解决一些复杂的问题

非阻塞IO

阻塞IO:放在旁边照顾后面的进程,这是LINUX内核的一个常态。
父进程阻塞,等待回收子进程,收到SIGCHILD就去回收子进程。
常见的 阻塞式:wait pause sleep;

阻塞式的好处:非常有利于系统的性能发挥,他是Linux系统设计的默认方式。让CPU时刻在工作状态下。

为什么要实现非阻塞?
阻塞式在多路io时候会遇到问题
比如我同时读鼠标、键盘。
我希望动了键盘来唤醒一个进程,但是设计成了阻塞式鼠标触发,这样我鼠标不动可能会卡一辈子。
这个过程称为多路IO!

实现非阻塞?方法有二:

O_NONBOCK打开文件 的时候设置
fcntl打开文件之后,对fd进行操作,对其添加一个NONBLOCK模式进去


6.2
程序读键盘 标准输入设备,stdin
程序读鼠标
程序读键盘和鼠标

1键盘

inc
main()
{
	ptf "before read"		//查看阻塞效果
	read(0 , buf , 2);
	ptf %s, buf;	
}

现象是卡在read那一句。只打印了read之前一句,他读fd 0 ,因为没有按键盘就没有读到东西,这样就进入默认阻塞了
写入abcd还不出,是因为LINXU是行缓冲的,按个回车才行。
按回车:读出的 是 ab

2鼠标

inc
main()
{
	int fd = open(鼠标设备文件)	//注意:鼠标不是标准输入,/dev/input/下去找 mouse,cat去测试
				cat测试的时候,晃一下鼠标就行,会出来好多乱码。
				乱码是二进制文件 以 cat字符格式输出造成的。
	ptf before
	read fd
	ptf %s,buf	
}

读出来的就是乱码。

3同时读键盘和鼠标
默认:东西没有的时候会被阻塞住,等待被唤醒
现在:鼠标键盘都可以被读到,但是用户会非常不爽,必须要配合,这样的设计时不合乎使用的。

inc
main()
{
	//读鼠标,ptf , 为了配合这个程序,读到这里必须动鼠标

	memset buf
	
	//读键盘,ptf, 为了配合这个程序,读到这里必须动键盘,顺序还不能反
}

阻塞式IO的困境:我的程序阻塞在了鼠标这里,想去读键盘就毫无反应。后面等待处理的键盘急得要死但是由于被坑了,但是也没有办法。软件没问题,问题就是阻塞式IO的缺陷依然存在。

signal的升级版:sigaction。


6.3 fcntl实现非阻塞

inc 
main
{
	//读键盘
	int flag = -1
	             = fcntl (0 ,F_GETFL);		//先获取原来的 flag,
	flag |= O_NONBLOCK		//原来的flag添加非阻塞
	
	fcntl(0 ,	 F_SETFL,	 flag);	
	   那个文件   设置flag   传参flag

	//这三步之后,0,也就是stdin就是非阻塞式的了
	read
	ptf	after;
}

运行之后立马出来,打印after,而且什么都没读出来。

读鼠标的话,open的时候直接加O_NONBLOCK就行了,很简单

鼠标+键盘:
before
读出鼠标:空的
读出键盘:空的
after
程序结束

这也不是最终想要的效果
改进:

	while(1)
	{
		memset buf,读鼠标ret = read()
		if (ret > 0 )
			ptf( %s,  buf)		//read返回值是读到的个数,读到>0就打印

		memset buf,读键盘ret = read()
		if(ret >0)
			ptf %s , buf
	}

准确的说,这样的性能不是特别好。非阻塞式虽然能实现,但是不如多路复用IO和异步IO。涉及到CPU的调度,有些难度后面再说。


6.5 IO多路复用

IO多路复用技术:应对一些阻塞式IO的问题
复习一下非阻塞式的缺陷:那就是必须轮询read+print,CPU不得空闲,是一种非常本质的缺陷。

于是我们引入了select() 和 poll()两个函数,他们是unix的两个派别引起的分歧,其思想一样,外部特征不一样。
IO多路复用原理
外部阻塞,内部非阻塞。

假设有A和B两个部分进行多路复用

循环(1)			//这里的循环用了一定的方法降低了CPU 使用资源
{
	A阻塞
	B阻塞  
}

[select的简单使用]

inc
main
{

fd_set myset;
fd = open鼠标

FD_ZERO(&myset)		先把他用宏清零,注意传的是指针
FD_SET( fd , &myset)
FD_SET (0 ,&myset)			//把我们的fd添加进select准备复用的列表里,然后就可以select了

struct timeval tm;
tm.tv_sec =10;
tm.tv_usec =10;			//超时时间定时10s 0us
 
	ret = select (  fd+1  , 	&myset ,	NULL , 	NULL,	 &tm);
		      nfds					超时时间
	if(ret < 0)
		error
	else if ret==0
		超时
	else			//大于0,意味着一路IO到达,在此处理我们列表里的鼠标或键盘
		if(FD_ISSET(fd, &myset))
			//处理鼠标
			memset;		read;		printf		

		if(FD_ISSET(0 ,&myset))	
			//处理键盘
			memset;		read;		printf		
}
 

7.2 线程
Linux中的线程是脱开于进程的,可以说是一种轻量级进程,是参与内核调度的最小单元
一个进程中可以有多个线程

【Eg:使用线程实现同步键鼠】

inc
void *func(void *arg)
{
	//辅程序(while1读鼠标)--子线程
}
	
main
{
	pthread_t th = -1;
		
	create_pthread( &th , NULL ,	 func , 	NULL  )
			  线程属性      函数	给线程的传参
	
	//主程序(while1读键盘)--父线程
	fd =open..mouse0
	...
	while1
}

编译要使用gcc xxx.c -lpthread 使用了链接库,不写的话会报链接错误。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值