可重入函数
- 不含全局变量和静态变量是可重入函数的一个要素
- 可重入函数见man 7 signal
- 在信号捕捉函数里应使用可重入函数
- 在信号捕捉函数里禁止调用不可重入函数
例如:strtok就是一个不可重入函数,因为strtok内部维护了一个内部静态指针,保存上一 次切割到的位置,如果信号的捕捉函数中也去调用strtok函数,则会造成切割字符串混乱, 应用strtok_r版本,r表示可重入。
信号引起的竞态和异步I/O
时序竞态
int pause(void)
- 使调用进程挂起,直到有信号递达,如果递达信号是忽略,则继续挂起
int sigsuspend(const sigset_t *mask)
- 以通过指定mask来临时解除对某个信号的屏蔽,
- 然后挂起等待,
- 当被信号唤醒sigsuspend返回时,进程的信号屏蔽字恢复为原来的值
mysleep实现,这种实现方式是否存在BUG?
#include <unistd.h> #include <signal.h> #include <stdio.h> void sig_alrm(int signo) { /* nothing to do */ } unsigned int mysleep(unsigned int nsecs) { struct sigaction newact, oldact; unsigned int unslept; newact.sa_handler = sig_alrm; sigemptyset(&newact.sa_mask); newact.sa_flags = 0; sigaction(SIGALRM, &newact, &oldact); alarm(nsecs); pause(); unslept = alarm(0); sigaction(SIGALRM, &oldact, NULL); return unslept; } int main(void) { while(1){ mysleep(2); printf("Two seconds passed\n"); } return 0; }
mysleep改进版
unsigned int mysleep(unsigned int nsecs) { struct sigaction newact, oldact; sigset_t newmask, oldmask, suspmask; unsigned int unslept; /* set our handler,save previous information */ newact.sa_handler = sig_alrm; sigemptyset(&newact.sa_mask); newact.sa_flags = 0; sigaction(SIGALRM, &newact, &oldact); /* block SIGALRM and save current signal mask */ sigemptyset(&newmask); sigaddset(&newmask, SIGALRM); sigprocmask(SIG_BLOCK, &newmask, &oldmask); alarm(nsecs); suspmask = oldmask; sigdelset(&suspmask, SIGALRM); /* make sure SIGALRM isn't blocked */ sigsuspend(&suspmask);/* wait for any signal to be caught */ /* some signal has been caught,SIGALRM is now blocked */ unslept = alarm(0); sigaction(SIGALRM, &oldact, NULL);/* reset previous action */ /* reset signal mask, which unblocks SIGALRM */ sigprocmask(SIG_SETMASK, &oldmask, NULL); return(unslept); }
全局变量异步I/O
可重入函数
- 不含全局变量和静态变量是可重入函数的一个要素
- 可重入函数见man 7 signal
- 在信号捕捉函数里应使用可重入函数
避免异步I/O的类型
- sig_atomic_t 平台下的原子类型
- volatile 防止编译器开启优化选项时,优化对内存的读写
SIGCHLD信号处理
SIGCHLD的产生条件
- 子进程终止时
- 子进程接收到SIGSTOP信号停止时
- 子进程处在停止态,接受到SIGCONT后唤醒时
代码实现
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <sys/types.h> #include <sys/wait.h> #include <signal.h> void sys_err(char *str) { perror(str); exit(1); } void do_sig_child(int signo) { int status; pid_t pid; while ((pid = waitpid(0, &status, WNOHANG)) > 0) { if (WIFEXITED(status)) printf("child %d exit %d\n", pid, WEXITSTATUS(status)); else if (WIFSIGNALED(status)) printf("child %d cancel signal %d\n", pid, WTERMSIG(status)); } } int main(void) { pid_t pid; int i; //阻塞SIGCHLD for (i = 0; i < 10; i++) { if ((pid = fork()) == 0) break; else if (pid < 0) sys_err("fork"); } if (pid == 0) { int n = 18; while (n--) { printf("child ID %d\n", getpid()); sleep(1); } return i; }else if (pid > 0) { //先设置捕捉 //再解除对SIGCHLD的阻塞 struct sigaction act; act.sa_handler = do_sig_child; sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGCHLD, &act, NULL); while (1) { printf("Parent ID %d\n", getpid()); sleep(1); } } return 0; }
status处理方式
pid_t waitpid(pid_t pid, int *status, int options)
- options
- WNOHANG 没有子进程结束,立即返回
- WUNTRACED
- 如果子进程由于被停止产生的SIGCHLD, waitpid则立即返回
- WCONTINUED 如果子进程由于被SIGCONT唤醒而产生的SIGCHLD, waitpid则立即返回
- 获取status
- WIFEXITED(status) 子进程正常exit终止,返回真 WEXITSTATUS(status)返回子进程正常退出值
- WIFSIGNALED(status) 子进程被信号终止,返回真
- WTERMSIG(status)返回终止子进程的信号值 WIFSTOPPED(status)
- 子进程被停止,返回真 WSTOPSIG(status)返回停止子进程的信号值
- WIFCONTINUED(status) 子进程由停止态转为就绪态,返回真
向信号捕捉函数传参
sigqueue
int sigqueue(pid_t pid, int sig, const union sigval value) union sigval { int sival_int; void *sival_ptr; };
sigaction
void (*sa_sigaction)(int, siginfo_t *, void *) siginfo_t { int si_int; void *si_ptr; sigval_t si_value; ... } sa_flags = SA_SIGINFO /* POSIX.1b signal */ /* POSIX.1b signal */ /* Signal value */
实例
- 进程自己收发信号,在同一地址空间
- 不同进程间收发信号,不在同一地址空间,不适合传地址
信号中断系统调用
read阻塞时,信号中断系统调用:
- 返回部分读到的数据
- read调用失败,errno设成EINTER
总结
本文介绍了可重入函数,信号引起的竞态和异步I/O,SIGCHLD信号处理,向想好捕捉函数传参,信号中断系统调用。