并发_信号(二)

6.常用函数

1.kill函数

发送信号;
监测进程是否存在;

#include <sys/types.h>
 
#include <signal.h>

int kill(pid_t pid, int sig);

参数:
pid:可能选择有以下四种

  1. pid大于零时,pid是信号欲送往的进程的标识。
  2. pid等于零时,信号将送往所有与调用kill()的那个进程属同一个使用组的进程。
  3. pid等于-1时,信号将送往所有调用进程有权给其发送信号的进程,除了进程1(init)。
  4. pid小于-1时,信号将送往以-pid为组标识的进程。

sig:准备发送的信号代码,假如其值为零则没有任何信号送出,但是系统会执行错误检查,通常会利用sig值为零来检验某个进程是否仍在执行。

返回值:
成功执行时,返回0。失败返回-1,errno被设为以下的某个值。
EINVAL:指定的信号码无效(参数 sig 不合法)
EPERM;权限不够无法传送信号给指定进程
ESRCH:参数 pid 所指定的进程或进程组不存在

2.raise函数

kill函数将信号发送给进程或进程组。raise函数则允许进程向自身发送信号。

#include <signal.h>

int raise(int signo)

单线程中,等价于 kill(getpid(), sig);
多线程中,等价于pthread_kill(pthread_sekf(), sig);

返回值
如果成功该函数返回零,否则返回非零。

3.alarm函数

#include <unistd.h> 
unsigned int alarm(unsigned int seconds); 

1.因为信号响应有延时,10ms以内的计时不准,10ms以上的计时准确。
2.alarm也称为闹钟函数,它可以在进程中设置一个定时器,当定时器指定的时间到时,它向进程发送SIGALRM信号。
3.可以设置忽略或者不捕获此信号。如果采用默认方式则终止调用该alarm函数的进程。

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

int main()
{
	alarm(5);
	
	sleep(14);
	
	printf("111");
	
	exit(0);
}

在这里插入图片描述
结果并没有打印111,因为程序经过5s后默认终止;

设置信号SIGALRM在经过seconds指定的秒数后传送给目前的进程,如果在定时未完成的时间内再次调用了alarm函数,则后一次定时器设置将覆盖前面的设置,没有办法实现多任务计数器。
当seconds设置为0时,定时器将被取消。它返回上次定时器剩余时间,如果是第一次设置则返回0。

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

int main()
{
	alarm(5);
	
	alarm(1);
	
	printf("111\n");
	
	exit(0);
}

程序执行1s中就会结束;

因此有的平台sleep函数是用alarm+pause封装,如果源码里面有sleep函数,不可取!

获取时间精度对比

1.使用time

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

int main()
{
	int64_t count = 0;
	time_t end;

	end = time(NULL) + 5;

	while(time(NULL) < end)
		count++;

	printf("%lld\n", count);
	exit(0);
}

在这里插入图片描述
2.使用alarm+signal
signal语句要在alarm之前

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

//static loop = 1; //不能进行编译器优化
static voliate loop = 1;

static void alrm_handler(int i)
{
	loop = 0;
}

int main()
{
	int64_t count;
	signal(SIGALRM, alrm_handler); 
	alarm(5);
	
	while(loop)
		count++;
	
	printf("%lld\n", count);
	
	exit(0);
}

在这里插入图片描述
精度不在一个量级,自增次数也不在一个量级。

扩展:编译器优化带来的问题及解决
volatile关键字:
1.作为指令关键字,确保本条指令不会因为编译器的优化而省略,而且要求每次从内存中直接读取值。
2.当使用volatile声明变量值时,系统总是重新从它所在的内存读取数据,直接访问变量地址,而编译器对于访问该变量时也不再进行优化
3.volatile关键字影响编译器的结果,用volatile声明的变量表示该变量随时可能发生变化(因为编译器优化时可能将其放入寄存器中),与该变量有关的运算,不要再进行编译优化以免出错。
参考链接

对于while(loop) count++; 如果使用gcc -O1进行编译器优化,因为loop作为while条件并没有在循环体内作为变量参与,则编译器优化时会把loop优化成常量,产生死循环。
在这里插入图片描述
编译器优化后自增次数提高了10倍;

4.pause函数

#include<unistd.h>

int pause(void);

功能:一旦执行进程就会进入无限的休眠(暂停),直到接收到信号且信号函数执行成功返回后, pause函数才会返回,继续向下执行。

1.alarm + signal +pause实现流量控制:每秒打印10个字符
漏桶实现

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

#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> <des_file>", argv[0]);
		exit(1);
    }
    
    int sfd, dfd = 1;
    int ret;    
    int wet; 
	int pos;
    char buf[BUFFSIZE];

 
	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);
   
    while(1)
    {
		while(!loop)
			pause; //此处使用pause能够让当前进程休眠,不用一直监测
			//; 会让进程一直监测,占用cpu资源
		loop= 0;
		//问题:如果sfd是设备,设备内容为空,则此条件会导致进程一直在此处等待
        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);
}

在这里插入图片描述
令牌桶
没有流量过来就累加计数,有大流量过来就把之前计数全部消耗完;
注意点:
token的取值和赋值要放在一条语句中,原子操作

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

#define CPS 10
#define BUFFSIZE CPS
#define BURST 100 //令牌桶最多能够攒的令牌数

//sig_atomic_t保证token的取值和赋值操作由一条指令完成
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(stderr, "usage: %s <src_file>", argv[0]);
		exit(1);
    }
    
    int sfd, dfd = 1;
    int ret;    
    int wet; 
	int pos;
    char buf[BUFFSIZE];

	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);
   
    while(1)
    {
    	//1
		while(token <= 0)
			pause; //此处使用pause能够让当前进程休眠,不用一直监测
			//; 会让进程一直监测,占用cpu资源
		token--; //此语句并不是由一条指令完成,从token地址取值,--后再放入内存中
		
        while((ret = read(sfd, buf, BUFFSIZE)) < 0)
		{
			if(errno == EINTR)
				continue;
            perror("read src_file");
            break;
		}
       //2
        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);
}

//1 … //2代码段解释
read为阻塞IO,当sfd没有数据过来时,会一直等待;如果等待时间到1s,alarm会向进程发送SIGALRM信号,token会自增1并且信号会打断read调用,进入while循环体内。
循环体内检测到信号是打断的,则执行continue继续回到while循环条件中。
如果sfd仍旧没有数据过来,则read仍旧阻塞,等待时间到1s,token++,继续执行上述过程;
当sfd有数据过来时,会把while(1)执行当前token次,直至token<0;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值