Linux 开关中断系列函数探究

中断与锁

中断是Linux响应异步事件的一种方式,区别于陷阱、异常指令和出错等在指令执行完后产生的异常,它往往发生在处理器的外部,通常由外设触发,可以在指令执行完成的瞬间产生,也可能在指令执行的过程中产生,因而不可预测。从外设的角度看,中断是设备请求CPU响应的方式;而从调度的视角来看,中断是驱动系统心跳和调度的手段。而为了实现同步互斥机制,内核还基于处理器提供的原子指令实现了锁的机制,在进入临界段时需要上锁,而在退出时需要解锁,这样就保证了其他处理器不会也进入临界区。通过关本地中断,保重了当前运行的处理器不会再去执行其他内核线程,而是沿着当前上下文继续执行下去;而在临界段之前上锁,则防止其他处理器也执行即将要被执行的代码。那么在什么时候需要上锁,什么时候要关中断,什么时候二者都需要呢?下文结合Linux内核已经提供的几组相关函数进行了分析。

三组相关函数

Spin_lock 与spin_unlock

Spin_lock() 与 spin_unlock(),顾名思义,是内核提供的分别上锁和解锁的函数,值得一提的是这组函数在单处理器系统中,都退化成空操作,因为单处理器系统上只有一个处理器,所以任何当前正在执行的临界段,不可能被其他处理器再次进入。

Spin_lock_irq与spin_unlock_irq

与上面的spin_lock和spin_unlock这组函数的不同,spin_lock_irq()在执行的过程中,会先关本地中断,然后上锁;spin_unlock_irq()相反,会先解锁,然后打开本地中断。注意这里两个函数是互逆对称的操作过程。读者可以思考一下,spin_lock_irq()先上锁后关中断是否可以?答案是不可以,否则对应的spin_unlock_irq()执行的顺序就是开中断先然后解锁,在解锁和开中断的瞬间,如果有同一中断再被当前CPU响应,它就会再次调用spin_lock_irq(),里面先执行spin_lock(),可是这时该锁已经被它上次申请,并且没有来得及释放,这样它就会译制在这里死等。虽然它还可以响应其他类型的中端请求,但是该CPU无法再正确处理当前的中断类型。用户将会看到对应的设备好像停止了工作。

Spin_lock_irqsave 与spin_unlock_irqrestore

Spin_lock_irqsave()与spin_unlock_irqsrestore()除了分别包含spin_lock_irq()和spin_unlock_irq()所执行的所有操作之外,还会分别保存中断使能与否的状态、恢复中断使能的状态。因为有的中断可以以嵌套的方式执行,当前的中端可能正是嵌套进入的,这种情况下在unlockspin 锁后,内核不能贸然使能本地中断,而应恢复之前的中断使能状态,否则相当于在嵌套中断返回之前提前执行了本地中断使能的工作。这组函数具体的伪码如下:

Spin_lock_irqrestore()

{

…………

Save_local_irq_state(&flag);

Local_irq_disable()

Spin_lock();

…………

}

Spin_unlock_irqrestore()

{

Spin_unlock()

Restore_local_irq_state(flag);

Enable_local_irq()

}

使用场景对比

通过上面描述的对比可以看到,三组函数既有共同的地方,那就是上锁和解锁,也有不同的地方,那就是关于中断是否关闭、是否保存。所实现功能的不同,也就决定了三组函数应用场景的不同,下表给出了对比和说明:

总结

内核提供了看似差不多实则有不小差别的中断、自旋锁相关的函数,我们需要根据不同的应用场景和需求谨慎选择,否则很容易出现意想不到的问题。

时间: 2024-10-19 08:34:11

Linux 开关中断系列函数探究的相关文章

Linux中backtrace()系列函数的应用实例

一.引言 backtrace()系列函数可用来输出代码出错时的函数调用关系. A backtrace is the series of currently active function calls for the program. #include <execinfo.h> int backtrace(void **buffer, int size); char **backtrace_symbols(void *const *buffer, int size); void backtrac

