linux多进程通过中断实现,Linux驱动中断上下文中会发生什么结果实验测试

一、前言

每一个Linux驱动工程师都知道这样一个准则:在中断上下文中不能睡眠。但是为什么interrupt context中不能调用导致睡眠的kernel API呢?如果驱动这么做会导致什么样的后果呢?这就是本文探讨的主题。为了理解这个主题,我们设计了一些非常简单的驱动程序和用户空间的程序,实际做实验观察实验效果,最后给出了结果和分析。

BTW,本文的实验在X86 64bit + 标准4.4内核上完成。

二、测试程序

1、cst驱动模块

我们首先准备一个能够在中断上下文中睡眠的驱动程序,在这里我称之Context schedule test module(后文简称cst模块)。这个驱动程序类似潜伏在内核中的“捣蛋鬼”,每隔1秒随机命中一个进程,然后引发调度。首先准备一个Makefile,代码如下:

262e788a104bd13b258179764e9215f2.png

按理说代码中的xxxx应该是我的名字,如果你愿意在你的环境中测试,可以修改成你的名字,当然,最重要的是需要某个版本的内核代码。在内核升级文档中,我已经编译了/home/xxxx/work/linux-4.4.6目录下的内核,并把我的计算机升级到4.4.6的内核上,如果你愿意可以按照那份文档进行升级,否则可能会有一些版本问题需要处理。除了Makefile之外,还需要一个Kbuild文件:

obj-m := cst.o

当然,最重要的是cst模块的源代码:

7d408a02c39c0fa360b78982ca4f2148.png

代码非常的简单,无需多说,直接make就可以编译得到cst.ko的内核模块了。

2、用户空间测试程序

为了更方便的测试,我们需要准备一个“受害者”,代码如下:

90794b058e8f9d7ba4e9c7ea90917617.png

这段代码也很简单:不断的产生一个随机数,并运算其平方根,在使得的时候,向用户输出一些字符,表明自己的状态。当程序执行起来的时候,大部分时间在用户态(运算),偶尔进入内核态(printf)。这个进程并不知道在内核态有一个cst的模块,每隔一秒就发射一只休眠之箭,可能命中用户态,也可能命中内核态,看运气吧,但是无论怎样,该进程被射中之后都会进入睡眠。

三、执行测试并观察结果

1、先把用户空间的测试程序跑起来

要想测试导弹(呵呵~~我们的cst模块就是一个捣蛋) 的性能,必须要有靶机或者靶舰。当然也可以不用“靶机”程序,只不过捣蛋鬼cst总是命中swapper进程,有点无趣,因此这里需要把我们用户空间的那个测试程序跑起来,让CPU先活跃起来。

需要注意的是,在多核cpu上,我们需要多跑几个“靶机”进程才能让系统不会always进入idle状态。例如我的T450是4核cpu,因此我需要运行4个靶机程序才能让系统中的4个cpu core都燥起来。可以通过下面的命令确认:

ps –eo comm,psr | grep cst

BTW,靶机程序是cst_test。通过上面的命令,可以看到系统中运行了四个cst_test进程,分别在4个cpu上。

2、插入内核模块

靶机已经就绪,是时候发射捣蛋了,命令如下:

sudo insmod ./cst.ko

一旦插入了cst内核模块,捣蛋鬼就开始运作了,每隔1秒钟发射一次,总有一个倒霉蛋被命中,被调度。当然,在我们的测试中,一般总是cst_test这个进程被命中。

3、观察结果

一切准备就绪,是时候搬个小板凳坐下来看好戏了。当然,我们需要一个观察的工具,输入如下命令:

sudo tail –f /var/log/messages

在上面的cst模块中,输出并没有直接到控制台,因此我们需要通过内核日志来看看cst的运行情况。

四、结果和分析

1、结果

很奇怪,一切都是正常的,系统没有死,cst模块也运行正常,cst_test进程也始终保持alive状态,不断的运行在无聊的平方根、打印着无聊的字符串。唯一异常的是日志,每隔1秒钟就会dump stack一次。

2、分析

