ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

LINUX系统编程-- 5 信号

2021-09-16 09:02:26  阅读:251  来源: 互联网

标签:-- signal 编程 int exit 信号 LINUX include alarm


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);
}

在这里插入图片描述

发现实时信号不会丢失,连续发送三次信号,如果是标准信号,只会响应一次,而实时信号响应了三次。

标签:--,signal,编程,int,exit,信号,LINUX,include,alarm
来源: https://blog.csdn.net/luseysd/article/details/120321481

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有