信号实现并发
同步
异步/并发:何时发生?什么结果?都是未知的
异步时间的处理:
- 查询法:不断询问事件的发生(适合高频率)
- 通知法:事件发生的时候反馈(适合低频率)
- 信号是什么?概念
信号的软件中断
信号的响应有依赖于中断
中断是个硬件概念 - void (*signal(*int signum,void (*func)(int)))(int); 信号的发出函数
命令 kill -l
1-34标准信号 34-…
多数的作用都是终止+core (core文件)
ulimit -a 看一下core文件大小,默认是0,因为经常出现段错误,会产生很多
信号会打断阻塞的系统调用
#include <signal.h>
typedef void (*sighandler_t)(int); // 函数指针重名为sighandler_t
sighandler_t signal(int signum, sighandler_t handler);
-
信号的不可靠
信号行为不可靠,执行现场是内核布置,第一次调用没有结束,第二次调用开始了,第二次结果会将第一次结果覆盖:localtime -
可重入函数
第一次调用没有结束,第二次调用开始了,但是不会出错,就是可重入
所有的系统调用都是可重入的,一部分库函数也是可重入的(尤其是返回值是指针类型的函数),eg:memcpy拷贝内存空间 _r后缀的函数 -
信号的响应过程
信号从收到到响应有一个不可避免的延迟
信号如何忽略掉一个信号的?
标准信号为什么要丢失?
标准信号的响应,没有严格的顺序
答1:信号的忽略的做法,就是将umask的该位置设置成0,那么&之后永远都是0,不会被响应
答2:每次接收信号都是将pending的那个信号位置设置为1,如果连续来多个同一个信号,他们的作用都是将那一位设置为1,多个来也没有作用,这是标准信号丢失的原因
- 信号常用函数
- kill(); kill - send a signal to a process
// 成功返回0 失败返回-1,修改errno
int kill(pid_t pid, int sig); // pid >0 发送给pid进程
// pid = 0 发送给同组每一个进程(组内广播)
// pid = -1 发送给当前进程有权限发送的 每一个进程(除了init)本质只有init会使用
// pid < -1 发送给|pid|组内的所有进程
// sig为0 就是没有信号
- raise();
#include <signal.h>
int raise(int sig); // 给自己进程发信号
- alarm(); // 可以实现每秒。。。即流量控制
alarm函数的默认作用是到达时间后,调用该函数的进程终止,但是一般情况下调用进程都会捕获该信号,
如果想捕捉SIGALRM信号,则必须在调用alarm之前安装该信号的处理程序,
如果先调用alarm,然后在我们能够安装SIGALRM处理程序之前已经接到该信号,那么进程将终止(摘取自apue 第三版p268)
pause
函数使调用进程挂起,直到捕捉到一个信号,继续执行,eg捕获一个ALRM信号
#include <unistd.h>
unsigned int alarm(unsigned int seconds); // 计时为0时会发送alarm信号
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
alarm(5); // 5s后发送 alarm信号,会杀死当前进程
alarm(1);
alarm(5);
while (1)
{
/* code */
}
return 0;
}
实现一个cat
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#define CPS 10
#define BUFFSIZE CPS
static volatile int loop = 0;
static void alrm_handler(int s)
{
alarm(1); // 第一次执行的时候,发出下一次执行的时钟信号
loop = 1;
}
int main(int argc, char ** argv)
{
if (argc < 2)
{
fprintf(stdout, "usage");
exit(1);
}
signal(SIGALRM, alrm_handler); // 接收时钟信号,
alarm(1); // 一秒钟发一个时钟信号 1s后会执行时钟处理函数
int sfd = 1;
int dfd = 1;
do
{
// 信号检查
sfd = open(argv[1], O_RDONLY);
if (sfd < 0)
{
if (errno != EINTR)
{
strerror(errno);
exit(1);
}
}
} while (sfd < 0);
// const int SIZE = 8192;
char buf[BUFFSIZE];
int count = 0;
while (1)
{
// loop初始导致这里阻塞 1s之后
while (!loop)
{
pause(); // loop == 0 当alrm信号到来时,会打断pause系统调用,再次查看循环条件
}
loop = 0; // 死循环下次再到while的时候,又pause,等待时钟信号到来,再跳出执行读写
int len = 0; // read 返回值
int ret = 0; // write 返回值
int pos = 0; // 写截止的位置
while ((len = read(sfd, buf, BUFFSIZE)) < 0)
{
if (len < 0)
{
if (errno == EINTR)
continue;
strerror(errno);
break;
}
}
if (len == 0)
{
break;
}
// 防止写入不足
while (len > 0)
{
ret = write(dfd, buf + pos, len); // 读出的结果写入到目标文件中
if (ret < 0)
{
if (errno == EINTR)
continue;
printf("%s\n", strerror(errno));
exit(1);
}
pos += ret;
len -= ret;
}
count++;
}
// printf("%d\n", count);
close(sfd);
return 0;
}
令牌桶实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#define CPS 10
#define BUFFSIZE CPS
#define BURST 100
// 上限,最大积攒100个字节,每次是10个字节
// 令牌桶实例
static volatile sig_atomic_t token = 0; // 信号原子类型,这种类型的修改,机器会保证都是原子操作
static void alrm_handler(int s)
{
alarm(1); // 第一次执行的时候,发出下一次执行的时钟信号
token++;
if (token > BURST)
{
token = BURST;
}
}
int main(int argc, char ** argv)
{
if (argc < 2)
{
fprintf(stdout, "usage");
exit(1);
}
signal(SIGALRM, alrm_handler); // 接收时钟信号
alarm(1); // 一秒钟发一个时钟信号 1s后会执行时钟处理函数 发出第一个alrm信号
int sfd = 1;
int dfd = 1;
do
{
// 信号检查
sfd = open(argv[1], O_RDONLY);
if (sfd < 0)
{
if (errno != EINTR)
{
strerror(errno);
exit(1);
}
}
} while (sfd < 0);
// const int SIZE = 8192;
char buf[BUFFSIZE];
int count = 0;
while (1)
{
// token初始导致这里阻塞 1s之后
while (token <= 0) // 没有字节就等着
{
pause(); // 等待有信号打断pause 1s之后打断
}
// 累加
token--; // 执行一次 , 读10 写10 消耗一个令牌
int len = 0; // read 返回值
int ret = 0; // write 返回值
int pos = 0; // 写截止的位置
while ((len = read(sfd, buf, BUFFSIZE)) < 0) // token的作用在这里,如果时钟信号打断,token会增加到23445...积攒处理次数
{
if (len < 0)
{
if (errno == EINTR)
continue;
strerror(errno);
break;
}
}
if (len == 0)
{
break;
}
// 防止写入不足
while (len > 0)
{
ret = write(dfd, buf + pos, len); // 读出的结果写入到目标文件中
if (ret < 0)
{
if (errno == EINTR)
continue;
printf("%s\n", strerror(errno));
exit(1);
}
pos += ret;
len -= ret;
}
count++;
}
// printf("%d\n", count);
close(sfd);
return 0;
}
使用单一计时器,构造一组函数,实现任意数量的计时器
- abort();
- system();
- sleep();
- 信号集
- 信号屏蔽字/判定集的处理
- 扩展
- sigsuspend();
#include <signal.h>
int sigsuspend(const sigset_t *mask);
信号驱动程序
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
static void int_handler(int s)
{
write(1, "!", 1);// 终端输出一个!
}
int main()
{
sigset_t set, oset, saveset;
signal(SIGINT, int_handler); // 发出中断信号的时候,打印一个!
sigemptyset(&set);
sigaddset(&set, SIGINT);
sigprocmask(SIG_UNBLOCK, &set, &saveset); // 保存当前场景的做法,一般使用UNBLOCK,把set中的信号unblock,把之前的状态存到saveset中
sigprocmask(SIG_BLOCK, &set, &oset); // 保存当前场景的做法,一般使用UNBLOCK,把set中的信号unblock,把之前的状态存到saveset中
for (int j = 0; j < 1000; j++)
{
// sigprocmask(SIG_BLOCK, &set, &oset); // 对信号集合中的信号进行屏蔽,集合中只有中断信号,保存当前信号集到oset
sigprocmask(SIG_BLOCK, &set, NULL);
// 执行不受到影响---不响应
for (int i = 0; i < 5; i++)
{
write(1, "*", 1); // 1的fd1
sleep(1);
}
write(1, "\n", 1); // 1的fd1
sigsuspend(&oset); // 一旦解除oset信号集阻塞,立即进入等待状态
// sigset_t tmpset;
// sigprocmask(SIG_SETMASK, &oset, &tmpset);
// pause();
// sigprocmask(SIG_SETMASK, &tmpset, NULL);
}
sigprocmask(SIG_SETMASK, &saveset, NULL); // 与保存状态方法互补,恢复saveset之前的状态
return 0;
}
- sigaction();
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
// 对signum信号定义新的行为 sigaction,保存旧的信号行为oladact不保存也可以为空
struct sigaction {
void (*sa_handler)(int); // 信号处理函数
void (*sa_sigaction)(int, siginfo_t *, void *); //
sigset_t sa_mask; // 在响应信号的时候,将一个集合的信号阻塞住
int sa_flags;
void (*sa_restorer)(void);
};
// siginfo_t
siginfo_t {
int si_signo; /* Signal number */
int si_errno; /* An errno value */
int si_code; /* Signal code */
// 常用
...
}
流量控制,仅内核信号可中断,使用sigaction,setitimer函数重构
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/time.h>
#include "mytbf.h"
// 结构体隐藏到.c中编译成库
struct mytbf_st
{
int cps;
int burst;
int token;
int pos;
};
// typedef void (*sighandler_t)(int); // 标准中抽象出来是,要实现重命名
// static sighandler_t alrm_handler_save;
static int inited = 0;
static struct mytbf_st* job[MTBF_MAX];
void static alrm_action(int s, siginfo_t* infop, void* used)
{
// 仅仅响应内核信号
if (infop->si_code != SI_KERNEL)
{
return;
}
// alarm(1);
for (int i = 0; i < MTBF_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 struct sigaction alrm_sa_save;
static void module_unload(void)
{
// signal(SIGALRM, alrm_handler_save); // 恢复
// alarm(0);
struct itimerval itv;
sigaction(SIGALRM, &alrm_sa_save, NULL); // 恢复
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 < MTBF_MAX; i++)
{
free(job[i]);
}
}
static void module_load()
{
// alrm_handler_save = signal(SIGALRM, alrm_handler); // 被修改的信号返回
// alarm(1);
struct sigaction sa;
struct itimerval itv;
sa.sa_sigaction = alrm_action;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO; // 第三个参数,表示使用的是三参的处理函数
if (sigaction(SIGALRM,&sa, &alrm_sa_save) < 0) // 第三个参数是保存
{
perror("");
exit(2);
}
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); // 钩子函数
}
static int min(int a, int b)
{
return a < b ? a : b;
}
static int get_free_pos(void)
{
for (int i = 0; i < MTBF_MAX; i++)
{
if (job[i] == NULL)
{
return i;
}
}
return -1;
}
mytbf_t* mytbf_init(int cps, int burst)
{
struct mytbf_st *me;
if (inited == 0)
{
module_load();
inited = 1;
}
int pos = get_free_pos();
if (pos < 0)
{
// 没有空位了
return NULL;
}
me = malloc(sizeof(*me));
if (me == NULL)
{
return NULL;
}
me->token = 0;
me->burst = burst;
me->cps = cps;
me->pos = pos;
job[pos] = me; // 把一个结构体指针存放入数组
return me;
}
int mytbf_fetchtoken(mytbf_t* ptr, int size)
{
struct mytbf_st *me = ptr;
if (size <= 0)
return -1;
while (me->token <= 0)
pause();
int n = min(me->token, size);
me->token -= n;
return n;
}
int mytbf_returntoken(mytbf_t *ptr, int size)
{
struct mytbf_st *me = ptr;
if (size <= 0)
return -1;
me->token += size;
if (me->token > me->burst)
me->token = me->burst;
return size;
}
int mytbf_destory(mytbf_t *ptr)
{
struct mytbf_st *me = ptr;
job[me->pos] = NULL;
free(ptr);
}
重构守护进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <syslog.h>
#include <signal.h>
#define FNAME "/tmp/out"
static int daemonize(void)
{
FILE* fd;
pid_t pid = fork();
if (pid < 0)
{
perror("fork");
exit(1);
}
else if (pid > 0)
{
exit(0); // 父进程退出
}
else
{
// 脱离终端 012需要重定向 不能定向到终端
fd = open("dev/null", O_RDWR);
if (fd < 0)
{
perror("open");
return (-1);
}
// 守护进程打开文件,使其具有文件描述符012, 任何一个读写err的库例程都不会产生效果
dup2(fd, 0); // 文件输入输出重定向
dup2(fd, 1);
dup2(fd, 2);
if (fd < 2)
close(fd);
setsid(); // 子进程设置为守护进程
chdir("/"); // 工作路径设置到根目录 因为从父进程继承来的工作目录可能是个挂载的目录,
// 如果守护进程的工作目录也是一个挂载目录,那么该文件系统将不可以被卸载
// umask(0);
return 0;
}
}
static FILE* fp;
static int daemon_exit(int s)
{
// 此信号处理函数是可重入的,就是在处理一个信号的时候,另一个信号也来了(通过底层的&运算),
// 那么此时如果刚刚执行fclose,没有closelog的执行,响应了另一个信号,再次进入到该信号处理函数中,
// 解决方法:在进入信号处理函数的时候,将其他信号屏蔽,处理结束的时候,在解除屏蔽sigprocmask
fclose(fp);
closelog();
exit(1);
}
int main()
{
// 必须使用规定信号才可以杀死该进程
// signal(SIGINT,daemon_exit);
// signal(SIGQUIT,daemon_exit);
// signal(SIGTREM,daemon_exit);
struct sigaction sa;
sa.sa_handler = daemon_exit; // 第一个参数 第二个是三个参数的信号处理函数,暂时不考虑
sigemptyset(&sa.sa_mask); // 第三个参数 执行处理函数期间,被屏蔽的信号集
sigaddset(&sa.sa_mask, SIGQUIT);
sigaddset(&sa.sa_mask, SIGTERM);
sigaddset(&sa.sa_mask, SIGINT); // 响应SIGINT的时候,SIGINT的mask位是0,再将其加入到屏蔽集合中,只不过是再次的设置一次0
sa.sa_flags = 0; // 第四个参数,一般用不到,先设置为0
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL); // 三个信号驱动,这三个信号都可以实现阻塞进程
sigaction(SIGQUIT, &sa, NULL);
// 一般使用pid 守护进程类别
openlog("mydaemon", LOG_PID, LOG_DAEMON);
// 守护进程
if (daemonize())
{
syslog(LOG_ERR, "init failed");
}
else
{
syslog(LOG_INFO, "successed");
}
// 守护进程做的任务
fp = fopen(FNAME, "w+");
if (fp == NULL)
{
syslog(LOG_ERR, "write file failed");
exit(1);
}
syslog(LOG_INFO, "%s opened", FNAME);
for (int i = 0; ;i++)
{
fprintf(fp, "%d", i);
fflush(NULL);
syslog(LOG_INFO, "%d wrote", i);
sleep(1);
}
closelog(); // 执行不到,因为都是异常终止
fclose(fp);
return 0;
}
- setitimer();
更精确的时间控制时间
int getitimer(int which, struct itimerval *curr_value);
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
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 */
};
- 实时信号
解决标准信号的不足,响应有顺序,同时到来是先响应标准信号的