当cst模块命中cst_test进程,无论是userspace还是kernel space,都会在内核栈上保存中断发生那一点的上下文,唯一不同是如果发生在userspace,那么发生中断那一刻,内核栈是空的,而如果在kernel space,内核栈上已经有了cst_test通过系统调用进入内核的现场以及该系统调用各个函数的stack frame,当中断发生的时候,在当前内核栈的栈顶上继续压入了中断现场,之后就是中断处理的各个函数的stack frame,最后是cst_timer_handler的stack frame,由于调用了schedule函数,cst_test进程的现场被继续压入内核栈,调度器决定下一个调度的进程。

cst_test进程虽然被调度了,但是仍然在runqueue中,调度器一定会在适当的时机调度cst_test进程重新进入执行态,这时候恢复其执行就OK了,cpu执行cst_TImer_handler函数schedule之后的代码,继续未完的中断上下文的执行,然后从内核栈恢复中断现场,一切又按照原路返回了。

当然,这里的测试看起来一切OK,但这并不是说可以自由的在中断上下文中调用导致睡眠的内核API,因为我们这里给出了一个简单的例子,实际上也有可能导致系统死锁。例如在内核态持有锁的时候被中断,然后发生调度。有兴趣的同学可以自己修改上面的代码实验这种情况。

3、why

最后还是回到这个具体的技术问题:为什么interrupt context中不能调用导致睡眠的kernel API?

我的看法是这样的:调度器是每一个OS的必备组件,在编码阶段之前,我们往往要制定下我们的设计概念。对于Linux 调度器,它的目标就是调度一个线程,一个线程就是调度实体(暂不考虑group sched)。中断上下文是不是调度实体呢?当然不是,它没有专属的task struct,内核无从调度。这是调度器设计者的决定,这样的决定让调度器设计起来简洁而美丽。

基于上面的设计概念,中断上下文(hard irq和sofTIrq context)并不参与调度(暂不考虑中断线程化),它们是异步事件的处理机制,目标就是尽快完成处理,返回现场。因此,所有中断上下文的优先级都是高于进程上下文的,也就是说,对于用户进程(无论内核态还是用户态)或者内核线程,除非disable了CPU的本地中断,否则一旦中断发生,它们是没有任何能力阻挡中断上下文抢占当前进程上下文的执行的。

因此,Linux kernel的设计者制定了规则:

1、中断上下文不是调度实体

2、中断上下文的优先级高于进程上下文

而在中断上下文中调度毫无疑问会打破规则,因此不能在硬中断、软中断环境中调用阻塞函数。不过,在linux调度器的具体实现的时候,检测到了在中断上下文中调度schedule函数也并没有强制linux进入panic,可能是linux的开发者认为一个好的内核调度器无论如何也尽自己最大的努力让系统运行下去吧。但是,在厂商自己提供的内核中,往往修改调度器行为,在中断上下文中检测到调度就直接panic了,对于内核开发者而言,这样更好,可以尽早的发现问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/380521.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

cmp指令

功能:比较 格式: CMP destination,sourceCMP 指令比较整数。字符代码也是整数,因此可以用 CMP 指令。 如果比较的是两个无符号数,则零标志位和进位标志位表示的两个操作数之间的关系 如果比较的是两个有符号数,则符…

游戏后的迷茫

9月份花了很多时间耐下心的玩了一个游戏——三国志11,这个是我继三国5以后耐下心玩得最多的一个游戏了,也是2年来耐下心玩得最多的游戏。现在是不是真的太浮躁了。连玩游戏都耐不下心。每天的泡论坛,看电影,下载,刻录的…

两个人 三声叹 一钵泪

寂寞的人,流下的泪珠是单数的 转载于:https://www.cnblogs.com/aque1984/archive/2006/10/02/520282.html

散列碰撞_散列中的碰撞和碰撞解决技术

散列碰撞Prerequisite: Hashing data structure 先决条件: 哈希数据结构 碰撞 (Collisions) Hash functions are there to map different keys to unique locations (index in the hash table), and any hash function which is able to do so is known as the per…

JAVA JDK环境渲染

