这周的作业同样分为两部分,第一部分的学习MOOC第四节[扒开系统调用的三层皮],并结合实验楼的实验四深入学习。第二部分阅读学习教材「Linux内核设计与实现 (Linux Kernel Development)」第教材第7,8章。
首先从第一部分开始,系统调用的三层皮分别是xyz(API) system_call(中断向量对应的中断服务程序) sys_xyz(服务程序)。为了更深入的了解三层“皮”,我们做了如下的实验。
1.获取系统时间
2.代码完成后我们进行编译并运行
3.C语言代码嵌入汇编代码格式如下
''' _asm_(
汇编语句模板:
输出部分:
输入部分:
破坏描述部分);
即格式为asm("statements":output_...); '''
4.接下来直接用例子来说明
对于里面出现的双%,第一个%代表转译。
5.用实际例子来看懂输出结果
#实验的结果应该是0和1,但在实际操作过程中遇到了编译错误,问题尚未解决,后期会会跟进问题进行处理。
后期跟紧:已正确输出:
缺少代码:movl %%eax,%1;\n\t
6.用汇编方式触发系统调用获取当前时间
代码中d是16进制的13,使用eax传递系统调用号,这里time是13
7.编译并运行上述代码
学习这段代码,我们可以清楚的知道用户态向内核态做了什么,它传递了一个系统调用号,参数NUll就是给ebx传递了一个空参。
实验中,我们首先了解了用户态,内核态,已经了解了系统调用的三层“皮”。学习代码中,我们知道了用户态和内核态传递参数的方式,以及他们之间的联系。接下来就是对课本章节的阅读情况,在第七章,我们了解了内核为处理中断而提供的中断处理程序机制,但中断处理程序本身也有局限,局限包括:
1)中断处理程序以异步方式执行,并且它右可能会打断其他重要代码的执行。
2)如果当前有一个中断处理程序正在执行,最好的情况,同级的其他中断会北屏蔽,最坏的情况,当前处理机上所有的其他中断会被屏蔽。
3)由于中断处理程序往往需要对硬件进行操作,所以他们通常有很高的时限要求。
4)中断处理程序不能在进程上下文中运行,所以他们不能阻塞。
中断和下半部
中断机制就是在硬件需要的时候向内核发出信号。中断本质上是一种由硬件向处理器发出的特殊的电信号,不考虑与处理器的时钟同步。不同的设备对应不同的中断,所以每个中断都通过一个唯一的数字标,称为中断请求(IRQ)线。
异常称为同步中断,是由处理器本身产生的中断;异步中断由其他硬件产生的。
中断处理程序
内核执行的用来响应中断的函数叫中断处理程序或者中断服务例程,是设备驱动程序的一部分。每个中断都有相应的中断处理程序,它要负责通知硬件设备中断已被接收。
驱动程序通过request_irq()注册中断处理程序并激活给定的中断线:
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
irq表示要分配的中断号;handler指针指向处理该中断的中断处理程序;flags有几个重要的标志,IRQF_DISABLED表示处理该中断处理程序本身旗舰,禁止所有其他中断,IRQF_SAMPLE_RANDOM表示该设备中断对内核熵池有贡献,IRQF_TIMER是为系统定时器的中断处理准备,IRQF_SHARED表明可在多个中断处理程序之间共享中断线;name是与终端相关的设备的ASCⅡ文本表示;dev用于共享中断线,提供唯一标志信息以便从共享中断线的多个中断处理程序中删除指定那个。若函数返回0则表示成功,返回-EBUSY表示中断线已经在使用。
卸载驱动程序要注销相应的中断处理程序,并释放中断线,要调用函数:
void free_irq(unsigned int irq, void *dev)
对于共享的中断处理程序:
•request_irq()的参数flags必须设置IRQF_SHARED标志;
•对每个注册的中断处理程序,dev参数必须唯一;
•中断处理程序必须能区分它的设备是否真的产生了中断。
内核收到中断后,会依次调用该中断线上注册的每一个处理程序,因此处理程序必须知道它是否应该为这个中断负责,与它相关的设备没有产生中断,那处理程序就必须立刻退出。
中断控制
中断控制的原因归根结底是需要提供同步。通过禁止中断确保某个中断处理程序不会抢占当前代码或内核。锁提供保护机制,防止来自其他处理器的并发访问;禁止中断提供保护机制,防止来自其他处理程序的并发访问。
下半部和退后执行的工作
鉴于中断处理程序运行速度和完成工作量之间的矛盾,把中断处理分为两部分:上半部和下半部。
中断处理程序是上半部,接收到中断就立马执行,只做严格的限时工作;能被允许稍后完成的工作会推迟到下半部,在合适的时机,下半部会被开中断执行。
下半部的任务是执行与中断处理密切相关但中断处理本身不执行的工作。一般情况,需要放在上半部的工作:
•对时间非常敏感;
•和硬件相关;
•要保证不被其他中断打断。
下半部实现机制有BH、任务队列、软终端、tasklet和工作队列。其中BH和任务队列已经去除。软中断在编译期间就进行静态注册,tasklet可以通过代码进行动态注册。
软中断由softirq_action结构表示,定义在<linux/interrupt.h>中。最多可能有32个软中断。中断处理程序返回前会标记软中断,注册的软中断必须在被标记后才会执行,称作触发软中断。而软中断都要在do_softirq()中执行。
tasklet是利用软中断实现的一种下半部机制,由tasklet_struct结构表示:
struct tasklet_struct{
struct tasklet_struct next;//链表中下一个tasklet
unsigned long state; //tasklet的状态
atomic_t count; //引用计数器
void (func)(unsigned long);//tasklet处理函数
unsigned long data; //给tasklet处理函数的参数
}
工作队列子系统是一个用于创建内核线程的接口,通过它创建的进程负责执行有内核其他部分排到队列里的任务,这些内核线程称作工作者线程。缺省的工作者线程叫event/n,n是处理器编号,每个处理器一个线程。工作者线程用workqueue_struct结构表示:
struct workqueue_struct{
struct cpu_workqueue_struct cpu_wq[NR_CPUS];
struct list_head list;
const char *name;
int sinqlethread;
int freezeable;
int rt;
}
该结构内是一个由cpu_workqueue_struct结构组成的数组:
struct cpu_workqueue_struct{
spinlock_t lock;//锁保护这种结构
struct list_head worklist;//工作列表
wait_queue_head_t more_work;
struct work_struct *current_struct;
struct workqueue_struct *wq;//关联工作队列结构
task)t *thread;//关联线程
}
所有工作者线程都是用普通的内核线程实现的,都要执行worker_thread()函数。工作用work_struct结构体表示:
struct work_struct{
atomic_long_t data;
struct list_head entry;
work_func_t func;
}
对于现在常用的三种下半部接口,软中断和tasklet都是在中断上下文中,而工作队列是进程上下文;软中断没有顺序执行保障,tasklet同类型不能同时执行,工作队列也没有顺序执行保障,和进程上下文一样被调度。
所以,整个中断处理流程就被分为了两个部分,或叫两半。第一部分是中断处理程序,内核通过对它的异步执行完成对硬件中断的即时响应。第二部分的任务就是执行与中断处理密切相关但中断处理程序本身不执行的工作。第八章涵盖了用于延迟Linux内核工作的三种机制:软中断、tasklet和工作队列。