全局跳转
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]$ |