引言
同步和异步的使用。
异步事件处理的两种方法:查询法、通知法。(单核机器不存在异步)
一、信号
1. 信号的概念
信号是软件中断。信号的响应依赖于中断。中断是底层硬件的机制。
2. signal函数
kill -l命令查看所有的信号。1 -31 属于标准信号 34 - 64属于实时信号
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
展示形式为: void (* signal(int signum, void (*func)(int) ) ) (int )
信号会打断阻塞的系统调用。
cpp
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void mySig()
{
write(1,".",1);
}
int main()
{
signal(SIGINT,SIG_IGN);//运行过程中忽略这个信号
signal(SIGINT,mySig);
int i;
for(i = 0;i<10;i++)
{
write(1,"*",1);
sleep(1);
}
return 0;
}
重构之前的mycpy.c 对于open write read 函数增加出现系统调用打断的行为判断
mycpy.c
cpp
#include <stdlib.h>
#include <stdio.h>
#include <error.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#define BUF_SIZE 1024*100
int main(int argc,char **argv)
{
int fps,fpd;
int ret;
char buf[BUF_SIZE];
int len,pos;
if(argc <3)
{
fprintf(stderr,"Usage:%s <src_file> <dest_file>\n",argv[0]);
exit(1);
}
do
{
fps = open(argv[1],O_RDONLY);
if(fps <0)
{
if(errno != EINTR)
{
perror("open");
exit(1);
}
}
}while(fps<0);
do
{
fpd = open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0600 );
if(fpd <0)
{
if(errno != EINTR)
{
close(fps);
perror("open");
exit(1);
}
}
}while(fpd<0);
while(1)
{
len = read(fps,buf,BUF_SIZE);
if(len < 0)
{
if(errno == EINTR)
continue;
perror("read()");
break;
}
if(len ==0)
break;
pos = 0;
while(len > 0)
{
ret = write(fpd,buf+pos,len);
if(ret <0)
{
if(errno == EINTR)
continue;
perror("write()");
exit(1);
}
pos += ret;
len-=ret;
}
}
close(fpd);
close(fps);
return 0;
}
3. 信号的不可靠
标准的信号会丢失,实时信号不会丢失。指的是信号的行为不可靠。(第一次调用还未结束,第二次调用就开始了)
4. 可重入函数
第一次调用还未结束,第二次调用不会出错叫可重入函数(reentrant)。所有的系统调用都是可重入的,一部分库函数也是可重入的,比如说:memcpy rand_r localtime_r memove
5. 信号的响应过程
mask(信号屏蔽字,32位一般情况都是1)pending(32位 位图,初始都为0),收到信号后pending位置1,内核在kernal态进入到user态时,会用mask值 & pending 获取发生的信号,将mask与pending全部置为0,执行函数,执行完毕将mask位置1。
信号从收到到响应有一个不可避免的延迟。
思考:
如何忽略掉一个信号? 将mask位置0
标准信号为什么会丢失? 在执行信号时,pending位置多次1,此时mask位是0
标准信号的响应没有严格的顺序。
不能从信号处理函数中随意的往外跳。(sigsetjmp,siglongjmp)
6. 相关常用函数
kill
发送一个信号到一个进程或进程组
raise
给当前进程或者线程发送一个信号
alarm
指定秒数,发送SIGALRM信号。默认alarm会杀掉当前程序。alarm不能实现多任务的计时器,只能执行最后一次设置的计时器。
alarm.c
cpp
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void mySig()
{
write(1,".",1);
}
int main()
{
alarm(5);
alarm(2);
alarm(10);
while(1)
pause();
#if 0
signal(SIGALRM,mySig);
int i;
for(i = 0;i<10;i++)
{
write(1,"*",1);
alarm(1);
sleep(1);
}
#endif
return 0;
}
5秒进行计数,不用alarm实现
5sec.c
cpp
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <time.h>
int main()
{
time_t end;
long long count = 0;
end = time(NULL)+5;
while(time(NULL)<= end)
{
count++;
}
printf("count = %lld \n",count);
return 0;
}
使用alarm实现
5sec_signal.c
cpp
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <time.h>
static int loop = 1;
void alrm_handler(int num)
{
loop = 0;
}
int main()
{
signal(SIGALRM,alrm_handler);
alarm(5);
long long count=0;
while(loop)
{
count++;
}
printf("count = %lld \n",count);
return 0;
}
gcc -S 5sec_signal.c 会产生 5sec_singal.s的汇编文件
使用-O1优化之后,程序会一直执行下去,因为程序中while中没有用到loop,优化后loop的值会直接从寄存器取程序while会一直成立,volatile关键字告诉程序要去真正的地址取数据,不要从内存获取
cpp
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <time.h>
static volatile int loop = 1; //从真实的空间去取数据
void alrm_handler(int num)
{
loop = 0;
}
int main()
{
signal(SIGALRM,alrm_handler);
alarm(5);
long long count=0;
while(loop)
{
count++;
}
printf("count = %lld \n",count);
return 0;
}
mycat.c
cpp
#include <stdlib.h>
#include <stdio.h>
#include <error.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#define BUF_SIZE 1024*100
int main(int argc,char **argv)
{
int fps,fpd=1;
int ret;
char buf[BUF_SIZE];
int len,pos;
if(argc <2)
{
fprintf(stderr,"Usage:%s <src_file> <dest_file>\n",argv[0]);
exit(1);
}
do
{
fps = open(argv[1],O_RDONLY);
if(fps <0)
{
if(errno != EINTR)
{
perror("open");
exit(1);
}
}
}while(fps<0);
while(1)
{
len = read(fps,buf,BUF_SIZE);
if(len < 0)
{
if(errno == EINTR)
continue;
perror("read()");
break;
}
if(len ==0)
break;
pos = 0;
while(len > 0)
{
ret = write(fpd,buf+pos,len);
if(ret <0)
{
if(errno == EINTR)
continue;
perror("write()");
exit(1);
}
pos += ret;
len-=ret;
}
}
close(fps);
return 0;
}
实例:使用单一计时器,构造一组函数实现,任意数量的计时器。
pause
等待一个信号来打断他,让程序不在忙等(while函数)
abort
给当前进程发送一个SIGABRT信号,并且产生一个core文件
system
调用shell完成shell命令。 execl("/bin/sh", "sh", "-c", command, (char *) NULL);
如果在信号中使用,需要block 这个SIGCHLD 信号,并且 忽略SIGINT SIGQUIT信号。
sleep
某些平台 用alarm+pause封装 ,当前平台用nanolseep封装,考虑到移植最好不要使用sleep.可以使用usleep,select
7. 信号集
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
8. 信号屏蔽字mask/pending集处理
/* Prototype for the glibc wrapper function
glibc包装器函数的原型
*/
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
/* Prototype for the underlying system call
底层系统调用的原型
*/
int rt_sigprocmask(int how, const kernel_sigset_t *set,
kernel_sigset_t *oldset, size_t sigsetsize);
/* Prototype for the legacy system call (deprecated)
旧系统调用的原型(已弃用)
*/
int sigprocmask(int how, const old_kernel_sigset_t *set,
old_kernel_sigset_t *oldset);
cpp
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void mySig()
{
write(1,".",1);
}
int main()
{
signal(SIGINT,mySig);
int i;
sigset_t set,old_set;
sigemptyset(&set);
sigaddset(&set,SIGINT);
for(i = 0;i<1000;i++)
{
sigprocmask(SIG_BLOCK,&set,NULL); //堵塞 SIGINT信号
for(int j=0;j<5;j++)
{
write(1,"*",1);
sleep(1); // 信号可以打断堵塞 的系统调用
}
write(1,"\n",1);
sigprocmask(SIG_UNBLOCK,&set,NULL); //解除堵塞信号
}
return 0;
}
cpp
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void mySig()
{
write(1,".",1);
}
int main()
{
signal(SIGINT,mySig);
int i;
sigset_t set,old_set;
sigemptyset(&set);
sigaddset(&set,SIGINT);
for(i = 0;i<1000;i++)
{
sigprocmask(SIG_BLOCK,&set,&old_set); // 保存之前的状态
for(int j=0;j<5;j++)
{
write(1,"*",1);
sleep(1); // 信号可以打断堵塞 的系统调用
}
write(1,"\n",1);
sigprocmask(SIG_SETMASK,&old_set,NULL); //回复当前的状态
}
return 0;
}
cpp
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void mySig()
{
write(1,".",1);
}
int main()
{
signal(SIGINT,mySig);
int i;
sigset_t set,old_set,save_set;
sigemptyset(&set);
sigaddset(&set,SIGINT);
sigprocmask(SIG_UNBLOCK,&set,&save_set);// 模块话编程,先保存之前的状态。
for(i = 0;i<1000;i++)
{
sigprocmask(SIG_BLOCK,&set,NULL);
for(int j=0;j<5;j++)
{
write(1,"*",1);
sleep(1); // 信号可以打断堵塞 的系统调用
}
write(1,"\n",1);
sigprocmask(SIG_UNBLOCK,&old_set,NULL);
}
sigprocmask(SIG_SETMASK,&save_set,NULL); //结束后,恢复之前保存的状态。
return 0;
}
9. 扩展
sigsuspend()
实现信号驱动程序。
susp.c
cpp
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void mySig()
{
write(1,".",1);
}
int main()
{
signal(SIGINT,mySig);
int i;
sigset_t set,old_set,save_set;
sigemptyset(&set);
sigaddset(&set,SIGINT);
sigprocmask(SIG_UNBLOCK,&set,&save_set);// 模块话编程,先保存之前的状态。
sigprocmask(SIG_BLOCK,&set,&old_set);
for(i = 0;i<1000;i++)
{
// sigprocmask(SIG_BLOCK,&set,&old_set);
for(int j=0;j<5;j++)
{
write(1,"*",1);
sleep(1); // 信号可以打断堵塞 的系统调用
}
write(1,"\n",1);
sigsuspend(&old_set);
/* sigsuspend的原子操作
sigset_t tmpset;
sigprocmask(SIG_SETMASK,&old_set,&tmpset); //解除阻塞
pause(); //响应信号
sigprocmask(SIG_SETMASK,&tmpset,NULL); //重新恢复信号。
*/
}
sigprocmask(SIG_SETMASK,&save_set,NULL); //结束后,恢复之前保存的状态。
return 0;
}
sigaction()
用于替换signal,signal有使用上的缺陷。
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);
};
第三个参数来源于:
#include <ucontext.h>
int getcontext(ucontext_t *ucp);
int setcontext(const ucontext_t *ucp);
struct sigaction sa;
sa.sa_handler = daemon_exit; //指定要执行的函数
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask,SIGINT)
sigaddset(&sa.sa_mask,SIGQUIT)
sigaddset(&sa.sa_mask,SIGTERM)
sa.sa_flag = 0;
sigaction(SIGINT,&sa,NULL);
while true ;do kill -ALRM 121812;done;SHELL脚本
重新更新mytbf,不响应终端的信号。
mytbf.c
cpp
#include <stdlib.h>
#include <stdio.h>
#include <error.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <sys/time.h>
#include "mytbf.h"
struct mytbf_st
{
int cps;
int burst;
int token;
int pos;
};
static struct mytbf_st* job[MYTBF_MAX];
static int inited = 0;
typedef void (*sighandler_t)(int);
struct sigaction alrm_sa_save;
static int get_free_pos(void)
{
for(int i=0;i< MYTBF_MAX;i++)
{
if(job[i]==NULL)
return i;
}
return -1;
}
static void alrm_action(int num,siginfo_t*infop,void *unsed)
{
if(!infop->si_code == SI_KERNEL) //响应kernal来的信号,一般从程序中发出就是kernal来的。
return;
for(int i=0;i<MYTBF_MAX;i++)
{
if(job[i] != NULL)
{
job[i]->token += job[i]->cps;
if(job[i]->token >job[i]->burst )
{
job[i]->token = job[i]->burst;
}
}
}
}
static void module_unload()
{
sigaction(SIGALRM,&alrm_sa_save,NULL);
struct itimerval itv;
itv.it_interval.tv_sec = 0;
itv.it_interval.tv_usec = 0;
itv.it_value.tv_sec = 0;
itv.it_value.tv_usec = 0;
setitimer(ITIMER_REAL,&itv,NULL);
for(int i=0;i<MYTBF_MAX;i++)
free(job[i]);
}
static void module_load()
{
struct sigaction sa;
sa.sa_sigaction = alrm_action;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO; //表示传入的是三参的形式
sigaction(SIGALRM,&sa,&alrm_sa_save);
struct itimerval itv;
itv.it_interval.tv_sec = 1;
itv.it_interval.tv_usec = 0;
itv.it_value.tv_sec = 1;
itv.it_value.tv_usec = 0;
setitimer(ITIMER_REAL,&itv,NULL);
atexit(module_unload);
}
mytbf_t* mytbf_init(int cps ,int burst) //C语言中,void*可以赋值给任何类型的指针,任何类型的指针也都可以赋值给void*
{
struct mytbf_st*me;
int pos;
if(inited == 0)
{
module_load();
inited = 1;
}
pos = get_free_pos();
if(pos < 0)
return NULL;
me = malloc(sizeof(*me));
if(me == NULL)
return NULL;
me->cps = cps;
me->burst = burst;
me->token = 0;
me->pos = pos;
job[pos] = me;
return me;
}
static int min(int token,int size)
{
if(token> size)
return size;
return token;
}
int mytbf_fetchtoken(mytbf_t*ptr,int size) //获取token
{
if(size <= 0)
return -EINVAL; //参数非法
struct mytbf_st*me = ptr;
while(me->token <= 0 ) //token为空就等待
pause();
// int n = min(me->token,size);
int n = (me->token>size?size:me->token);
me->token -= n;
return n;
}
int mytbf_returntoken(mytbf_t*ptr,int size) //返还token
{
if(size<=0)
return -EINVAL;
struct mytbf_st*me = ptr;
me->token+= size;
if(me->token > me->burst)
me->token = me->burst;
return size;
}
int mytbf_destroy(mytbf_t*ptr)
{
struct mytbf_st *me;
me = ptr;
job[me->pos] = NULL;
free(ptr);
return 0;
}
setitimer()
#include <sys/time.h>
int getitimer(int which, struct itimerval *curr_value);
int setitimer(int which, const struct itimerval *new_value,
struct itimerval *old_value)
比alarm时间更加的准确
struct itimerval {
struct timeval it_interval; /* Interval for periodic timer */
struct timeval it_value; /* Time until next expiration */
};
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
slowcat.c
cpp
#include <stdlib.h>
#include <stdio.h>
#include <error.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#define CPS 100
#define BUF_SIZE CPS
static volatile int loop = 0;
static void alrm_handler(int num)
{
// alarm(1);
loop = 1;
}
int main(int argc,char **argv)
{
int fps,fpd=1;
int ret;
char buf[BUF_SIZE];
int len,pos;
if(argc <2)
{
fprintf(stderr,"Usage:%s \n",argv[0]);
exit(1);
}
signal(SIGALRM,alrm_handler);
// alarm(1);
struct itimerval itv;
itv.it_interval.tv_sec = 1;
itv.it_interval.tv_usec = 0;
itv.it_value.tv_sec = 1;
itv.it_value.tv_usec = 1;
if(setitimer(ITIMER_REAL,&itv,NULL) <0)
{
fprintf(stderr,"setitimer:%s",strerror(errno));
}
do
{
fps = open(argv[1],O_RDONLY);
if(fps <0)
{
if(errno != EINTR)
{
perror("open");
exit(1);
}
}
}while(fps<0);
while(1)
{
//while(!loop);
while(loop == 0)
pause(); //用于等待一个打断的到来。不加则是忙等。
loop = 0;
while(1)
{
len = read(fps,buf,BUF_SIZE);
if(len < 0)
{
if(errno == EINTR)
continue;
perror("read()");
break;
}
break;
}
if(len ==0)
break;
pos = 0;
while(len > 0)
{
ret = write(fpd,buf+pos,len);
if(ret <0)
{
if(errno == EINTR)
continue;
perror("write()");
exit(1);
}
pos += ret;
len-=ret;
}
}
close(fps);
return 0;
}
int sigpending(sigset_t *set); 不清楚使用场景。
10. 实时信号
标准信号会丢失,实时信号不会丢失,需要排队,响应有一个顺序。
SIGUSR1 SIGUSR2是预留的信号。
susp_rt.c
cpp
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#define MYRTSIG (SIGRTMIN+6)
void mySig()
{
write(1,".",1);
}
int main()
{
signal(MYRTSIG,mySig);
int i;
sigset_t set,old_set,save_set;
sigemptyset(&set);
sigaddset(&set,MYRTSIG);
sigprocmask(SIG_UNBLOCK,&set,&save_set);// 模块话编程,先保存之前的状态。
sigprocmask(SIG_BLOCK,&set,&old_set);
for(i = 0;i<1000;i++)
{
// sigprocmask(SIG_BLOCK,&set,&old_set);
for(int j=0;j<5;j++)
{
write(1,"*",1);
sleep(1); // 信号可以打断堵塞 的系统调用
}
write(1,"\n",1);
sigsuspend(&old_set);
}
sigprocmask(SIG_SETMASK,&save_set,NULL); //结束后,恢复之前保存的状态。
return 0;
}
kill -40 pid 杀掉信号,可以使程序继续运行,切发送多次信号都会执行。排队次数用ulimit -i查看。