文章目录
- 📝 由软件条件产⽣信号
- 🌠 基本alarm验证-体会IO效率问题
- 🌉设置重复闹钟
- 🌠如何理解软件条件
- 🌉如何简单快速理解系统闹钟
- 🚩总结
📝 由软件条件产⽣信号
SIGPIPE
是⼀种由软件条件产⽣的信号,在“管道”中已经介绍过了。本节主要介绍和SIGALRM
信号。
NAME
alarm - set an alarm clock for delivery of a signal
SYNOPSIS
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
DESCRIPTION
alarm() arranges for a SIGALRM signal to be delivered to the calling process in seconds seconds.
If seconds is zero, any pending alarm is canceled.
In any event any previously set alarm() is canceled.
RETURN VALUE
alarm() returns the number of seconds remaining until any previously scheduled alarm was due to be deliv‐
ered, or zero if there was no previously scheduled alarm.
调用alarm
函数可以设定一个闹钟,也就是告诉内核在seconds
秒之后给当前进程发SIGALRM
信号,该信号的默认处理动作是终止当前进程。
这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。打个⽐⽅,某⼈要⼩睡⼀觉,设定闹
钟为30分钟之后响,20分钟后被⼈吵醒了,还想多睡⼀会⼉,于是重新设定闹钟为15分钟之后响,“以
前设定的闹钟时间还余下的时间”就是10分钟。如果seconds值为0,表⽰取消以前设定的闹钟,函数
的返回值仍然是以前设定的闹钟时间还余下的秒数。
🌠 基本alarm验证-体会IO效率问题
程序的作⽤是1
秒钟之内不停地数数,1
秒钟到了就被SIGALRM
信号终⽌。必要的时候,对SIGALRM
信号进⾏捕捉
- IO 多,因频繁往我们的显示器文件进行写入操作
//IO 多
#include <iostream>
#include <unistd.h>
#include <signal.h>
int main()
{
int count = 0;
alarm(1);
while(true)
{
std::cout<< "count : "<< count <<std::endl;
count++;
}
return 0;
}
- IO 少:过一秒后收到
alarm
的信号再通过特定的处理函数可以进行一次写入显示器文件
#include <iostream>
#include <unistd.h>
#include <signal.h>
int count = 0;
void handler(int signumber)
{
std::cout<< " count: "<<count<<std::endl;
exit(0);
}
int main()
{
signal(SIGALRM, handler);
alarm(1);
while(true)
{
count++;
}
return 0;
}
由23W的次数,增强到5亿次,可见频繁的进行IO会减少很高的效率
结论:
- 闹钟会响⼀次,默认终⽌进程
- 有IO效率低
🌉设置重复闹钟
#include <iostream>
#include <string>
#include <sys/types.h>
#include <unistd.h>
#include <wait.h>
#include <signal.h>
#include <vector>
#include <functional>
using func_t = std::function<void()>;
int gcount = 0;
std::vector<func_t> gfuncs;
//把信号更换成为硬件中断
void handler(int signo)
{
for(auto& f: gfuncs)
{
f();
}
std::cout<< "gcount: "<<gcount <<std::endl;
// int n = alarm(1);//重设闹钟,会返回上一次闹钟的剩余时间
// std::cout<<"剩余时间:"<< n <<std::endl;
}
int main()
{
gfuncs.push_back([](){
std::cout<< "我是一个内核刷新操作"<<std::endl;
});
gfuncs.push_back([](){
std::cout<< "我是⼀个检测进程时间⽚的操作,如果时间⽚到了,我会切换进程"<<std::endl;
});
gfuncs.push_back([](){
std::cout<< "我是⼀个内存管理操作,定期清理操作系统内部的内存碎⽚"<<std::endl;
});
alarm(1);//一次性闹钟,超时alarm会自动被取消
signal(SIGALRM, handler);
while(true)
{
pause();
std::cout<< "我醒来了..." <<std::endl;
gcount++;
}
return 0;
}
程序运行一次,就暂停了,当一个进程调用pause
函数时,它会使该进程进入睡眠状态(阻塞状态),并暂停执行后续的代码,直到该进程接收到一个信号并从该信号的处理程序中返回。也就是说,pause函数实际上是在等待一个信号来中断当前的暂停状态,使进程能够继续往下执行。
我们可以在handler函数中在设置一个alarm,形成发送取消中断信号,重新运行:
#include <iostream>
#include <string>
#include <sys/types.h>
#include <unistd.h>
#include <wait.h>
#include <signal.h>
#include <vector>
#include <functional>
using func_t = std::function<void()>;
int gcount = 0;
std::vector<func_t> gfuncs;
//把信号更换成为硬件中断
void handler(int signo)
{
for(auto& f: gfuncs)
{
f();
}
std::cout<< "gcount: "<<gcount <<std::endl;
int n = alarm(1);//重设闹钟,会返回上一次闹钟的剩余时间
std::cout<<"剩余时间:"<< n <<std::endl;
}
int main()
{
gfuncs.push_back([](){
std::cout<< "我是一个内核刷新操作"<<std::endl;
});
gfuncs.push_back([](){
std::cout<< "我是⼀个检测进程时间⽚的操作,如果时间⽚到了,我会切换进程"<<std::endl;
});
gfuncs.push_back([](){
std::cout<< "我是⼀个内存管理操作,定期清理操作系统内部的内存碎⽚"<<std::endl;
});
alarm(1);//一次性闹钟,超时alarm会自动被取消
signal(SIGALRM, handler);
while(true)
{
pause();
std::cout<< "我醒来了..." <<std::endl;
gcount++;
}
return 0;
}
再次运行,会进行死循环操作流程:
1.main
函数进来设置alarm
一秒,然后通过signal设置alarm
的处理方式从终止改为handler
,然后进入while(true)
执行pause()
暂停循环;当一秒到来,执行handler
函数,等到handler
里的alarm
函数再次设置启动,pause()
收到取消睡眠状态信号,再次运行这个1
秒。接下来就是往复复的动作了。
结论:
- 闹钟设置⼀次,起效⼀次
- 重复设置的⽅法
- alarm(0):如果seconds值为0,表⽰取消以前设定的钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数
🌠如何理解软件条件
在操作系统中,信号的软件条件指的是由软件内部状态或特定软件操作触发的信号产⽣机制。这些条件包括但不限于定时器超时(如alarm函数设定的时间到达)、软件异常(如向已关闭的管道写数据产⽣的SIGPIPE信号)等。当这些软件条件满⾜时,操作系统会向相关进程发送相应的信号,以通知进程进⾏相应的处理。简⽽⾔之,软件条件是因操作系统内部或外部软件操作⽽触发的信号产⽣。
🌉如何简单快速理解系统闹钟
系统闹钟,其实本质是OS必须⾃⾝具有定时功能,并能让⽤⼾设置这种定时功能,才可能实现闹钟这
样的技术。
现代Linux是提供了定时功能的,定时器也要被管理:先描述,在组织。内核中的定时器数据结构是:
struct timer_list
{
struct list_head entry;
unsigned long expires;
void (*function)(unsigned long)
unsigned long data;
struct tvec_t_base_s *base;
};
我们不在这部分进行深究,为了理解它,我们可以看到:定时器超时时间expires
和处理方法function
。
操作系统管理定时器,采用的是时间轮的做法,但是我们为了简单理解,可以把它在组织成为"堆结构"(小堆排序,处理时间短的节点)。