1,alarm与pause的原型
#inlcude <unistd.h>
unsigned int alarm(unsigned int seconds);
int pause(void);
- alarm是一个闹钟,在指定的秒数之后发出一个SIGALRM信号;
- 但是,alarm函数自身是马上就返回的,不用等待n秒之后再返回,异步;
- 这个信号只能本进程自己接收;
- 如果在调用alarm时,前一个闹钟还没到触发信号的时候,则返回该闹钟的剩余时间,同时用新闹钟的时间替换旧闹钟的剩余时间;
- 如果前面没有闹钟,返回0;
- alarm(0)表示取消以前未触发的闹钟,返回旧闹钟的剩余时间,不会发出SIFALRM信号;
- pause使得本进程挂起,直到捕捉到任意一个信号;
- 只有执行了一个信号处理程序,并从其返回时,pause才返回;返回值-1;
2,sleep的第一种实现
我们用上面两个函数就能自己实现一个sleep的功能。如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
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的实现是有一些问题的:
- 我们这里用alarm来发信号,但是我们没有考虑到会不会在调用sleep1之前刚好有一个未触发的闹钟?如果有的话,那么我们会把别人的闹钟清除掉;
- 我们注册了SIGALRM,为它指定了一个处理程序,所以也就修改了原先的配置,或许人家原先是忽略这个信号或者使用默认值呢。那么我们在sleep结束前是不是应当恢复这些配置?
- alarm与pause之间有一个竞争关系。如果执行玩alarm之后,进入了子进程,而不是马上调用pause,那么就有可能在执行了SIGALRM的处理程序之后才调用pause,这样会造成pause一直不被唤醒。。。
3,sleep的第二种实现
现在我们用setjmp来解决第三种问题。
利用setjmp和longjmp可以实现在不同的函数间跳转。这是goto实现不了的功能。goto只能在函数内部跳转。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <setjmp.h>
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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <setjmp.h>
#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终止,而是会使得其重启。。。。
本文介绍alarm与pause函数的原理及应用,演示如何使用这两个函数实现sleep功能,并探讨存在的问题及解决方案。此外,还展示了如何利用alarm实现限时读取。

519

被折叠的 条评论
为什么被折叠?



