并发_信号(三)

5.setitimer()函数

现在的系统中很多程序不再使用alarm调用,而是使用setitimer调用来设置定时器,用getitimer来得到定时器的状态。
误差不累计;

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

参数:
which
表示类型,可选的值有:
ITIMER_REAL:按实际时间计时,计时到达将给进程发送SIGALRM信号。
ITIMER_VIRTUAL:仅当进程执行时才进行计时。计时到达将发送SIGVTALRM信号给进程。
ITIMER_PROF:当进程执行时和系统为该进程执行动作时都计时。与ITIMER_VIR-TUAL是一对,该定时器经常用来统计进程在用户态和内核态花费的时间。计时到达将发送SIGPROF信号给进程。
new_value和old_value均为itimerval结构体;
new_value:
用来对计时器进行设置,it_interval为计时间隔,it_value为第一次延时时长,后面时间间隔就为it_interval。
old_value
通常使用不上。设置为NULL,它是用来存储上一次setitimer调用时设置的new_value值。
返回值:
成功返回0,失败返回-1。

struct itimerval {
    struct timeval it_interval; /* next value */
    struct timeval it_value;    /* current value */
};

struct timeval {
    time_t      tv_sec;         /* seconds */
    suseconds_t tv_usec;        /* microseconds */
};

it_interval
表示两次定时任务之间的时间间隔
it_value
表示设置定时器后间隔多久开始执行定时任务;

工作机制:
先对it_value倒计时,当it_value为零时触发信号。然后重置为it_interval(原子操作)。然后继续对it_value倒计时,一直这样循环下去。
基于此机制。setitimer既能够用来延时运行,也可定时运行。

itimeval由两个timeval结构体组成,timeval包括tv_sec和tv_usec两部分。其中tv_se为秒,tv_usec为微秒(即1/1000000秒)

假如it_value为0是不会触发信号的,所以要能触发信号,it_value得大于0;假设it_interval为零,仅仅会延时。不会定时(也就是说仅仅会触发一次信号)。

#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/time.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(stderr, "usage: %s <src_file>", argv[0]);
		exit(1);
    }
    
    int sfd, dfd = 1;
    int ret;    
    int wet; 
	int pos;
    char buf[BUFFSIZE];
	struct itimerval itv;

 
	do
	{
		sfd = open(argv[1], O_RDONLY);
		if(sfd < 0)
		{
			if(errno != EINTR)
			{
				perror("open src_file");
				exit(1); 
			}
		}
	}while(sfd < 0);
	
	signal(SIGALRM, alrm_handler);
	//alarm(1);
	
	itv.it_interval.tv_sec = 1;
	itv.it_interval.tv_usec = 0;
	itv.it_value.tv_sec = 5;
	itv.it_value.tv_usec = 0;
	
	if(setitimer(ITIMER_REAL, &itv, NULL) < 0)
	{
		perror("setitimer()");
		exit(1);
	}
   
    while(1)
    {
		while(!loop)
			pause; //此处使用pause能够让当前进程休眠,不用一直监测
			//; 会让进程一直监测,占用cpu资源
		loop= 0;
		
        while((ret = read(sfd, buf, BUFFSIZE)) < 0)
		{
			if(errno == EINTR)
				continue;
            perror("read src_file");
            break;
		}
      
        if(ret == 0)
        {
            break;    
        }
        
        //wet = write(dfd, buf, BUFFSIZE);   
		//检测如果没有一次写完,就继续写,直至把这次读到的数据全部写完
		pos = 0;
		while(ret > 0)
		{
			wet = write(dfd, buf+pos, ret);
			if(wet < 0)
			{
				if(errno == EINTR)
					continue;
				perror("write des_file");
				exit(1);
			}
			pos += wet;
			ret -= wet;
		} 	
    }
     
    close(sfd);
	
	printf("\n");
	
    exit(0);
}

6.abort()函数

给当前进程发送SIGABRT信号,结束当前进程并产生core dump文件。

7.system()函数

可以简单的理解为exec + wait + fork的封装;

如果在有信号相关的程序当中,想正常使用system函数,就要:
1.block住SIGCHLD信号
2.忽略掉SIGINT和SIGQUIT

8.sleep()函数

可以用nanosleep()、usleep()和select()代替实现休眠;

9.信号集

信号集类型:sigset_t
sigemptyset();
sigfillset();
sigaddset();
sigdelset();
sigismember();

10.信号屏蔽字/pending集的处理

