修正 并发控制示例.

Signed-off-by: chen.yang <chen.yang@yuzhen-iot.com>
This commit is contained in:
chen.yang 2022-04-21 19:16:33 +08:00
parent e270b9f71f
commit a495dd298e
3 changed files with 124 additions and 11 deletions

View File

@ -0,0 +1,103 @@
# 8.10 中断与时钟
所谓中断是指 CPU 在执行程序的过程中,出现了某些突发事件时 CPU 必须暂停执行当前的程序,转去处理突发事件,处理完毕后 CPU 又返回原程序被中断的位置并继续执行。
根据中断的来源,中断可分为内部中断和外部中断,内部中断的中断源来自 CPU 内部(软件中断指令、溢出、除法错误等,例如,操作系统从用户态切换到内核态需借助 CPU 内部的软件中断),外部中断的中断源来自 CPU 外部,由外设提出请求。
根据是否可以屏蔽中断分为可屏蔽中断与不屏蔽中断NMI可屏蔽中断可以通过屏蔽字被屏蔽屏蔽后该中断不再得到响应而不屏蔽中断不能被屏蔽。
根据中断入口跳转方法的不同,中断分为向量中断和非向量中断。采用向量中断的 CPU 通常为不同的中断分配不同的中断号,当检测到某中断号的中断到来后,就自动跳转到与该中断号对应的地址执行。不同中断号的中断有不同的入口地址。非向量中断的多个中断共享一个入口地址,进入该入口地址后再通过软件判断中断标志来识别具体是哪个中断。也就是说,向量中断由硬件提供中断服务程序入口地址,非向量中断由软件提供中断服务程序入口地址。
## Linux 中断处理程序架构
### 顶半部与底半部
设备的中断会打断内核中进程的正常调度和运行,系统对更高吞吐率的追求势必要求中断服务程序尽可能地短小精悍。但是,这个良好的愿望往往与现实并不吻合。在大多数真实的系统中,当中断到来时,要完成的工作往往并不会是短小的,它可能要进行较大量的耗时处理。
为了在中断执行时间尽可能短和中断处理需完成大量工作之间找到一个平衡点Linux 将中断处理程序分解为两个半 部顶半部top half和底半部bottom half
顶半部完成尽可能少的比较紧急的功能,它往往只是简单地读取寄存器中的中断状态并清除中断标志后 就进行“登记中断”的工作。“登记中断”意味着将底半部处 理程序挂到该设备的底半部执行队列中去。这样,顶半部执行的速度就会很快,可以服务更多的中断请求。
现在,中断处理工作的重心就落在了底半部的头上,它来完成中断事件的绝大多数任务。底半部几乎做了中断处理程序所有的事情,而且可以被新的中断打断,这也是底半部和顶半部的最大不同,因为顶半部往往被设计成不可中断。底半部则相对来说并不是非常紧急的,而且相对比较耗时,不在硬件中断服务程序中执行。
尽管顶半部、底半部的结合能够改善系统的响应能力,但是,僵化地认为 Linux 设备驱动中的中断处理一定要分两个半部则是不对的。如果中断要处理的工作本身很少,则完全可以直接在顶半部全部完成。
### 中断与文件系统
在 Linux 系统中,查看 /proc/interrupts 文件可以获得系统中断的统计信息。
```bash
# cat /proc/interrupts
CPU0
16: 1313862 GPC 55 Level i.MX Timer Tick
18: 292 GPC 26 Level 2020000.serial
19: 0 GPC 98 Level sai
20: 0 GPC 50 Level 2034000.asrc
45: 0 gpio-mxc 19 Edge 2190000.usdhc cd
152: 0 gpio-mxc 24 Edge gpiolib
195: 25383 GPC 120 Level 20b4000.ethernet
196: 0 GPC 121 Level 20b4000.ethernet
197: 0 GPC 80 Level 20bc000.wdog
200: 0 GPC 49 Level imx_thermal
205: 1 GPC 19 Level rtc alarm
211: 0 GPC 2 Level sdma
216: 3 GPC 43 Level 2184000.usb
217: 741 GPC 42 Level 2184200.usb
218: 0 GPC 118 Level 2188000.ethernet
219: 0 GPC 119 Level 2188000.ethernet
220: 0 GPC 22 Level mmc0
221: 30726 GPC 23 Level mmc1
223: 0 GPC 8 Level pxp-dmaengine
224: 0 GPC 18 Level pxp-dmaengine-std
225: 1 GPC 107 Level
226: 0 GPC 27 Level 21e8000.serial
227: 0 GPC 28 Level 21ec000.serial
228: 0 GPC 29 Level 21f0000.serial
229: 0 GPC 46 Level dcp-vmi-irq
230: 0 GPC 47 Level dcp-irq
232: 2 GPC 6 Level imx-rng
IPI0: 0 CPU wakeup interrupts
IPI1: 0 Timer broadcast interrupts
IPI2: 0 Rescheduling interrupts
IPI3: 0 Function call interrupts
IPI4: 0 Single function call interrupts
IPI5: 0 CPU stop interrupts
IPI6: 0 IRQ work interrupts
IPI7: 0 completion interrupts
Err: 0
```
## Linux 中断编程
```cpp
/**
* @param irq 要申请的硬件中断号。
* @param handler 是向系统登记的中断处理函数,是一个回调函数,中断发生时,系统调用这个函数。
* @param irqflags 是中断处理的属性
* SA_INTERRUPT则表示中断处理程序是快速处理程序快速处理程序被调用时屏蔽所有中断慢速处理程序不屏蔽
* SA_SHIRQ表示多个设备共享中断
* @param devname An ascii name for the claiming device. 使用cat /proc/interrupt 可以查看中断程序名字。
* @param dev_id 在中断共享时会用到,一般设置为这个设备的设备结构体或者 NULL。
* 注册共享中断时不能为NULL因为卸载时需要这个做参数避免卸载其它中断服务函数。
* @return
* 0表示成功
* -INVAL表示中断号无效或处理函数指针为 NULL
* -EBUSY表示中断已经被占用且不能共享。
* @brief 申请 IRQ
*/
int request_irq(unsigned int irq,
void (*handler)(int irq, void *dev_id, struct pt_regs *regs),
unsigned long irqflags,
const char *devname,
void *dev_id);
/**
* @param irq 申请时所用的硬件中断号。
* @brief 这个是要卸载的中断 Action下的哪个服务函数
*/
void free_irq(unsigned int irq, void *dev_id);
```
1. 如果是采用非共享方式注册中断,则 request_irq 和 free 的最后一个参数都要为 NULL。
2. 如果采用共享中断方式,所有使用 request_irq 注册的中断时 flags 都要加上 IRQF_SHARED 这个共享参数,表明其实共享中断。
3. 对于共享中断,每一个申请共享的中断,申请和释放时都要给 request_irq 和 free_irq 的最后一个参数 dev 和 id_dev 传递一个指针,将来来中断的时候,将会传递这个指针到每个中断函数中,而中断函数就可以用来区分到底是不是它的中断,是则执行,不是则判断后直接退出中断处理函数即可。同时在 free_irq 时也会使用这个指针,查找这个贡献中断链表上了所有注册的 irq只有在这个指针能对的上的时候才会删除它所在的链表节点如果是最后一个节点还要释放该中断。所在在编写中断处理函数时该指针必须是唯一的通常传的这个指针是该设备结构体的地址这个每个设备不一样所以肯定是唯一的。

