Linux学习笔记17——alarm、pause

1,alarm与pause的原型

#inlcude 
unsigned int alarm(unsigned int seconds);
int pause(void);
  1. alarm是一个闹钟,在指定的秒数之后发出一个SIGALRM信号;
  2. 但是,alarm函数自身是马上就返回的,不用等待n秒之后再返回,异步;
  3. 这个信号只能本进程自己接收;
  4. 如果在调用alarm时,前一个闹钟还没到触发信号的时候,则返回该闹钟的剩余时间,同时用新闹钟的时间替换旧闹钟的剩余时间;
  5. 如果前面没有闹钟,返回0;
  6. alarm(0)表示取消以前未触发的闹钟,返回旧闹钟的剩余时间,不会发出SIFALRM信号;
  7. pause使得本进程挂起,直到捕捉到任意一个信号;
  8. 只有执行了一个信号处理程序,并从其返回时,pause才返回;返回值-1;

2,sleep的第一种实现

我们用上面两个函数就能自己实现一个sleep的功能。如下:

#include 
#include 
#include 
#include 
#include 

void sig_alrm(int signo);
unsigned int sleep1(unsigned int seconds);

int main(){
	//10.10
	unsigned int seconds = 3; //指定时间
	printf("wait for %ds\n",seconds);
	sleep1(seconds);
	printf("End\n"); 
}

// 10.10 sleep的实现1
void sig_alrm(int signo)
{
	//啥都不干,就为了唤醒pause
	printf("recived SIGALRM\n");
}

unsigned int sleep1(unsigned int seconds)
{
	//1,注册信号
	if(signal(SIGALRM, sig_alrm) == SIG_ERR)
	{
		printf("can't catch SIGALRM !\n");
		exit(1);
	}

	//2, 定好闹钟发送信号
	alarm(seconds);

	//3,等待信号
	pause();

	//4,清除闹钟
	return(alarm(0));
}

调用alarm可以等待指定时间后触发信号,调用pause可以一直等待信号的出现。这两个一结合就是sleep的功能。

实现效果如下:

➜  code ./study_linux
wait for 3s
recived SIGALRM
End

但是,这个sleep的实现是有一些问题的:

  1. 我们这里用alarm来发信号,但是我们没有考虑到会不会在调用sleep1之前刚好有一个未触发的闹钟?如果有的话,那么我们会把别人的闹钟清除掉;
  2. 我们注册了SIGALRM,为它指定了一个处理程序,所以也就修改了原先的配置,或许人家原先是忽略这个信号或者使用默认值呢。那么我们在sleep结束前是不是应当恢复这些配置?
  3. alarm与pause之间有一个竞争关系。如果执行玩alarm之后,进入了子进程,而不是马上调用pause,那么就有可能在执行了SIGALRM的处理程序之后才调用pause,这样会造成pause一直不被唤醒。。。

3,sleep的第二种实现

现在我们用setjmp来解决第三种问题。

利用setjmp和longjmp可以实现在不同的函数间跳转。这是goto实现不了的功能。goto只能在函数内部跳转。

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

static jmp_buf env_alrm; //保存setjmp时的环境
unsigned int sleep2(unsigned int seconds);
int main(){
	//10.10
	unsigned int seconds = 5;
	printf("wait for %ds\n",seconds);
	sleep2(seconds);
	printf("End\n"); 
}

//sleep的第二种实现
void sig_alrm2(int signo)
{
	//跳回setjmp的地方,并返回1
	longjmp(env_alrm,1);
}

unsigned int sleep2(unsigned int seconds)
{
	//1,注册信号
	if(signal(SIGALRM, sig_alrm2) == SIG_ERR)
	{
		printf("can't catch SIGUSR1 !\n");
		exit(1);
	}

	//等于0表示第一次执行
	//等于1表示sig_alrm2已经被触发了,再返回这里
	if (setjmp(env_alrm) == 0)
	{
		//2, 定好闹钟发送信号
		alarm(seconds);

		//3,等待信号,
		//pause还是不能省略,不然不等信号发出就直接return了
		pause();
	}

	//4,清除闹钟
	return(alarm(0));
}

现在我们看看,就算在执行alarm之后由于某个原因,没有马上执行pause,等待n秒之后,触发了信号会怎么样?

这时执行了sig_alrm2,执行longjmp时,跳回到setjmp,并使得setjmp返回1,判断失败,于是return。

所以即便pause没有执行也不会出错。

4,实现一个限时的read功能

从标准输入读字符,如果到了限制的时间read还没有完成的话,就停止读取

我们可以在读取前设定闹钟,读取后取消闹钟;对大部分系统而言,一旦收到信号并进行处理,则read这种慢系统调用就会被终止。所以如果超过了时间read就算没有返回也会被动终止。

代码如下:

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

#define MAXLINE 1024

int main(){
	//带时间限制调用read,如果超过时间read还没有完成,则终止
	int n = 0; //read读取的字符数
	char line[MAXLINE];//read读取的内容

	//注册信号
	if(signal(SIGALRM, sig_alrm) == SIG_ERR)
	{
		printf("can't catch SIGUSR1 !\n");
		exit(1);
	}

	//设定闹钟
	alarm(20);

	//从标准输入读取
	//对大部分系统而言,捕获到信号并处理时,会使得read操作终止
	if ((n = read(STDIN_FILENO,line,MAXLINE)) < 0)
	{
		printf("read error !\n");
		exit(1);
	}

	//如果读完一行之后,闹钟还没触发,则取消闹钟
	alarm(0);

	//将读取的内容打印到屏幕上
	printf("write: \n");
	write(STDOUT_FILENO, line, n);
	exit(0);

}

可惜的是,对于ubuntu而言,捕获信号并不会使得read终止,而是会使得其重启。。。。

你可能感兴趣的:(学习笔记)