sigpromask(); //给定一种接口可以控制pending位图中的mask
不能决定信号什么时候到来,但是可以通过此函数决定信号什么时候被响应;

#include <signal.h>

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

参数:
how:
用于指定信号修改的方式,可能选择有三种

SIG_BLOCK //加入信号到进程屏蔽。
SIG_UNBLOCK //从进程屏蔽里将信号删除。
SIG_SETMASK //将set的值设定为新的进程屏蔽。

set:
为指向信号集的指针,在此专指新设的信号集,如果仅想读取现在的屏蔽值,可将其置为NULL。
oldset:
也是指向信号集的指针,在此存放操作之前的信号集。

返回:
成功执行时,返回0。失败返回-1,errno被设为EINVAL。

sigprocmask就是用来在进程执行过程中,对某段程序
不想让其它的信号量影响其执行而设计的,一般在程序段开始处调用一次,屏蔽需要屏蔽的信号量,
在程序段结束的地方调用一次,恢复被屏蔽的信号量。

#include<stdio.h>
#include<stdlib.h>
#include<signal.h>

void func(int i)
{
	write(1, "!", 1);
}

void hook(void)
{
	write(1, "\n", 1);
}

int main()
{
	int i,j; 
	sigset_t set, saveset;
	
	signal(SIGINT, func);
	sigemptyset(&set);//集合初始化为空
	sigaddset(&set, SIGINT);//加入SIGINIT信号
	
	sigprocmask(SIG_UNBLOCK, &set, &saveset);//将之前信号状态保存到saveset中
	
	for(j = 0; j < 100; 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);
	}
	
	sigprocmask(SIG_SETMASK, &saveset, NULL);
	
	atexit(hook);
	
	exit(0);
}

在这里插入图片描述
答应期间,SIGINT被屏蔽,直到换行后,才恢复执行信号的动作。
多个SIGINT在一行
中只打印一个*,因为标准信号的丢失问题。

其中,sigprocmask(SIG_UNBLOCK, &set, &saveset);和sigprocmask(SIG_SETMASK, &saveset, NULL)保证了经过此模块,整体的信号状态不会受影响。

int i,j; 
	sigset_t set, oset;
	
	signal(SIGINT, func);
	sigemptyset(&set);//集合初始化为空
	sigaddset(&set, SIGINT);//加入SIGINIT信号
	
	for(j = 0; j < 100; j++)
	{
		sigprocmask(SIG_BLOCK, &set, &oset);
		for(i = 0; i < 5; i++)
		{
			write(1, "*", 1);
			sleep(1);
		}
		write(1, "\n", 1);
		sigprocmask(SIG_SETMASK, &oset, NULL);
	}
	
	atexit(hook);
	
	exit(0); 

11.sigsuspend()函数

#include <signal.h>
int sigsuspend(const sigset_t *mask);

参数:
@mask 希望屏蔽的信号

返回值:
sigsuspend返回后将恢复调用之前的的信号掩码。信号处理函数完成后,进程将继续执行。该系统调用始终返回-1,并将errno设置为EINTR.

备注:
进程执行到sigsuspend时,sigsuspend并不会立刻返回,进程处于TASK_INTERRUPTIBLE状态并立刻放弃CPU,
等待UNBLOCK(mask之外的)信号的唤醒。进程在接收到UNBLOCK(mask之外)信号后,调用处理函数,然后还原信号集,
sigsuspend返回,进程恢复执行。

使用场景:
sigsuspend() 函数可以更改进程的信号屏蔽字可以阻塞所选择的信号,或解除对它们的阻塞。
使用这种技术可以保护不希望由信号中断的代码临界区。
如果希望对一个信号解除阻塞,然后pause等待以前被阻塞的信号发生,那么必须使用 sigsuspend() 函数
pause() 函数无法达成上述目的

信号驱动程序

#include<stdio.h>
#include<stdlib.h>
#include<signal.h>

/*
1.打印一行*期间,进程不能被信号打断
2.打印一行换行后,接收到信号在继续打印下一行
3.打印一行*期间,到来的多个标准信号会有丢失,后续只响应一个标准信号
*/
void func(int i)
{
	write(1, "!", 1);
}

void hook(void)
{
	write(1, "\n", 1);
}

