LINUX系统编程-- 5 信号
五 信号
并发分两种:信号(进程间通信)与线程
内容:
1、信号的概念
2、signal
3、信号的不可靠
4、可重入函数
5、 信号的响应过程
6、常用函数:kill、raise、alarm、pause、abort、system、sleep等
7、信号集
8、 信号屏蔽字、peding集的处理
9、sigsuspend()、sigaction()、setitimer
10、实时信号
1信号的概念和相关概念
并发概念。
信号是初步异步、线程是强烈异步
同步概念
异步概念
异步事件的处理:查询法,通知法
信号是软件中断。信号的响应依赖于中断。
1、 kill -l 查看全部的信号,是shell命令
1-31是标准信号。之后的是实时信号
2 core文件:一个程序的某一个现场
默认下,是不产生core文件的:
可知默认设置下,允许的core文件的大小是0。也就是不产生core文件。
我们可以用ulimit -c 10240 来设置core文件的最大为10M。
当执行一个程序出错时,就会有core文件的产生。core文件后面的数字是进程号。
3 signal函数
函数原型是:
void(*signal(int signum,void(*func)(int)))(int)
4ctrl+c是SIGINT的快捷方式(2号信号)
5 实例
例一:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
int main()
{
int i;
signal(SIGINT,SIG_IGN);
for (i = 0;i < N;i++){
write(1,"*",1);
sleep(1);
}
exit(0);
}
这个程序使得ctrl+c不再管用,因为忽略掉了这个信号。
例二:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#define N 20
void handler(int sig){
write(1,"!",1);
}
int main()
{
int i;
signal(SIGINT,handler);
for (i = 0;i < N;i++){
write(1,"*",1);
sleep(1);
}
exit(0);
}
注意点:
- signal函数最需要注意的就是,回调函数!当signal函数的第二个参数是函数地址指向信号处理函数的时候,这时的信号处理函数是一个回调函数。signal只是起到了对这个信号处理函数的注册作用!!当有指定的信号发生的时候,信号处理函数才会执行。(信号处理函数执行的一个前提就是,信号发生时,程序未结束!)
回调函数一般放在程序的开头,声明和定义放在一块。 - 信号会打断阻塞的系统调用!!!!以open为例,当open打开一个很慢的设备的时候,open是在阻塞的,这个时候会被信号打断,打断的结果是,返回-1,并设置error为EINTR!read、write函数也是同理,当读数据或者写数据的时候,如果是阻塞中,也是会被信号打断的(同样也是将errno设置为EINTR)!
- 通常来讲,我们将系统调用被信号打断称为假错,也就是说,这不是程序本身的问题,所以通常我们会在出错处理上加一个出错判断。
6 信号的不可靠
指的是信号的行为不可靠。
第一次调用还没结束,第二次调用就开始了,第二次调用用的环境是第一次调用还没调用完的环境,所以会出问题。
待补充。。
。。
。。
。。
。。
7 可重入函数
是解决信号的不可靠的一种方法!
可重入函数就是说,当第一次信号处理函数还没执行完时,若又来了一个信号,那么会先把第一次的信号处理函数执行完。(而忽略第二次的信号)。也就是说,信号处理函数第一次调用没结束,发生第二次调用 但不会出错
可重入函数都是安全的,就如“可重入”的意思一样,如果某个函数是非可重入函数,那么此函数不能用于信号处理函数中。通常这个信号会提供第二个版本(通常是后面有_r),
所有的系统调用都是可重入的!一部分库函数也是可重入的,比如说:memcpy
8 信号的响应过程
9 常用函数
1、 kill函数
kill()-需要注意的就是第一个参数,可以取正负或者0,各自有不同的含义。
- pid>0 给指定进程发送信号
- pid==0 组内广播,即给所有同组进程发送信号
- pid==-1 全局广播,给所有自己有权限发送信号的进程 发送信号,除init进程外,不包括init进程。一般就是init进程会发全局广播。
- pid<-1 发送给指定进程组。将信号发送给 进程组ID绝对值 == pid绝对值的 进程组中的每个进程
- sig == 0 不会发送任何信号,用于检测一个进程或者进程组 是否存在。
返回值: 0成功 -1失败
如果失败,查看errno 如下:
- EINVAL 表示目标信号无效
- EPERM 表示 进程没有向任何目标进程发送信号的权限,即表示 目标进程或进程组存在,但是你没有发送信号的权限!!
- ESRCH pid或进程组不存在。注意,现有的进程可能是僵死进程,即已经提交了终止请求,但还没有被等待(2)的进程。
2、raise()
给当前进程发送信号,自己给自己发信号
在多进程中,相当于:
kill(getpid(), sig);
在多线程中:相当于:
pthread_kill(pthread_self(), sig);
3、alarm()
设置一个发送信号的闹钟;
alarm()以秒为单位安排将一个SIGALRM信号发送给调用进程。 SIGALRM 也是终止信号。
alarm例子:
例一:使用单一计时器
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
alarm(5);
while(1);
exit(0);
}
mhr@ubuntu:~/Desktop/xitongbiancheng/parallel/signal/test$ gcc alarm.c
mhr@ubuntu:~/Desktop/xitongbiancheng/parallel/signal/test$ ./a.out
Alarm clock
mhr@ubuntu:~/Desktop/xitongbiancheng/parallel/signal/test$ 延迟5秒
例二:alarm的连续设置 最有一个alarm有效
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
alarm(5);
alarm(10);
alarm(1);
while(1);
exit(0);
}
mhr@ubuntu:~/Desktop/xitongbiancheng/parallel/signal/test$ gcc alarm.c
mhr@ubuntu:~/Desktop/xitongbiancheng/parallel/signal/test$ ./a.out
Alarm clock
只延迟1秒
mhr@ubuntu:~/Desktop/xitongbiancheng/parallel/signal/test$
例三:
在alarm这里需要注意的是,当要改变sigalarm的默认行为的时候,signal 一定要写在 alarm前面,如果alarm写在前面,如下:
alarm(5);
... 中间程序 经过了5秒
signal(SIGALRM,alarm_handler);
这种情况 中间程序 做了5秒以上的工作,那么5秒后 当此时信号来了,程序还没有执行到 signal() 也就看不到 给信号注册的新的行为:alarm_handler,那么就会沿用这个信号默认的行为,即终止程序。
所以正确写法是:
/ signal 一定要写在 alarm前面
signal(SIGALRM,alarm_handler);
alarm(5);
4、pause函数
等待一个信号,即这是人为做出来的一个阻塞的系统调用。(我们专门做出来的一个用来被打断的 阻塞的系统调用)。
pause()使调用进程(或线程)处于休眠状态,直到发出终止进程或调用信号捕捉函数的信号。
- 这个函数需要注意的就是他是使进程处于休眠状态,即不在占用CPU资源,而while(1),虽然效果与pause一样,但是while使CPU一直在运行!
5、补充
在有些环境下 sleep() 是由 alarm() + pause()封装的。有些是用nanosleep()封装的。
所以 不建议使用 sleep(),理由是在 sleep() 是由 alarm() + pause()封装的环境中,当你程序中同时使用 sleep() 和 alarm()的时候,必然有覆盖一个 alarm,即 alarm() 和 sleep()当中必然会有一个失效!!当前我们使用的环境用sleep不会有任何问题,因为他是用nanosleep封装的,但是为了保证程序的可移植性,还是尽量避免使用sleep。
6、alarm实例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
static int loop = 1;
static void alarm_handler(int s)
{
loop = 0;
}
int main()
{
int64_t count = 0;
// signal 一定要写在 alarm前面
signal(SIGALRM,alarm_handler);
alarm(5);
while(loop)
count++;
printf("%ld\n",count);
exit(0);
}
mhr@ubuntu:~/Desktop/xitongbiancheng/parallel/signal/test$ gcc alarm.c
mhr@ubuntu:~/Desktop/xitongbiancheng/parallel/signal/test$ time ./a.out
2629709698
real 0m5.002s
user 0m4.936s
sys 0m0.000s
mhr@ubuntu:~/Desktop/xitongbiancheng/parallel/signal/test$
补充:
- alarm比time的精度高。
- volatile 关键字:去到这个变量真正的存储空间取数值,而不是根据局部结构判断取值
9.1 漏桶实例,流量控制
实现一个cat,这个mycat与cat不同的点是每秒cat10个字符 输出到标准输出。
读取一个视频、一个音频等都是要进行流量控制的!数据不是一股脑全读完的。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#define BUFSIZE 10
static volatile int loop = 0;
static void alrm_handler(int s)
{
alarm(1);//重新定时
loop = 1;
}
int main(int argc,char *argv[])
{
int sfd,dfd=1;
char buf[BUFSIZE];
int len,ret,pos;
if(argc < 2)
{
fprintf(stderr,"Usage:%s <src_file> <dest_file>\n",argv[0]);
exit(1);
}
signal(SIGALRM,alrm_handler);
alarm(1);
do
{
sfd = open(argv[1],O_RDONLY);
if(sfd < 0)
{
if(errno != EINTR)//防止是 信号打断阻塞的系统调用
{
perror("open()");
exit(1);
}
}
}while(sfd < 0);
while(1)
{
//休眠挂起 直到收到信号,重新开始执行while(!loop)循环,实现一秒一输出
// 这里也可以 不用pause(),while()后 执行空,但是这样 CPU 占用率会很高,一秒钟会在这里执行循环上亿次,所以用pause()替换,直接休眠等待信号来唤醒
/*
while(!loop)
;
*/
while(!loop)
pause();
loop = 0;
while((len = read(sfd,buf,BUFSIZE)) < 0)
{
if(errno == EINTR)//防止是 信号打断阻塞的系统调用
continue;
perror("read()");
break;
}
if(len == 0)
break;
//确保写进去 len 个字节
pos = 0;
while(len > 0)
{
ret = write(dfd,buf+pos,len);
if(ret < 0)
{
if(errno == EINTR) //防止是 信号打断阻塞的系统调用
continue;
perror("write()");
exit(1);
}
pos += ret;
len -= ret;
}
}
close(sfd);
}
缺点:如果读取的是打印机类的设备,并且当时打印机上面没有数据,那么程序就会一直循环于 read处.
while((len = read(sfd,buf,BUFSIZE)) < 0)
{
if(errno == EINTR)//防止是 信号打断阻塞的系统调用
continue;
问题是这几句,一直循环于:
读阻塞 ,打断阻塞 判断是假错误(信号打断阻塞的系统调用) 返回重新读 。。。。
读阻塞 ,打断阻塞 判断是假错误(信号打断阻塞的系统调用) 返回重新读 。。。。
读阻塞 ,打断阻塞 判断是假错误(信号打断阻塞的系统调用) 返回重新读 。。。。
所以漏桶的缺陷就是 如果没有数据的时候就会一直循环等待,直到有数据,如果忽然来的数据量很大,也不能快速的去读数据,只能慢慢的一秒10个字节的去读n次
9.2 令牌桶,流量控制
令牌桶的优势,就是当没有数据可读的时候,会积攒自己的权限,意思是 如果之前30秒一直没有数据,读空了30秒,那么就存下30个权限,等到有数据的时候,快速使用前面30个权限,快速连续读30次。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#define CPS 10
#define BUFSIZE CPS
#define BURST 100
static volatile int token = 0;
static void alrm_handler(int s)
{
alarm(1);
token++;
if(token > BURST)
token = BURST;
}
int main(int argc,char *argv[])
{
int sfd,dfd=1;
char buf[BUFSIZE];
int len,ret,pos;
if(argc < 2)
{
fprintf(stderr,"Usage:%s <src_file> <dest_file>\n",argv[0]);
exit(1);
}
signal(SIGALRM,alrm_handler);
alarm(1);
do
{
sfd = open(argv[1],O_RDONLY);
if(sfd < 0)
{
if(errno != EINTR)//signal
{
perror("open()");
exit(1);
}
}
}while(sfd < 0);
while(1)
{
while(token <= 0)
pause();
token--;
while((len = read(sfd,buf,BUFSIZE)) < 0)
{ if(errno == EINTR)//signal
continue;
perror("read()");
break;
}
if(len == 0)
break;
//确保写进去 len 个字节
pos = 0;
while(len > 0)
{
ret = write(dfd,buf+pos,len);
if(ret < 0)
{
if(errno == EINTR) //signal
continue;
perror("write()");
exit(1);
}
pos += ret;
len -= ret;
}
}
close(sfd);
}
9.3 令牌桶封装成库实例
将上述的令牌桶流量控制的一些细节做进一步优化,并将上述的机制封装成一个库,供后续直接使用。
//mytbf.h
#ifndef MYTBF_H__
#define MYTBF_H__
#define MYTBF_MAX 1024
typedef void mytbf_t;
mytbf_t *mytbf_init(int cps,int burst);
//获取token
int mytbf_fetchtoken(mytbf_t *,int);
//归还token
int mytbf_returntoken(mytbf_t *,int);
int mytbf_destroy(mytbf_t *);
#endif
//mystf.c
#include <asm-generic/errno-base.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include "mytbf.h"
struct mytbf_st{
int csp;
int burst;
int token;
int pos;//任务列表的下标
};
static struct mytbf_st *job[MYTBF_MAX];
static volatile int inited = 0;
static void (*alarm_status)(int);
static int get_free_pos(){
for (int i = 0;i < MYTBF_MAX;i++){
if (job[i] == NULL)
return i;
}
return -1;
}
//信号处理函数
static void handler(int sig){
alarm(1);
for (int i = 0;i < MYTBF_MAX;i++){
if (job[i] != NULL){
job[i]->token += job[i]->csp;
if (job[i]->token > job[i]->burst){
job[i]->token = job[i]->burst;
}
}
}
}
//装载信号处理模块
static void mod_load(){
alarm_status = signal(SIGALRM,handler);//保存alarm信号处理函数原来的状态
alarm(1);
}
//卸载信号处理模块 当发生异常退出时 可以将占用的资源释放 将alarm信号取消
static void mod_unload(){
signal(SIGALRM,alarm_status);
alarm(0);
for (int i = 0;i < MYTBF_MAX;i++){
free(job[i]);
}
}
mytbf_t *mytbf_init(int cps,int burst){
struct mytbf_st *tbf;
if (!inited){
mod_load();
}
//将新的tbf装载到任务组中
int pos;
pos = get_free_pos();
if (pos == -1){
return NULL;
}
tbf = malloc(sizeof(*tbf));
if (tbf == NULL)
return NULL;
tbf->token = 0;
tbf->csp = cps;
tbf->burst = burst;
tbf->pos = pos;
job[pos] = tbf;
return tbf;
}
//获取token ptr是一个 void * size是用户想要获取的token数
int mytbf_fetchtoken(mytbf_t *ptr,int size){
struct mytbf_st *tbf = ptr;
if (size <= 0){
return -EINVAL;
}
//有token继续
while (tbf->token <= 0)
pause();
int n =tbf->token<size?tbf->token:size;
tbf->token -= n;
//用户获取了 n 个token
return n;
}
//归还token ptr是一个 void *
int mytbf_returntoken(mytbf_t *ptr,int size){
struct mytbf_st *tbf = ptr;
if (size <= 0){
return -EINVAL;
}
tbf->token += size;
if (tbf->token > tbf->burst)
tbf->token = tbf->burst;
return size;
}
int mytbf_destroy(mytbf_t *ptr){
struct mytbf_st *tbf = ptr;
job[tbf->pos] = NULL;
free(tbf);
return 0;
}
//main.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <math.h>
#include "mytbf.h"
static const int SIZE = 1024;
static const int CPS = 3;
static const int BURST = 100;//最大令牌数
static volatile int token = 0;//持有令牌数
int main(int argc,char** argv)
{
if (argc < 2){
fprintf(stdout,"Usage...");
exit(1);
}
mytbf_t *tbf;
tbf = mytbf_init(CPS,BURST);
if (tbf == NULL){
fprintf(stderr,"tbf init error");
exit(1);
}
//打开文件
int sfd,dfd = 0;
do{
sfd = open(argv[1],O_RDONLY);
if (sfd < 0){
if (errno == EINTR)
continue;
fprintf(stderr,"%s\n",strerror(errno));
exit(1);
}
}while(sfd < 0);
char buf[SIZE];
while(1){
int len,ret,pos = 0;
int size = mytbf_fetchtoken(tbf,SIZE);
int i = 0;
while(i < 2){
sleep(1);
i++;
}
if (size < 0){
fprintf(stderr,"mytbf_fetchtoken()%s\n",strerror(-size));
exit(1);
}
len = read(sfd,buf,size);
while (len < 0){
if (errno == EINTR)
continue;
strerror(errno);
break;
}
//读取结束
if (len == 0){
break;
}
//要是读到结尾没用完token
if (size - len > 0){
mytbf_returntoken(tbf,size-len);
}
//以防写入不足
while(len > 0){
ret = write(dfd,buf+pos,len);
while (ret < 0){
if (errno == EINTR){
continue;
}
printf("%s\n",strerror(errno));
exit(1);
}
pos += ret;
len -= ret;
}
}
close(sfd);
mytbf_destroy(tbf);
exit(0);
}
9.4 setitimer函数
上面的计时等核心都是通过alarm函数实现的。实际开发过程中,
优先使用setitimer来计时!更灵活 而且误差不累积。(计时,优先使用setitimer)
(程序中优先使用setitimer,而不是alarm!!!)
参数:
1、which
-
TIMER_REAL 以系统真实的时间来计算,时间到了 它送出SIGALRM信号。可以代替 alarm
-
ITIMER_VIRTUAL 以该进程在用户态下花费的时间来计算,它送出SIGVTALRM信号
-
ITIMER_PROF 以该进程在用户态下和内核态下所费的时间来计算。它送出SIGPROF信号。
2、相关结构体
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 */
};
下面使用一个alarm或者setitimer计时器来实现多任务计时器的功能。
//流量控制之漏桶实验,用setitimer()实现
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/time.h>
#define BUFSIZE 10
static volatile int loop = 0;
static void alrm_handler(int s)
{
//alarm(1);
loop = 1;
}
int main(int argc,char *argv[])
{
int sfd,dfd=1;
char buf[BUFSIZE];
int len,ret,pos;
struct itimerval itv;
if(argc < 2)
{
fprintf(stderr,"Usage:%s <src_file> <dest_file>\n",argv[0]);
exit(1);
}
signal(SIGALRM,alrm_handler);
//alarm(1);
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)
{
perror("setitimer()");
exit(1);
}
do
{
sfd = open(argv[1],O_RDONLY);
if(sfd < 0)
{
if(errno != EINTR)//signal
{
perror("open()");
exit(1);
}
}
}while(sfd < 0);
while(1)
{
while(!loop)
pause();
loop = 0;
while((len = read(sfd,buf,BUFSIZE)) < 0)
{ if(errno == EINTR)//signal
continue;
perror("read()");
break;
}
if(len == 0)
break;
//确保写进去 len 个字节
pos = 0;
while(len > 0)
{
ret = write(dfd,buf+pos,len);
if(ret < 0)
{
if(errno == EINTR) //signal
continue;
perror("write()");
exit(1);
}
pos += ret;
len -= ret;
}
}
close(sfd);
}
9.5 system
如果想在有信号的程序当中正常使用 system(),需要阻塞SIGCHLD信号,忽略SIGINT信号和SIGQUIT信号
10 信号集
sigemptyset()
sigfillset()
sigaddset()
sigdelset()
sifismember()
1、信号屏蔽字/pending集的处理
sigprocmask():我们无法控制信号何时到来,但可以选择如何响应它
例一:
每秒向标准输出打印一个* 打印五秒换行。程序不受 CTRL+C 即 SIGINT信号影响,收到 SIGINT信号 响应信号 打印!。
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void sig_handler(int s)
{
write(1,"!",1);
}
int main()
{
int i,j;
signal(SIGINT,sig_handler);
for(j=0;j < 1000;j++)
{
for(i=0 ; i<5 ; i++)
{
write(1,"*",1);
sleep(1);
}
write(1,"\n",1);
}
exit(0);
}
例二:
修改: sigprocmask(SIG_BLOCK…)+ sigprocmask(SIG_UNBLOCK…)只在两行*之间输出 !, 即 选择响应 SIGINT 信号的时间。
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void sig_handler(int s)
{
write(1,"!",1);
}
int main()
{
int i,j;
sigset_t set;//创建信号集
signal(SIGINT,sig_handler);
sigemptyset(&set); //清空信号集
sigaddset(&set,SIGINT); // 添加 SIGINT信号 到信号集
for(j=0;j < 1000;j++)
{
sigprocmask(SIG_BLOCK,&set,NULL); // 对 set信号集中的信号 进行阻塞,即将mask信号屏蔽位置0
for(i=0 ; i<5 ; i++)
{
write(1,"*",1);
sleep(1);
}
write(1,"\n",1);
sigprocmask(SIG_UNBLOCK,&set,NULL); // 对 set信号集中的信号 解除阻塞,即将mask信号屏蔽位置1
}
exit(0);
}
mhr@ubuntu:~/Desktop/xitongbiancheng/parallel/signal/test$
mhr@ubuntu:~/Desktop/xitongbiancheng/parallel/signal/test$ gcc sigprocmask.c
mhr@ubuntu:~/Desktop/xitongbiancheng/parallel/signal/test$ ./a.out
***^C*^C*^C^C^C^C^C
!*^C^C^C^C^C^C*^C^C^C^C^C^C*^C**
!*****
*^C*^C^C^C^C^C*^C^C*^C*
!*****
*****
*****
*****
*****
****^C*^C
!***^C^C**
!*****^\Quit (core dumped)
mhr@ubuntu:~/Desktop/xitongbiancheng/parallel/signal/test$
发现实验中 SIGINT 信号不会打断阻塞的系统调用了。只有 SIG_BLOCK 之后才会,原因同上,因为已经屏蔽了该信号。
例三:
上面的例二有个问题,还是宏观思想的问题,当进入上面这个模块之后,若是SIGINT本身是非阻塞的,那么没有问题,因为这个模块先将信号阻塞然后接触阻塞。如果这个信号进入这个模块前是阻塞的,那么将会出问题,因为从这个模块出去后对SIGINT信号将是非阻塞的!
所以进入每个模块前要先记录资源的原始状态,从模块结束后恢复原状态!
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void sig_handler(int s)
{
write(1,"!",1);
}
int main()
{
int i,j;
sigset_t set,saveset;//创建信号集
signal(SIGINT,sig_handler);
sigemptyset(&set); //清空信号集
sigaddset(&set,SIGINT); // 添加 SIGINT信号 到信号集
//在当前模块修改之前 保存信号集状态,以便 当前模块执行完成后恢复 信号集状态
sigprocmask(SIG_UNBLOCK,&set,&saveset);
for(j=0;j < 1000;j++)
{
sigprocmask(SIG_BLOCK,&set,NULL); // 对 set信号集中的信号 进行阻塞,即将mask信号屏蔽位置0
for(i=0 ; i<5 ; i++)
{
write(1,"*",1);
sleep(1);
}
write(1,"\n",1);
sigprocmask(SIG_UNBLOCK,&set,NULL); // 对 set信号集中的信号 解除阻塞,即将mask信号屏蔽位置1
}
// 恢复 之前信号集状态
sigprocmask(SIG_SETMASK,&saveset,NULL);
exit(0);
}
11 sigsuspend() 设置信号集状态与挂起 的原子操作
函数功能:
(以下三步为原子操作)
第一步:设置当前进程信号集状态为 目标信号集状态mask,并保存进程旧的信号集状态为 oldmask
第二步:马上进入等待信号阶段
第三步:收到信号唤醒后 设置当前进程信号集状态为 旧的信号集状态oldmask。
例子一:
用pause()实现,打印一行*后 ,停止等待信号 驱动程序继续跑,
pause()休眠 等待信号唤醒,CTRL+C 唤醒程序执行打印下一行。
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void sig_handler(int s)
{
write(1,"!",1);
}
int main()
{
int i,j;
sigset_t set,saveset;
signal(SIGINT,sig_handler);
sigemptyset(&set);
sigaddset(&set,SIGINT);
sigprocmask(SIG_UNBLOCK,&set,&saveset);
for(j=0;j < 1000;j++)
{
sigprocmask(SIG_BLOCK,&set,NULL);
for(i=0 ; i<5 ; i++)
{
write(1,"*",1);
sleep(1);
}
write(1,"\n",1);
sigprocmask(SIG_UNBLOCK,&set,NULL);
pause();
}
sigprocmask(SIG_SETMASK,&saveset,NULL);
exit(0);
}
mhr@ubuntu:~/Desktop/xitongbiancheng/test$ ./a.out
*****
^C!*****
^C!**^C***
!
上面需要注意的:
- crtl+\ :是终止进程。
- crtl+c :也是终止进程。
- crtl+z:是将进程挂起,并不是结束进程,可以再次输入fg,将挂起的进程再次运行!
- pause,使调用进程(线程)进入休眠状态(就是挂起);直到接收到信号且信号处理函数成功返回 pause函数才会返回
上面的程序存在问题,如果在当前行打印途中 发送 SIGINT信号,不会驱动程序打印下一行数据,这是因为在sigprocmask(SIG_UNBLOCK,&set,NULL); pause()这两个函数中间,当解除信号阻塞之后,还没来得及执行pause(),就把 SIGINT信号响应掉了,所以信号就作用不到 pause(),即不会唤醒进程,程序响应 SIGINT信号之后,打印!再执行到 pause()。 我们知道,程序在运行当中 可以理解为 时时刻刻都在被中断,时间片一直在反复的耗尽,也就是反复 进入内核态等待调度,再继续执行程序,所以此时 刚刚好从内核态切换回用户态,发现SIGINT信号,于是马上响应信号动作。我们的初衷是 执行完解除阻塞后,马上pause()挂起,最后SIGINT信号作用到 pause()上,打印下一行 ,所以 这个问题的原因就是 sigprocmask(SIG_UNBLOCK,&set,NULL) 与 pause() 这两个操作组合 是非原子操作,是不原子的。
例二:
修改:用 sigsuspend()实现
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void sig_handler(int s)
{
write(1,"!",1);
}
int main()
{
int i,j;
sigset_t set,saveset,oset;
signal(SIGINT,sig_handler);
sigemptyset(&set);
sigaddset(&set,SIGINT);
sigprocmask(SIG_UNBLOCK,&set,&saveset);//保存进入该模块前的状态
sigprocmask(SIG_BLOCK,&set,&oset);
for(j=0;j < 1000;j++)
{
for(i=0 ; i<5 ; i++)
{
write(1,"*",1);
sleep(1);
}
write(1,"\n",1);
sigsuspend(&oset);
}
sigprocmask(SIG_SETMASK,&saveset,NULL);//恢复进入该模块前的状态
exit(0);
}
mhr@ubuntu:~/Desktop/xitongbiancheng/parallel/signal/test$
mhr@ubuntu:~/Desktop/xitongbiancheng/parallel/signal/test$
mhr@ubuntu:~/Desktop/xitongbiancheng/parallel/signal/test$ gcc sigsuspend.c
mhr@ubuntu:~/Desktop/xitongbiancheng/parallel/signal/test$ ./a.out
*****
^C!*****
^C!**^C*^C^C**
!*****
^C!**^\Quit (core dumped)
mhr@ubuntu:~/Desktop/xitongbiancheng/parallel/signal/test$
12 sigaction(),区别于 signal()
-
signal()的缺陷1 :当多个信号共用一个信号处理函数的时候,可能会发生重入,导致段错误。所以我们希望,在响应一个信号的时候,将其他信号阻塞。 sigaction()可以弥补
-
signal()的缺陷2 :不能指定接收信号来源, sigaction()可以弥补.
signal()的缺陷1 试验:当多个信号共用一个信号处理函数的时候,可能会发生重入。以前面的守护进程为例:
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <syslog.h>
#define FILENAME "/tmp/out"
static int craetdeamon(void)
{
pid_t pid;
int fd;
pid = fork();
if(pid < 0)
{
perror("fork()");
return -1;
}
if(pid > 0)
{
printf("%d\n",getpid());
exit(0);
}
fd = open("/dev/null",O_RDWR);
if(fd < 0)
{
perror("open()");
return -1;
}
dup2(fd,0);
dup2(fd,1);
dup2(fd,2);
if(fd > 2)
{
close(fd);
}
setsid();return
chdir("/");
return 0;
}
static void exitdeamon(int s)
{
fclose(fb);
closelog();
}
int main(int argc,char* argv[])
{
FILE* fp;
int i;
signal(SIGINT,exitdeamon);
signal(SIGQUIT,exitdeamon);
signal(SIGTERM,exitdeamon);
openlog("craetdeamon",LOG_PID,LOG_DAEMON);
if(craetdeamon())
{
syslog(LOG_ERR,"craetdeamon failed!");
exit(1);
}else{
syslog(LOG_INFO,"craetdeamon successded!");
}
fp = fopen(FILENAME,"w");
if(fp == NULL)
{
syslog(LOG_ERR,"fopen %s failed!",FILENAME);
exit(1);
}
syslog(LOG_INFO,"fopen %s successede!",FILENAME);
for(i = 0; ;i++)
{
fprintf(fp,"%d\n",i);
fflush(fp);
syslog(LOG_DEBUG,"%d is printed!",i);
sleep(1);
}
exit(0);
}
程序的本意是,守护进程 接收到 SIGINT,SIGQUIT,SIGTERM 三个信号任意信号 的时候终止守护进程,多个信号共用一个信号处理函数。但是有一个问题:
-接收到这三个信号中的任意信号,都会执行处理函数中内容,假如有这样的情形:
-
1 程序接受到了 SIGINT 信号,程序收到中断后扎内核,被调度后,从内核态切换到用户态,发现 收到了 SIGINT信号,于是开始执行处理函数,但是只执行了 fclose(fb); 这一句,就再次被打断,进程内核态,等待调度。
-
2 在等待调度的时候,程序又收到了 SIGQUIT 信号,等到程序被调度,从内核态切换到用户态时候,发现收到了 SIGQUIT信号,于是再次 执行 信号处理函数,再次执行到 fclose(fb);。于是fb 被两次 fclose(),会发生段错误。发生了重入!!
用 sigaction() 实现多信号共用一个处理函数。
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <syslog.h>
#include <signal.h>
#define FILENAME "/tmp/out"
static FILE* fp;
static int craetdeamon(void)
{
pid_t pid;
int fd;
pid = fork();
if(pid < 0)
{
perror("fork()");
return -1;
}
if(pid > 0)
{
printf("%d\n",getpid());
exit(0);
}
fd = open("/dev/null",O_RDWR);
if(fd < 0)
{
perror("open()");
return -1;
}
dup2(fd,0);
dup2(fd,1);
dup2(fd,2);
if(fd > 2)
{
close(fd);
}
setsid();return
chdir("/");
return 0;
}
static void exitdeamon(int s)
{
fclose(fp);
closelog();
}
int main(int argc,char* argv[])
{
int i;
struct sigaction sa;
sa.sa_handler = exitdeamon;
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask,SIGQUIT);
sigaddset(&sa.sa_mask,SIGTERM);
sigaddset(&sa.sa_mask,SIGINT);
sigaction(SIGINT,&sa,NULL);
sigaction(SIGTERM,&sa,NULL);
sigaction(SIGQUIT,&sa,NULL);
openlog("craetdeamon",LOG_PID,LOG_DAEMON);
if(craetdeamon())
{
syslog(LOG_ERR,"craetdeamon failed!");
exit(1);
}else{
syslog(LOG_INFO,"craetdeamon successded!");
}
fp = fopen(FILENAME,"w");
if(fp == NULL)
{
syslog(LOG_ERR,"fopen %s failed!",FILENAME);
exit(1);
}
syslog(LOG_INFO,"fopen %s successede!",FILENAME);
for(i = 0; ;i++)
{
fprintf(fp,"%d\n",i);
fflush(fp);
syslog(LOG_DEBUG,"%d is printed!",i);
sleep(1);
}
exit(0);
}
signal()缺陷2:不能识别信号来源
回顾之前的流量控制(流量控制的库):
在终端1 运行:
mhr@ubuntu:~/Desktop/xitongbiancheng/parallel/signal/test$ ./a.out /etc/services
在终端2 运行
mhr@ubuntu:~/Desktop/xitongbiancheng/parallel/signal/test$ while true ; do kill -ALRM 10405 ;done
会发现 程序会瞬间执行完成。即从其他终端以用户的角度向指定进程不停的发送 ALRM 信号,导致流控失效。问题在于 signal() 并不会检查区分信号的来源,属性信息,只要来了该信号,就会响应动作。但是实际上程序利用 alarm来发送信号,实际上 alarm信号是从 kernel 发送过来的,而刚刚的实验信号,是从user 发送的。所以需要指定只响应从某处来的信号,即指定信号来源,指定从 kernel来的信号,signal() 无法完成该动作,sigaction()可以。
利用sigactino修改后的版本:
//信号处理函数
static void handler(int sig,siginfo_t *infop,void *unused){
struct itimerval itv;
if (infop->si_code != SI_KERNEL){
return ;
}
itv.it_interval.tv_sec = 1;
itv.it_interval.tv_usec = 0;
itv.it_value.tv_sec = 1;
itv.it_value.tv_usec = 0;
if(setitimer(ITIMER_REAL,&itv,NULL) < 0){
perror("setitimer()");
exit(1);
}
for (int i = 0;i < MYTBF_MAX;i++){
if (job[i] != NULL){
job[i]->token += job[i]->csp;
if (job[i]->token > job[i]->burst){
job[i]->token = job[i]->burst;
}
}
}
}
//装载信号处理模块
static void mod_load(){
struct sigaction sa;
sa.sa_sigaction = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO;
if (sigaction(SIGALRM,&sa,&old_sa) < 0){
perror("sigaction()");
exit(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 = 0;
if(setitimer(ITIMER_REAL,&itv,&old_itv) < 0){
perror("setitimer()");
exit(1);
}
}
//卸载信号处理模块 当发生异常退出时 可以将占用的资源释放 将alarm信号取消
static void mod_unload(){
//signal(SIGALRM,alarm_status);
sigaction(SIGALRM,&old_sa,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;
if(setitimer(ITIMER_REAL,&itv,&old_itv) < 0){
perror("setitimer()");
exit(1);
}
for (int i = 0;i < MYTBF_MAX;i++){
free(job[i]);
}
}
13 实时信号
如果一个进程既受到标准信号,又受到实时信号,那么先响应标准信号后相应实时信号。
标准信号特点:
- 1 标准信号会丢失
- 2 标准信号 没有一个严格的响应顺序要求,未定义行为。
标准信号中有两个未定义行为的信号,留给我们使用:SIGUSR1,SIGUSR2
实时信号用于解决标准信号的不足:
- 1 实时信号不会丢失
- 2 实时信号响应有顺序要求
实时信号的例子:
选择40) SIGRTMIN+6 信号举例
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#define MYRTSIGNAL (SIGRTMIN+6)
void sig_handler(int s)
{
write(1,"!",1);
}
int main()
{
int i,j;
sigset_t set,saveset,oset;
signal(MYRTSIGNAL,sig_handler);
sigemptyset(&set);
sigaddset(&set,MYRTSIGNAL);
sigprocmask(SIG_UNBLOCK,&set,&saveset);
sigprocmask(SIG_BLOCK,&set,&oset);
for(j=0;j < 1000;j++)
{
for(i=0 ; i<5 ; i++)
{
write(1,"*",1);
sleep(1);
}
write(1,"\n",1);
sigsuspend(&oset);
}
sigprocmask(SIG_SETMASK,&saveset,NULL);
exit(0);
}
发现实时信号不会丢失,连续发送三次信号,如果是标准信号,只会响应一次,而实时信号响应了三次。