全局跳转
UNIX下的C语言中,有一对特殊的调用:跳转函数, 原型如下:
#include <setjmp.h> int setjmp(jmp_buf env); void longjump(jmp_buf env, int val); |
函数setjmp存储当前的堆栈环境(包括程序的当前执行位置)到参数env中,当函数正常调用成功时返回0. 函数longjmp恢复保存在env中堆栈信息, 并使程序转移到env中保存的位置处重新执行. 这两个函数联合使用, 可以实现程序的重复执行.
函数longjmp调用成功后, 程序转移到函数setjmp处执行, 函数setjmp返回val. 如果参数val的取值为0, 为了与上次正常调用setjmp相区别,函数setjmp将自动返回1.
下面是一个使用了跳转语句的例子, 它跳转两次后退出.
[bill@billstone Unix_study]$ cat jmp1.c #include <setjmp.h>
int j = 0; jmp_buf env;
int main() { auto int i, k = 0;
i = setjmp(env); printf("setjmp = [%d], j = [%d], k = [%d]/n", i, j++, k++); if(j > 2) exit(0); sleep(1); longjmp(env, 1);
return 0; } [bill@billstone Unix_study]$ make jmp1 cc jmp1.c -o jmp1 [bill@billstone Unix_study]$ ./jmp1 setjmp = [0], j = [0], k = [0] setjmp = [1], j = [1], k = [1] setjmp = [1], j = [2], k = [2] [bill@billstone Unix_study]$ |
其中, j记录了程序的执行次数. 按理说, k的值应该保持不变, 因为当返回到setjmp重新执行时, 保存的堆栈中k应该保持0不变, 但实际上却变化了. 请高手指点, 是不是setjmp本身实现的问题(我用的环境是Red Hat 9)?
单线程I/O超时处理
UNIX下的I/O超时处理是一个很常见的问题, 它的通常做法是接收输入(或发送输出)后立刻返回, 如果无输入(或输出)则n秒后定时返回.
一般情况下, 处理UNIX中I/O超时的方式有终端方式, 信号跳转方式和多路复用方式等三种. 本节设计一个定时I/O的例子, 它从文件描述符0中读取一个字符, 当有输入时继续, 或者3秒钟后超时退出,并打印超时信息.
(1) 终端I/O超时方式
利用ioctl函数, 设置文件描述符对应的标准输入文件属性为”接收输入后立刻返回, 如无输入则3秒后定时返回.
[bill@billstone Unix_study]$ cat timeout1.c #include <unistd.h> #include <termio.h> #include <fcntl.h>
int main() { struct termio old, new; char c = 0;
ioctl(0, TCGETA, &old); new = old; new.c_lflag &= ~ICANON; new.c_cc[VMIN] = 0; new.c_cc[VTIME] = 30; // 设置文件的超时时间为3秒 ioctl(0, TCSETA, &new); if((read(0, &c, 1)) != 1) printf("timeout/n"); else printf("/n%d/n", c); ioctl(0, TCSETA, &old);
return 0; } [bill@billstone Unix_study]$ make timeout1 cc timeout1.c -o timeout1 [bill@billstone Unix_study]$ ./timeout1 x 120 [bill@billstone Unix_study]$ ./timeout1 timeout [bill@billstone Unix_study]$ |
(2) 信号与跳转I/O超时方式
在read函数前调用setjmp保存堆栈数据并使用alarm设定3秒定时.
[bill@billstone Unix_study]$ cat timeout2.c #include <setjmp.h> #include <stdio.h> #include <unistd.h> #include <signal.h>
int timeout = 0; jmp_buf env;
void timefunc(int sig){ timeout = 1; longjmp(env, 1); }
int main() { char c;
signal(SIGALRM, timefunc); setjmp(env); if(timeout == 0){ alarm(3); read(0, &c, 1); alarm(0); printf("%d/n", c); } else printf("timeout/n");
return 0; } [bill@billstone Unix_study]$ make timeout2 cc timeout2.c -o timeout2 [bill@billstone Unix_study]$ ./timeout2 v // 需要按Enter健激活输入 118 [bill@billstone Unix_study]$ ./timeout2 timeout [bill@billstone Unix_study]$ |
(3) 多路复用I/O超时方式
一个进程可能同时打开多个文件, UNIX中函数select可以同时监控多个文件描述符的输入输出, 进程将一直阻塞, 直到超时或产生I/O为止, 此时函数返回, 通知进程读取或发送数据.
函数select的原型如下:
#include <sys/types.h> #include <sys/times.h> #include <sys/select.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); FD_CLR(int fd, fd_set *fdset); // 从fdset中删去文件描述符fd FD_ISSET(int fd, fd_set *fdset); // 查询文件描述符是否在fdset中 FD_SET(int fd, fd_set *fdset); // 在fdset中插入文件描述符fd FD_ZERO(fd_set *fdset); // 清空fdset |
参数nfds是select监控的文件描述符的时间, 一般为监控的最大描述符编号加1.
类型fd_set是文件描述符集合, 其元素为监控的文件描述符.
参数timeout是描述精确时间的timeval结构,它确定了函数的超时时间,有三种取值情况:
a) NULL. 函数永远等待, 直到文件描述符就绪.
b) 0. 函数不等待, 检查文件描述符状态后立即返回.
c) 其他值. 函数等待文件描述符就绪, 或者定时完成时返回.
函数select将返回文件描述符集合中已准备好的文件总个数. 函数select返回就绪文件描述符数量后, 必须执行read等函数, 否则函数继续返回就绪文件数.
[bill@billstone Unix_study]$ cat timeout3.c #include <stdio.h> #include <sys/types.h> #include <sys/times.h> #include <sys/select.h>
int main() { struct timeval timeout; fd_set readfds; int i; char c;
timeout.tv_sec = 3; timeout.tv_usec = 0; FD_ZERO(&readfds); FD_SET(0, &readfds); i = select (1, &readfds, NULL, NULL, &timeout); if(i > 0){ read(0, &c, 1); printf("%d/n", c); } else if(i == 0) printf("timeout/n"); else printf("error/n");
return 0; } [bill@billstone Unix_study]$ make timeout3 cc timeout3.c -o timeout3 [bill@billstone Unix_study]$ ./timeout3 x 120 [bill@billstone Unix_study]$ [bill@billstone Unix_study]$ ./timeout3 timeout [bill@billstone Unix_study]$ |