View File

@ -356,21 +356,21 @@ static ssize_t demo_read(struct file *filp, char __user *buffer, size_t count, l
// This is a test.
// 分析和获取有效的读长度
if(DEMO_DATA_SIZE<=p) // 要读的偏移位置越界
if(DEMO_DATA_SIZE<=p) { // 要读的偏移位置越界
up(&devp->demo_sem);
return 0; // End of a file
}
if(DEMO_DATA_SIZE<(count+p)) // 要读的字节数太大
count = DEMO_DATA_SIZE-p;
if(copy_to_user((void*)buffer, &devp->demo_text[p], count)) {
up(&devp->demo_sem);
if(copy_to_user((void*)buffer, &devp->demo_text[p], count))
ret = -EFAULT;
}
else
{
*position += count;
up(&devp->demo_sem);
*position += count
ret = count;
}
up(&devp->demo_sem);
return ret;
}
@ -384,21 +384,21 @@ static ssize_t demo_write(struct file *filp, const char __user *buffer, size_t c
p = *position;
// This is a test.
// 分析和获取有效的写长度
if(DEMO_DATA_SIZE<=p) // 要读的偏移位置越界
if(DEMO_DATA_SIZE<=p) { // 要读的偏移位置越界
up(&devp->demo_sem);
return 0; // End of a file
}
if(DEMO_DATA_SIZE<(count+p)) // 要读的字节数太大
count = DEMO_DATA_SIZE-p;
if(copy_from_user(&devp->demo_text[p], (void*)buffer, count)) {
up(&devp->demo_sem);
if(copy_from_user(&devp->demo_text[p], (void*)buffer, count))
ret = -EFAULT;
}
else {
*position += count;
up(&devp->demo_sem);
ret = count;
}
up(&devp->demo_sem);
return ret;
}

View File

@ -0,0 +1,10 @@
# 8.9 设备驱动中的异步通知与异步 IO
阻塞与非阻塞访问、poll() 函数提供了较好的解决设备访问的机制,但是如果有了异步通知整套机制就更加完整了。
异步通知的意思是:一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,这一点非常类似于硬件上“中断”的概念,比较准确的称谓是“信号驱动的异步 I/O”。信号是在软件层次上对中断机制的一种模拟在原理上一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的一个进程不必通过任何操作来等待信号的到达事实上进程也不知道信号到底什么时候到达。
Linux 下异步 IO 机制主要有:
* Linux 信号
* AIO