OS57.【Linux】理解信号: 信号的产生(2) 键盘输入和系统调用
本文探讨了Linux系统中信号的产生机制。首先解释了Ctrl+C如何通过硬件中断转化为信号:键盘输入触发中断,操作系统解释为信号发送给前台进程。信号产生是异步的,进程随时可能被中断。文章详细介绍了三种产生信号的系统调用:kill(向指定进程发送信号)、raise(向自身发送信号)和abort(发送SIGABRT信号并终止进程)。通过代码示例展示了这些调用的使用方法和效果,包括自定义信号处理函数的实
目录
1.知识回顾
之前在OS57.【Linux】理解信号: 信号的产生(1)文章讲过:Ctrl+C可以杀死前台进程,下面讲讲Ctrl+C是如何变成信号的
2.Ctrl+C是如何变成信号的
硬件中断
操作系统为了保证安全,进程是无法直接读取键盘数据的,显然键盘的键位被按下,一定是操作系统先知道,由于Linux的"一切皆文件"的思想,键盘也是文件,那么操作系统肯定会先读取键盘缓冲区文件
那操作系统怎么知道键盘上有数据要读取呢?
答: CPU上的针脚和外设是间接相连的,键盘有数据时会向CPU发送硬件中断,有CPU告诉操作系统将键盘数据拷贝到键盘文件缓冲区
计算机中的外设有很多,显然每个外设都可以向CPU发送中断,那如何确保是键盘发送的中断呢?
答: 每个中断都有唯一的编号,这个编号为中断号,CPU依此来区分不同的中断
外设向CPU发送中断,CPU根据中断解释出中断号,保护模式下, CPU根据中断号到中断描述符表找出对应中断例程的地址,然后CPU跳转到那里执行,结束后CPU通过iret返回
这个中断例程可以是操作系统将键盘外设上的数据拷贝到键盘文件缓冲区
补充: 如果多个硬件同时发送中断,操作系统会串行处理
信号
上方讲的是硬件中断,之前在OS57.【Linux】理解信号: 信号的产生(1)文章讲的信号是用软件的方式模拟硬件中断
变成信号的整个过程
用户按下Ctrl+C,这个键盘输入产生一个硬件中断,被操作系统获取,解释成信号,发送给目标前台进程, 前台进程因为收到信号,进而引起进程退出
回显的含义

正常情况下,从键盘输入的数据会显示到显示器上,这是因为操作系统将键盘缓冲区的数据拷贝到显示器的缓冲区

注: 图中的"键盘文件"和"显示器文件"是根据Linux的"一切皆文件"的思想,将硬件的内容抽象为文件

得出不回显的含义: 键盘缓冲区的数据不拷贝到显示器的缓冲区
3.信号产生是异步的
前台进程在运行过程中用户随时可能按下Ctrl+C而产生一个信号,也就是说该进程的用户空间代码执行到任何地方都有可能收到SIGINT信号而终止,所以信号相对于进程的控制流程来说是异步
(Asynchronous)的
1.例子理解: 操作系统不知道用户什么时候会按键盘,这是异步的
2.对立面理解: 同步的反义词是异步
信号是进程之间事件异步通知的一种方式,属于软中断,软中断是仿照硬件中断实现的软件逻辑
4.信号的产生: 使用系统调用
新建以下文件:
test_signal
├── hello_world.cpp
├── makefile
└── test_signal.cpp
hello_world.cpp写入:
makefile:
kill

作用: 向指定进程发送指定的信号
kill的参数:
pid: 进程号
sig: 信号编号
编写"kill -signo pid"命令
test_signal.cpp写入:
#include <unistd.h>
#include <iostream>
#include <signal.h>
int main(int argc,char* argv[])
{
if (argc!=3)
{
std::cout<<"用法: kill -signo pid"<<std::endl;
exit(1);
}
std::string signo_s=argv[1];
std::string pid_s=argv[2];
int signo=std::stoi(std::string(signo_s,1));
int pid=stoi(pid_s);
if (pdi<0||pid>64)
{
std::cout<<"非法的信号编号"<<std::endl;
exit(2);
}
if (kill(pid,signo))
{
perror("kill failed");
exit(3);
}
std::cout<<"已发送信号"<<std::endl;
return 0;
}
启动一个无限打印Hello World的进程,尝试杀死
运行结果:

raise

作用: 发送一个信号给调用者
sig是信号的编号
test_signal.cpp写入:
#include <unistd.h>
#include <iostream>
#include <signal.h>
int main(int argc,char* argv[])
{
int cnt=0;
for(;;)
{
std::cout<<"Hello World!"<<std::endl;
sleep(1);
cnt++;
if (cnt==5)
raise(9);
}
return 0;
}
运行结果:

结论: kill(getpid(),signo)和rasie(signo)作用等价
abort

作用: 给调用者发送6号信号SIGABRT
可以手动捕获abort()发送的6号信号,test_signal.cpp写入:
#include <unistd.h>
#include <iostream>
#include <signal.h>
void myhandler(int signo)
{
std::cout<<"已执行自定义动作,信号编号为"<<signo<<"号"<<std::endl;
}
int main(int argc,char* argv[])
{
signal(SIGABRT,myhandler);
int cnt=0;
for(;;)
{
std::cout<<"Hello World!"<<std::endl;
sleep(1);
cnt++;
if (cnt==5)
abort();
}
return 0;
}
运行结果:

发现捕获SIGABRT信号后,程序仍然终止了

abort函数发送SIGABRT信号,编号为6

结论: abort()除了能发送信号,内部实现有终止进程这个功能
更多推荐


所有评论(0)