时序竞态
unsigned int mysleep(unsigned int seconds) {
int ret;
struct sigaction newact, oldact;
newact.sa_handler = sig_alrm;
sigemptyset(&act.sa_mask);
acr.sa_flags = 0;
sigaction(SIGALRM,&newact,&oldact);
alarm(seconds);
//在这一步可能发生问题,因为失去cpu控制权,时间到了SIGALRM信号在这里触发
//等到获取cpu,将会立刻处理掉这个信号,等执行到pause()将会陷入一直挂起
int ret = pause();
sigaction(SIGALRM, &oldact, NULL);//用来恢复
return ret;
}
int sigsuspend(const sigset_t *mask); //挂起等待信号,原子操作
//调用期间,进程屏蔽字由mask指定,其功能与pause()相同
//改进版
unsigned int mysleep(unsigned int seconds) {
struct sigaction newact, oldact;
sigset_t newmask, oldmask, suspmask;
unsigned int unslept;
newact.sa_handler = sig_alrm;
sigemptyset(&act.sa_mask);
sigaction(SIGALRM,&newact,&oldact);
sigemptyset(&newact);
sigaddset(&newact.sa_mask);
sigprocmask(SIG_BLOCK, &newmask, &oldmask);
alarm(nsecs);
supsmask = oldmask;
sigdelmask(&suspmask, SIGALRM);
sigsuspend(&suspmask);
uslpet = alarm(0);
sigaction(SIGALARM,&oldact,NULL);
sigprocmask(SIG_SETMASK, &oldmask, NULL);
return uslpet;
}
全局变量的异步I/O问题
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
int n = 0, flag = 0;
void sys_err(char* str) {
perror(str);
exit(0);
}
void do_sig_child(int num) {
printf("I am child %d\t%d\n", getpid(), n);
n += 2;
flag = 1;
//sleep(1);
}
void do_sig_parent(int num) {
printf("I am parent %d\t%d\n", getpid(), n);
n += 2;
flag = 1;
//sleep(1);
}
int main(void) {
pid_t pid;
struct sigaction act;
if((pid = fork()) < 0) {
sys_err("fork");
} else if( pid > 0) {
n = 1;
sleep(1);
act.sa_handler = do_sig_parent;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGUSR2, &act, NULL);
do_sig_parent(0);
while(1) {
if(flag == 1) {
kill(pid, SIGUSR1);
flag = 0;
}
}
} else {
n = 2;
act.sa_handler = do_sig_child;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGUSR1,&act,NULL);
while(1) {
if(flag == 1) {
kill(getppid(),SIGUSR2);
flag=0;
}
}
}
return 0;
}
从上面的代码的执行,最终会自动停止,因为没sleep去做同步。如果sleep没有被注释的话,结果是正常的。
经过分析可以发现flag是用来进行标记程序的执行进度的。在 kill() 函数执行后,还没有对flag的值进行修改,这时可能因为kernel进行调度,对方可能会发送信号回调捕捉函数,从而修改了flag的值。
解决:
- 不要使用全局变量在多个进程间
- 加锁处理
可重入函数
程序没执行完(程序执行的过程中),又一次被执行了,称为重入。
如果函数的结果因为重入发生了变化,发生了不可预料的变化。则该函数不是可重入函数。
注意:
- 定义可重入函数,不可出现static变量、全局变量、不能使用malloc、free。
- 信号捕捉函数应设计为可冲入函数。
- 信号处理程序可以调用的可重入函数可以看 man 7 signal
- 没有包含上述列表中的函数大多为不可重入函数,因为:
- 使用了静态数据结构
- 调用了malloc或者free
- 是标准I/O函数
SIGCHLD信号
产生条件:
- 子进程终止时
- 子进程接收到SIGSTOP信号停止时
- 子进程处在静止态,接收到SIGCONT后唤醒时
信号传参
发送信号时
int sigqueue(pid_t pid, int sig, const union sigval value); //成功0,失败-1,设置errno
union sigval {
int sival_int;
void* sival_ptr;
};
//向指定进程发送指定信号时,携带数据。如,传地址,需要注意的是,不同进程有不同的虚拟地址空间。
//也就是说,将本进程的虚拟地址传给其他进程是没有用的。
捕捉信号时
int sigaction(int signum, const struct sigaction* act, struct sigaction* oldact);
struct sigaction{
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void*);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
当注册信号捕捉函数,希望获得更多信息时,不用sa_handler应使用sa_sigaction.但此时sa_flag必须为SA_SIGINFO。其中siginfo_t是一个结构体,可携带各种信号相关的数据。
中断系统调用
慢速系统调用在执行过程中被信号打断,这一过程称为中断系统调用。
慢速系统调用
造成进程永久阻塞
eg:wait() waitpid() pause() read()管道、设备
其他系统调用
一调用就结束。
慢速系统调用被中断后的行为
参见pause()
的行为:
- 信号不能被屏蔽。
- 信号的处理方式必须是捕捉。
- 中断后返回-1,设置errno为EINTR。
- sa_flags设置被信号中断后的系统调用是否重启:
- SA_INTERRURT不重启
- SA_RESTART重启