linux内核cdev_init系列函数(字符设备的注册)

内核中每个字符设备都对应一个 cdev 结构的变量,下面是它的定义: linux-2.6.22/include/linux/cdev.h struct cdev {    struct kobject kobj;          // 每个 cdev 都是一个 kobject    struct module *owner;       // 指向实现驱动的模块    const struct file_operations *ops;   // 操纵这个字符设备文件的方法    struct

linux timerfd系列函数总结

网上关于timerfd的文章很多,在这儿归纳总结一下方便以后使用,顺便贴出一个timerfd配合epoll使用的简单例子 一.timerfd系列函数 timerfd是Linux为用户程序提供的一个定时器接口.这个接口基于文件描述符,通过文件描述符的可读事件进行超时通知,因此可以配合select/poll/epoll等使用. 下面对timerfd系列函数先做一个简单的介绍: (1)timerfd_create()函数 #include <sys/timerfd.h> int timerfd_cr

linux tricks 之VA系列函数.

VA函数(variable argument function),参数个数可变函数,又称可变参数函数.C/C++编程中,系统提供给编程人员的va函数很少.*printf()/*scanf()系列函数,用于输入输出时格式化字符串:exec*()系列函数,用于在程序中执行外部文件(main(int argc, char* argv[]算不算呢,与其说main()也是一个可变参数函数,倒不如说它是exec*()经过封装后的具备特殊功能和意义的函数,至少在原理这一级上有很多相似之处).由于参数个数的不确

Linux中exec()执行文件系列函数的使用说明

函数原型: 描述:    exec()系列函数使用新的进程映像替换当前进程映像.    工作方式没有什么差别, 只是参数传递的方式不同罢了. 说明:    1. 这6个函数可分为两大类: execl()系列 和 execv()系列.    2. `l' 是指把所有传递给程序的参数依次列(list)出来.        `v' 是指把所有参数放到容器(数组, vector)中再一次性传入.         不论是list出来还是vector them all, 参数的最后一个都必须为空指针((ch

clone函数探究

我们都知道linux中创建新进程是系统调用fork,但实际上fork是clone功能的一部分,clone和fork的主要区别是传递了几个参数.clone隶属于libc,它的意义就是实现线程. 看一下clone函数: int clone(int (*fn)(void * arg), void *stack, int flags, void * arg); fn就是即将创建的线程要执行的函数,stack是线程使用的堆栈. 再来看一下clone和pthread_create的区别:linux中的pth

exec系列函数(execl,execlp,execle,execv,execvp)使用

本节目标: exec替换进程映像 exec关联函数组(execl.execlp.execle.execv.execvp) 一,exec替换进程映像 在进程的创建上Unix采用了一个独特的方法,它将进程创建与加载一个新进程映象分离.这样的好处是有更多的余地对两种操作进行管理. 当我们创建了一个进程之后,通常将子进程替换成新的进程映象,这可以用exec系列的函数来进行.当然,exec系列的函数也可以将当前进程替换掉. 例如:在shell命令行执行ps命令,实际上是shell进程调用fork复制一个新

Linux进程的创建函数fork()及其fork内核实现解析

进程的创建之fork() Linux系统下,进程可以调用fork函数来创建新的进程.调用进程为父进程,被创建的进程为子进程. fork函数的接口定义如下: #include <unistd.h> pid_t fork(void); 与普通函数不同,fork函数会返回两次.一般说来,创建两个完全相同的进程并没有太多的价值.大部分情况下,父子进程会执行不同的代码分支.fork函数的返回值就成了区分父子进程的关键.fork函数向子进程返回0,并将子进程的进程ID返给父进程.当然了,如果fork失败,

linux下的信号处理函数总结

1.信号处理函数 相关函数原型如下: #include <signal.h> sighandler_t signal(int signum, sighandler_t handler); 第一参数是信号 第二个参数是信号处理器:             1.可以是SIG_DFL,信号的默认动作             2. 可以是SIG_IGN,忽略该信号             3. 一个带有一个整型参数的处理函数. #include <signal.h> int sigacti