Linux 系统对中断的处理

进程、线程、中断的核心 — 栈

中断,究竟中断谁呢?
中断当前正在运行的进程、线程。
进程、线程是什么?内核如何切换进程、线程、中断?
要理解这些概念,必须理解栈的作用。

ARM 处理程序运行的过程

ARM 芯片属于精简指令集计算机 (RISC: Reduced Instruction Set Computing),它所用的指令比较简单,有如下特点:

  1. 对内存只有读、写指令
  2. 对于数据的运算是在CPU 内核实现
  3. 使用RISC 指令的CPU 复杂度小一点,易于设计
    比如对于 a = a + b 这样的算式,需要经过下面4个步骤才可以实现:
    在这里插入图片描述

细看这几个步骤,有些疑问

  1. 读a,那么a的值读出来后保存在CPU 里面哪里?
  2. 读b,那么b的值读出来后保存在CPU 里面哪里?
  3. a+b 的结果又保存在哪里?
    我们需要深入ARM 处理器的内部。简单概括如下,我们忽略各种CPU 模式(系统模式、用户模式等等)

在这里插入图片描述

CPU运行时,先去取得指令,再执行指令,一共四步:

  1. 把内存 a 的值读入 CPU 寄存器 R0
  2. 把内存 b 的值读入 CPU 寄存器 R1
  3. 把 R0、 R1 累加,存入 R0
  4. 把 R0 的值写入内存 a

程序被中断时,怎么保存现场

从上图可知, CPU内部的寄存器很重要,如果要暂停一个程序,中断一个程序,就需要把这些寄存器的值保存下来:这就称为保存现场。

保存在哪里?内存,这块内存就称之为栈。

程序要继续执行,就先从栈中恢复那些 CPU 内部寄存器的值。

这个场景并不局限于中断,下图可以概括程序 A、 B 的切换过程,其他情况是类似的:
在这里插入图片描述

函数调用:

  1. 在函数 A 里调用函数 B,实际就是中断函数 A 的执行;
  2. 那么需要把函数 A 调用 B 之前瞬间的 CPU 寄存器的值,保存到栈里;
  3. 再去执行函数 B;
  4. 函数 B 返回之后,就从栈中恢复函数 A 对应的 CPU 寄存器值,继续执行。

中断处理

  1. 进程 A 正在执行,这时候发生了中断;
  2. CPU 强制跳到中断异常向量地址去执行;
  3. 这时就需要保存进程 A 被中断瞬间的 CPU 寄存器值;
  4. 可以保存在进程 A 的内核态栈,也可以保存在进程 A 的内核结构体中;
  5. 中断处理完毕,要继续运行进程 A 之前,恢复这些值。

进程切换

  1. 在所谓的多任务操作系统中,我们以为多个程序是同时运行的;
  2. 如果我们能感知微秒、纳秒级的事件,可以发现操作系统时让这些程序依次执行一小段时间,进程 A 的时间用完了,就切换到进程 B;

怎么切换

  1. 切换过程是发生在内核态里的,跟中断的处理类似;
  2. 进程 A 的被切换瞬间的 CPU 寄存器值保存在某个地方;
  3. 恢复进程 B 之前保存的 CPU 寄存器值,这样就可以运行进程 B 了。

所以,在中断处理的过程中,伴存着进程的保存现场、恢复现场。进程的调度也是使用栈来保存、恢复现场:
在这里插入图片描述

进程、线程的概念

假设我们写一个音乐播放器,在播放音乐的同时会根据按键选择下一首歌。把事情简化为2件事:发送音频数据、读取按键。那可以这样写程序:

int main(int argc, char **argv)
{
    int key;
    while (1)
    {
        key = read_key();
        if (key != -1)
        {
            switch (key)
            {
                case NEXT:
                    select_next_music(); // 在 GUI 选中下一首歌
                    break;
            }
        }
        else
        {
            send_music();
        }
    }
    return 0;
}

这个程序只有一条主线,读按键、播放音乐都是顺序执行。无论按键是否被按下, read_key 函数必须马上返回,否则会使得后续的 send_music 受到阻滞导致音乐播放不流畅。

读取按键、播放音乐能否分为两个程序进行?可以,但是开销太大:读按键的程序,要把按键通知播放音乐的程序,进程间通信的效率没那么高。

这时可以用多线程之编程,读取按键是一个线程,播放音乐是另一个线程,它们之间可以通过全局变量传递数据,示意代码如下:

int g_key;
void key_thread_fn()
{
    while (1)
    {
        g_key = read_key();
        if (g_key != -1)
        {
            switch (g_key)
            {
                case NEXT:
                    select_next_music(); // 在 GUI 选中下一首歌
                    break;
            }
        }
    }
}
 
void music_fn()
{
    while (1)
    {
        if (g_key == STOP)
        {
            stop_music();
        }
        else
        {
            send_music();
        }
    }
}
 
int main(int argc, char **argv)
{
    int key;
    create_thread(key_thread_fn);
 
    create_thread(music_fn);
    while (1)
    {
        sleep(10);
    }
    return 0;
}

这样,按键的读取及 GUI 显示、音乐的播放,可以分开来,不必混杂在一起。

按键线程可以使用阻塞方式读取按键,无按键时是休眠的,这可以节省CPU 资源。

音乐线程专注于音乐的播放和控制,不用理会按键的具体读取工作, 并且这 2 个线程通过全局变量 g_key 传递数据,高效而简单。

在 Linux 中:资源分配的单位是进程,调度的单位是线程, 也就是说,在一个进程里,可能有多个线程,这些线程共用打开的文件句柄、全局变量等等。而这些线程,之间是互相独立的,“同时运行”,也就是说:每一个线程,都有自己的栈。如下图示:

在这里插入图片描述

  • 28
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值