我的编程空间,编程开发者的网络收藏夹
学习永远不晚

OS内核的信号机制:所有的异步都可以是同步的

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

OS内核的信号机制:所有的异步都可以是同步的

这个需求要做的事,跟Linux内核的信号机制是一样的。

OS内核的信号机制,在1970年的Unix时代就有了,是一个上古话题。

在unix里,可以使用kill -9 pid命令杀掉进程(pid为进程号),在Linux里也可以。

1.OS内核的信号

有个专有的宏定义#define SIGKILL 9,然后信号9就成了一个特别牛的信号,大概除了0号idle进程和1号init进程之外,其他进程都可以杀死。

0号进程和1号进程是不能杀死的,否则系统就崩溃了!

int sys_kill(int sig, int pid)
{
if (sig < 0 || pid < 0)
return -EINVAL;
if (0 == pid || 1 == pid) {
if (SIGKILL == sig) return -1;
}
tasks[pid]->sigmap |= 1 << sig;
return 0;
}

OS内核里对应着kill命令的sys_kill()系统调用,大概是上面这样:

在进程的task结构体的sigmap成员变量上,设置1个标志位,进程就可以收到信号了。

每个进程,在OS内核里都被一个task结构体表示,这个结构体的其中一个成员变量就是记录信号的:我们给他起名叫sigmap,Linux的不一定要叫这个名字,但肯定有这一项。

这个信号在什么时候处理呢?

等到收信号的进程下一次被调度运行的时候。

当前运行的进程,肯定是发信号的进程,否则它没法主动发起kill()系统调用。

发信号的进程做的事,只是把信号设置到接收进程的信号图上,这时信号实际上已经发到了:但是接收进程并不会马上因为SIGKILL信号而被杀死。

SIGKILL信号的杀进程,实际上进程是自杀的!

当收到信号的进程再次被调度运行的时候,操作系统会让它先执行信号的处理函数,而SIGKILL的处理函数,就是exit()系统调用:进程退出。

这个过程可以是异步的,等到接收进程下一次被调度时再处理,至于什么时候轮到它:等吧。

也可以让它马上同步处理,只需要在sys_kill()函数的末尾加一行代码就行:

shedule_task( tasks[pid] );

直接选择接收进程是下一个要调度的进程,并且马上调度它运行:接下来它就完事了。

不需要等OS内核统计时间片,确定调度的优先级了,既然用户想让它挂掉,OS当然要马上让它挂掉。

毕竟Linux系统也惹不起用户啊,用户是可以重装windows的​

接下来,说说shedule_task()之后的细节。

2.信号是怎么处理的

每个信号都有一个处理函数,叫信号处理函数。

信号处理函数,是在用户态的代码里运行的。

所以,程序员可以自己给部分信号编写处理函数,用signal()系统调用注册到OS内核,就可以(在收到信号时)运行这个自己编写的函数了。

如果信号处理函数是在内核状态运行的,那显然用户编写的函数是没法运行的,因为用户函数的内存地址在用户空间(它在进程的代码段里)。

OS内核在信号处理时要做的是,把进程从内核返回后要运行的代码地址,改成信号处理函数的地址。

修改过程如下:

系统内核的信号处理过程

1)进程从内核返回时的状态,如上图。

内核栈上的寄存器排布顺序不一定是对的,这要查intel的手册,但是这些项肯定都有。

在进程使用iret指令(中断返回)从内核返回的那一刻,内核栈上的这些数据都要弹出到对应的寄存器。

然后,进程就会运行EIP指向的用户代码,同时用户态的栈顶就是ESP。

EIP和ESP指向的内容到底是什么,内核不需要管:这是由程序员写代码时确定的。

进程从内核返回之后的错误,错的是程序员,不是系统内核。

但要是返不回来,或者不能处理信号,错的就是系统内核了。

2)OS内核要做的是,修改内核栈上、保存的、用户态的、EIP和ESP(注意这3个定语):

A,让EIP指向信号处理函数,

B,让ESP指向信号处理函数的参数,

C,在信号处理函数的下方,放上“真正的”返回地址,

D,在信号处理函数运行完之后,丢掉(信号处理函数的)参数,弹出真正的返回地址:让程序恢复正常的状态,继续运行。

如上图中的绿字部分。

如果一次要处理多个信号的话,就顺着用户栈继续叠加就行。

siska内核demo里的信号处理代码,如下的3张图:

因为信号处理函数有参数,而参数要压在用户态的栈上,所以信号处理函数运行完之后还要清理它。

所以,与一般的C函数不同,信号处理函数是被调函数清理堆栈的:即它是pascal调用,而不是C调用!

C调用,都是主调函数清理堆栈的。

所以,信号处理函数的总入口是一段汇编代码,用来在C语言里完成这个pascal调用。

这么看来,pascal这种老语言,也不是想象的那么差​

这个信号处理方式,是我给出来的解决方案​

至于Linux是不是也这么做的,我就不知道了。

但是,这么做是可行的。

siska信号处理,pascal调用的汇编

上图95行的call *(%eax),就是调用信号处理的函数指针。

它前后的汇编代码,都是准备参数和清理堆栈。

3.回到开头的问题

怎么让定时器线程在触发之后,让回调函数在工作线程里运行?

回调函数一般有一个参数,表示回调上下文,但没有返回值。

因为定时器的添加和处理在2个线程里,回调函数的返回值没有意义。

如果回调函数的处理出错了,就在上下文里设置错误码作为提示。

所以,它的函数声明是这样的:void callback(void* ctx);

要让它正常运行,必须把回调上下文的指针添加到工作线程的用户栈上,同时让工作线程的内核栈上保存的EIP指向回调函数。

这个处理方式,与OS内核的信号处理方式是一样的。

信号处理函数的声明:void sighandler(int sig); 也是一个参数、无返回值。

在定时器触发之后,定时器线程可以发起一个系统调用,把这些信息给到内核,然后内核修改工作线程的数据,让定时器的回调处理“像个信号”一样就可以了​

这个系统调用如果Linux没有提供的话,就只能自己修改Linux内核代码,或者给Linus大牛提个需求了(他有可能看不过来你的邮件)。

PS:

工作线程和定时器线程在同一个进程里,所以它们的用户态内存的代码段、数据段、堆都是共享的,只是内核栈和用户栈不一样。

内核栈:在内核看来,每个线程也是一个可调度的进程,它必须有自己的内核栈和页表。

同一个进程的不同线程之间共享内存,靠的是页表的映射:把它们映射到同一个物理内存页上。

用户栈:不同的线程可以并发运行,它们的用户栈肯定是不同的,否则局部变量就互相覆盖了:这肯定是不可能的。

siska里信号处理的代码,如下:

siska信号处理,1

siska信号处理,2

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

OS内核的信号机制:所有的异步都可以是同步的

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

OS内核的信号机制:所有的异步都可以是同步的

每个进程,在OS内核里都被一个task结构体表示,这个结构体的其中一个成员变量就是记录信号的:我们给他起名叫sigmap,Linux的不一定要叫这个名字,但肯定有这一项。
OS内核异步2024-12-01

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录