int main()
{
	int i,j; 
	sigset_t set, oset, saveset;
	
	signal(SIGINT, func);
	sigemptyset(&set);//集合初始化为空
	sigaddset(&set, SIGINT);
	sigprocmask(SIG_UNBLOCK, &set, &saveset);//将之前信号状态保存到saveset中
	
	sigprocmask(SIG_BLOCK, &set, &oset); //对set里信号进行block并保存之前的状态到oset里面去
	
	for(j = 0; j < 100; j++)
	{
		
		for(i = 0; i < 5; i++)
		{
			write(1, "*", 1);
			sleep(1);
		}
		write(1, "\n", 1);
		
		sigsuspend(&oset);	//原子操作,解除set信号的阻塞后,马上进入等待阶段
		/*
		sigset_t tmpset;
		sigprocmask(SIG_SETMASK, &oset, &tmpset); //恢复成oset里面的状态并将set里面的状态保存到tmpset中
		pause();
		sigprocmask(SIG_SETMASK, &tmpset, NULL); 
		*/
	}
	
	sigprocmask(SIG_SETMASK, &saveset, NULL); //做恢复用
	
	atexit(hook);
	
	exit(0);
}

在这里插入图片描述
打印*期间发送中断信号,换行后会响应此信号。

12.sigaction()函数

signal 函数的使用方法简单,但并不属于 POSIX 标准,在各类 UNIX 平台上的实现不尽相同,因此其用途受到了一定的限制。而 POSIX 标准定义的信号处理接口是 sigaction 函数,其接口头文件及原型如下:

int sigaction(int signum, const struct sigaction *act,
                     struct sigaction *oldact);

struct sigaction 类型用来描述对信号的处理,定义如下:

 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 是一个函数指针,其含义与 signal 函数中的信号处理函数类似。成员sa_sigaction 则是另一个信号处理函数,它有三个参数,可以获得关于信号的更详细的信息。当 sa_flags 成员的值包含了 SA_SIGINFO 标志时,系统将使用 sa_sigaction 函数作为信号处理函数,否则使用 sa_handler 作为信号处理函数。在某些系统中,成员 sa_handler 与 sa_sigaction 被放在联合体中,因此使用时不要同时设置。sa_mask 成员用来指定在信号处理函数执行期间需要被屏蔽的信号,特别是当某个信号被处理时,它自身会被自动放入进程的信号掩码,因此在信号处理函数执行期间这个信号不会再度发生。 sa_flags 成员用于指定信号处理的行为,它可以是一下值的“按位或”组合。

◆ SA_RESTART:使被信号打断的系统调用自动重新发起。
◆ SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号。
◆ SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程。
◆ SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。
◆ SA_RESETHAND:信号处理之后重新设置为默认的处理方式。
◆ SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数。

re_restorer 成员则是一个已经废弃的数据域,不要使用。
siginfo_t 结构体

The siginfo_t argument to sa_sigaction is a struct with the following fields:

           siginfo_t {
               int      si_signo;     /* Signal number 信号编号 */
               int      si_errno;     /* An errno value 如果为非零值则错误代码与之关联 */
               int      si_code;      /* Signal code 说明进程如何接收信号以及从何处收到*/
               int      si_trapno;    /* Trap number that caused
                                         hardware-generated signal
                                         (unused on most architectures) */
               pid_t    si_pid;       /* Sending process ID适用于SIGCHLD,代表被终止进程的PID  */
               uid_t    si_uid;       /* Real user ID of sending process适用于SIGCHLD,代表被终止进程所拥有进程的UID  */
               int      si_status;    /* Exit value or signal 适用于SIGCHLD,代表被终止进程的状态 */
               clock_t  si_utime;     /* User time consumed 适用于SIGCHLD,代表被终止进程所消耗的用户时间 */
               clock_t  si_stime;     /* System time consumed 适用于SIGCHLD,代表被终止进程所消耗系统的时间 */
               ==================
               sigval_t si_value;     /* Signal value */
               ==================
               int      si_int;       /* POSIX.1b signal */
               void    *si_ptr;       /* POSIX.1b signal */
               int      si_overrun;   /* Timer overrun count;
                                         POSIX.1b timers */
               int      si_timerid;   /* Timer ID; POSIX.1b timers */
               void    *si_addr;      /* Memory location which caused fault */
               long     si_band;      /* Band event (was int in
                                         glibc 2.3.2 and earlier) */
               int      si_fd;        /* File descriptor */
               short    si_addr_lsb;  /* Least significant bit of address
                                         (since Linux 2.6.32) */
               void    *si_call_addr; /* Address of system call instruction
                                         (since Linux 3.5) */
               int      si_syscall;   /* Number of attempted system call
                                         (since Linux 3.5) */
               unsigned int si_arch;  /* Architecture of attempted system call
                                         (since Linux 3.5) */
           }