①(随便在哪个盘里都行)创建一个文件夹名称:Java; ②在文件夹Java下创建一个子文件夹 名称:jdk; ③在子文件夹jdk下再创建一个子文件夹 名称jre; (文件夹名称随便,我这…

使用C和汇编实现一个加法操作

在C/C嵌入汇编指令格式: __asm{;汇编指令 }代码: #include "stdio.h"int main() {int a1;int b2;int c0;__asm{mov eax,amov ebx,badd eax,ebxmov c,eax}printf("ab%d\n",c);return 0; }结果:

FPU数据寄存器

FPU有8个独立的、可寻址的80位数据寄存器R0-R7,如下图所示,这些寄存器合称为寄存器栈。FPU 状态字中名为 TOP 的一个 3 位字段给出了当前处于栈顶的寄存器编号。例如,在下图中,TOP 等于二进制数 011,这表示现在栈顶为 …

C#多线程学习笔记(三)

刚刚把前两天的笔记整理完了,发现做笔记可以加深印象。要坚持做下去,可以学到一些细节的东西。a.今天学到一个非常试用的lock语法:lock(expression) statement_block expression代表你希望跟踪的对象,通常是对象引用。一般地,如果…

C和汇编混合编程----printf

今天终于用c和汇编成功调试出第一个程序了,程序很简单,我太菜了,花了几天的时间,才调试好,来记录一下,以防忘记了 先上程序: #include "stdio.h" int main() {char *str"begin\…

linux清理缓存cache,Linux清理cache缓存

当cache缓存占用太大,服务起不来,需要查看清理缓存查看缓存:free -m输入运行下面一行:echo 3 > /proc/sys/vm/drop_caches# 释放缓存区内存的方法1)清理pagecache(页面缓存)# echo 1 > /proc/sys/vm/drop_caches 或者 # sys…

c和汇编混合编程----shellcode----弹出计算器

先用c和汇编混合编程成功弹出计算器(在VC里运行) #include "stdio.h" #include "windows.h"int main(int argc, char* argv[]) {printf("begin\n");HINSTANCE libHandle;char *dll"kernel32.dll";libHandleLoa…

linux系统的层次结构,关于Linux操作系统层次结构分析

本文转自http://www.jb51.net/LINUXjishu/214104.html首先来看一张图(这是Linux操作系统的大致层次结构):最内层是硬件,最外层是用户常用的应用,比如说firefox浏览器,evolution查看邮件,一个计算流体模型等等。硬件是物…

shellcode---c和汇编混合编程---弹出cmd

首先用C/C语言实现弹出cmd #include "stdio.h" #include "windows.h"int main(int argc, char* argv[]) {printf("begin\n");HINSTANCE libHandle;char *dll"kernel32.dll";libHandleLoadLibrary(dll);WinExec("cmd.exe",S…

C和汇编混合编程---栈平衡

最近在搞C和汇编混合编程,对栈平衡有点小理解,记录一下 当我们调用一个API或者子程序时时,API和子程序可以理解为函数,我们不必在返回的时候平衡栈里面的函数参数,但C语言库函数要我们自己平衡栈数据, 比如…

如何创建Java程序

(1),点击 (2),点击OK (3),File->New->Project… (4),Java Project->Next> (5),Project name随便填(这里以qweqwe为例) 然后Finish (6),鼠标右击qweqwe(随便名称都可以)->New->Class (7),同理,…

c语言数据转移,重温C语言(2)之数据

一 数据类型数据就是代表某些信息的数字和字符。按计算机的储存方式可分为两大基本类型:整数类型和浮点数类型。1 关键字初始C90C99intsigned_Boollongvoid_Complexshort_Imaginaryunsignedcharfloatdouble2 存储单元首先得记住,计算机内部都是以二进制进…

对esp和ebp分析来了解函数的调用过程

esp:扩展栈指针寄存器,是指针寄存器的一种,用于存放函数栈顶指针(栈顶指针) ebp:扩展基址指针寄存器,也被称为帧指针寄存器,用于存放函数栈底指针(栈底指针)。…

仿射变换 c语言,c语言数字图像处理(三):仿射变换

仿射变换及坐标变换公式几何变换改进图像中像素间的空间关系。这些变换通常称为橡皮模变换,因为它们可看成是在一块橡皮模上印刷一幅图像,然后根据预定的一组规则拉伸该薄膜。在数字图像处理中,几何变换由两个基本操作组成:(1)坐标…

C和汇编混合编程----实现浮点数的加减乘除

加法 C程序: #include "stdio.h"int main() {float a1.0;float b2.34;float c;cab;printf("c%f",c);return 0; }反汇编之后,实现加法的关键程序 5: float a1.0; 00401028 C7 45 FC 00 00 80 3F mov dword ptr [eb…