1.当多个信号共用同一个处理函数的时候,希望在响应某一个信号期间,把其他信号阻塞住。
signal函数有这种问题,可以用sigaction()函数

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <syslog.h>
#include <errno.h>
#include <string.h>
#include <signal.h>

#define FNAME "/tmp/out"

static FILE* fp;

static int daemonize(void)
{
	pid_t pid;
	int fd;

	pid = fork();	
	if(pid < 0)
		return -1;

	if(pid > 0)
	{
		exit(0);	
	}
	
	fd = open("/dev/null", O_RDWR);
	if(fd < 0)	
		return -1;

	dup2(fd, 0);
	dup2(fd, 1);
	dup2(fd, 2);
	if(fd > 2)
		close(fd);
	
	setsid();		
	
	chdir("/"); //将工作路径设置到根路径下
	
	return 0;
	
}

//添加部分,s是对多个信号进行区分,可以进行不同的处理
//碰到异常情况退出时,需要做的事情
static void daemon_exit(int s)
{
	/*
	if(s == SIGINT)
	
	else if(s == SIGQUIT)
	
	else(s == SIGQUIT)
	*/
	fclose(fp);
	closelog();
	exit(0);//可以调用钩子函数
}

int main()
{		
	int i;
	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);
	sa.sa_flags = 0;
	
	sigaction(SIGINT, &sa, NULL);
	sigaction(SIGQUIT, &sa, NULL);
	sigaction(SIGQUIT, &sa, NULL);
	
	/*
	采用signal可能会造成信号重入。
	因为信号可以嵌套,假如SIGINT响应后

	signal(SIGINT, daemon_exit);
	signal(SIGQUIT, daemon_exit);
	signal(SIGTERM, daemon_exit);
	*/
	
	openlog("mydaemon", LOG_PID, LOG_DAEMON);

	if(daemonize())
	{
		syslog(LOG_ERR, "daemonize() failed!");	
		exit(1);
	}
	else
	{
		syslog(LOG_INFO, "daemonize() sucess");
	}

	fp = fopen(FNAME, "w");	
	if(fp == NULL)
	{
		syslog(LOG_ERR, "fopen(): %s", strerror(errno));
		exit(1);
	}
	
	syslog(LOG_INFO, "%s was opened.", FNAME);

	for(i = 0; ;i++)	
	{
		fprintf(fp, "%d\n", i);	
		fflush(fp);
		syslog(LOG_DEBUG, "%d is printed", i);

		sleep(1);
	}	
	
	/*
	没有办法正常收尾
	*/
	//fclose(fp);
	//closelog();
	
	exit(0);
}

2.signal函数不能区分信号的来源,只是接收到信号然后执行此信号对应的行为。
sigation函数可以区分。

参见令牌桶算法(二)

13.实时信号

标准信号的响应顺序是未定义的;
如果进程中收到标准和实时信号,则先响应标准信号;
用到的信号是否排队,相应是否有要求,取决于用哪种信号。

实时信号终端查看:
在这里插入图片描述
实时信号最多能排多长:
在这里插入图片描述

#include<stdio.h>
#include<stdlib.h>
#include<signal.h>

#define MYRTSIG	(SIGRTMIN+6)

/*
1.打印一行*期间,进程不能被信号打断
2.打印一行换行后,接收到信号在继续打印下一行
3.打印一行*期间,会对过来的多个实时信号依次响应
*/
void func(int i)
{
	write(1, "!", 1);
}

void hook(void)
{
	write(1, "\n", 1);
}

int main()
{
	int i,j; 
	sigset_t set, oset, saveset;
	
	signal(MYRTSIG, func); //接收信号类型为实时信号
	sigemptyset(&set);//集合初始化为空
	sigaddset(&set, MYRTSIG);
	sigprocmask(SIG_UNBLOCK, &set, &saveset);//将之前信号状态保存到saveset中
	
	sigprocmask(SIG_BLOCK, &set, &oset); //对set里信号进行block并保存之前的状态到oset里面去
	
	for(j = 0; j < 100; j++)
	{
		
		for(i = 0; i < 5; i++)
		{
			write(1, "*", 1);
			sleep(1);
		}
		write(1, "\n", 1);
		
		sigsuspend(&oset);	//原子操作,解除set信号的阻塞后,马上进入等待阶段
	}
	
	sigprocmask(SIG_SETMASK, &saveset, NULL); //做恢复用
	
	atexit(hook);
	
	exit(0);
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值