网易云课堂学习
1、intel x86 CPU有四种不同的执行级别0-3,linux只使用了其中的0级和3级分贝来表示内核态和用户态。
2、一般来说在linux中,地址空间是一个显著的标志:0xc0000000以上的地址空间只能在内核态下访问,0x00000000-0xbfffffff的地址空间在两种状态下都可访问。(地址空间指逻辑地址不是物理地址)。 3、系统调用的三层皮:xyz(API)、system_call(中断向量)、sys_xyz(不同种类的服务程序)。 4、Libc库定义个一些API引用了封装例程(wrapper routine,唯一的目的就是发布系统调用,程序员在写代码的时候不需要用汇编指令来触发一个系统调用,而是直接触发一个函数就能进行系统调用了。) 5、system_call是linux中所有系统调用的入口点,每个系统调用至少有一个参数,即由eax传递的系统调用号。实验部分
本次实验内容是使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用。我选的是4号系统调用write。在屏幕上打印输出“hello world!”,对应的API就是printf。
用API实现,创建hello.c文件,代码如下:#include#include int main(){ char* msg = "Hello World"; printf("%s", msg); return 0;}
用汇编代码实现,创建hello-asm.c文件,代码如下:
#include#include #include #include int main(){ char* msg = "hello world!"; int len = 12; int result = 0; asm volatile ( "movl %2, %%edx;\n\r" /*传入参数:要显示的字符串长度*/ "movl %1, %%ecx;\n\r" /*传入参赛:文件描述符(stdout)*/ "movl $1, %%ebx;\n\r" /*传入参数:要显示的字符串*/ "movl $4, %%eax;\n\r" /*系统调用号:4 sys_write*/ "int $0x80" /*触发系统调用中断*/ :"=m"(result) /*输出部分*/ :"m"(msg),"r"(len) /*输入部分:绑定字符串和字符串长度变量*/ :"%eax"); return 0;}
要在 "asm" 内使用寄存器 %eax,%eax 的前面应该再加一个 %,换句话说就是 %%eax,因为 "asm" 使用 %0、%1 等来标识变量。任何带有一个 % 的数都看作是输入/输出操作数,而不认为是寄存器。
在汇编中用 %序号 来代表这些输入/输出操作数, 序号从 0 开始。为了与操作数区分开来, 寄存器用两个%引出,如:%%eax。 $表示当前位置。运行结果如图:
linux内核设计与实现
中断和中断处理
为了提高CPU和外围硬件(硬盘,键盘,鼠标等等)之间协同工作的性能,引入了中断的机制。
没有中断的话,CPU和外围设备之间协同工作可能只有轮询这个方法。 在接收到来自外围硬件(相对于中央处理器和内存)的异步信号,或来自软件的同步信号之后,处理器将会进行相应的硬件/软件处理。发出这样的信号称为进行中断请求(interrupt request,IRQ)。中断的类型
在PC机系统中,根据中断源的不同,中断常分为两大类:硬件中断和软件中断。
硬件中断也称为外部中断,它又可以分为两种:可屏蔽中断(INTR)和非屏蔽中断NMI。 中断有优先级之分,中断优先级指中断的响应级别。 软件中断优先级最高,非屏蔽中断次之,可屏蔽中断优先级最低。 我们经常可以看到IRQ和INT的缩写。IRQ是主板提供的硬件中断端口,一般有8或16个;INT则是操作系统提供的中断处理程序的入口标记,一般有256个。中断处理函数
- irg 表示要分配的中断号
- handler 一个指针,指向实际的中断处理程序
- flags 标志位,表示此中断的具有特性
- name 是与中断相关的设备的ASCII文本表示
- dev 用于共享中断线,多个中断程序共享一个中断线时(共用一个中断号),依靠dev来区别各个中断程序
- 返回值:执行成功返回0;执行失败返回非0
中断控制方法表
函数 | 说明 |
---|---|
local_irq_disable() | 禁止本地中断传递 |
local_irq_enable() | 激活本地中断传递 |
local_irq_save() | 保存本地中断传递的当前状态,然后禁止本地中断传递 |
local_irq_restore() | 恢复本地中断传递到给定的状态 |
disable_irq() | 禁止给定中断线,并确保该函数返回之前在该中断线上没有处理程序在运行 |
disable_irq_nosync() | 禁止给定中断线 |
enable_irq() | 激活给定中断线 |
irqs_disabled() | 如果本地中断传递被禁止,则返回非0;否则返回0 |
in_interrupt() | 如果在中断上下文中,则返回非0;如果在进程上下文中,则返回0 |
in_irq() | 如果当前正在执行中断处理程序,则返回非0;否则返回0 |
下半部和推后执行的工作
上半部的功能是"登记中断",当一个中断发生时,它进行相应地硬件读写后就把中断例程的下半部挂到该设备的下半部执行队列中去。因此,上半部执行的速度就会很快,可以服务更多的中断请求。但是,仅有"登记中断"是远远不够的,因为中断的事件可能很复杂。因此,Linux引入了一个下半部,来完成中断事件的绝大多数使命。下半部和上半部最大的不同是下半部是可中断的,而上半部是不可中断的,下半部几乎做了中断处理程序所有的事情,而且可以被新的中断打断!下半部则相对来说并不是非常紧急的,通常还是比较耗时的,因此由系统自行安排运行时机,不在中断服务上下文中执行。
上半部和下半部的划分
- 如果一个任务对时间十分敏感,将其放在上半部
- 如果一个任务和硬件有关,将其放在上半部
- 如果一个任务要保证不被其他中断打断,将其放在上半部
- 其他所有任务,考虑放在下半部
中断下半部实现的机制主要有三种:软中断、tasklet和工作队列。
tasklet
tasklet由tasklet_struct结构表示,它在<linux/interrupt.h>中定义为:
struct tasklet_struct{ struct tasklet_struct *next;//链表中下一个tasklet unsigned long state; //tasklet的状态 atomic_t count; //引用计数器 void (*func)(unsigned long);//tasklet处理函数 unsigned long data; //给tasklet处理函数的参数}
工作队列
工作队列子系统是一个用于创建内核线程的接口。工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行。这样,通过工作队列执行的代码能占尽进程上下文的所有优势。最重要的就是工作队列允许被重新调度甚至是睡眠。
- 工作队列的使用
- 创建推后的工作 DECLARE_WORK(name,void (func) (void ),void *date
- 工作队列处理函数 void work_handler(void *date)
- 对工作进行调度 schedule_work(&work)
- 刷新指定工作队列 void flush_scheduled_work(void) 三种机制的差别与联系。 软中断: 1、软中断是在编译期间静态分配的。 2、最多可以有32个软中断。 3、软中断不会抢占另外一个软中断,唯一可以抢占软中断的是中断处理程序。 4、可以并发运行在多个CPU上(即使同一类型的也可以)。所以软中断必须设计为可重入的函数(允许多个CPU同时操作),因此也需要使用自旋锁来保护其数据结构。 5、目前只有两个子系直接使用软中断:网络和SCSI。 6、执行时间有:从硬件中断代码返回时、在ksoftirqd内核线程中和某些显示检查并执行软中断的代码中。 tasklet: 1、tasklet是使用两类软中断实现的:HI_SOFTIRQ和TASKLET_SOFTIRQ。 2、可以动态增加减少,没有数量限制。 3、同一类tasklet不能并发执行。 4、不同类型可以并发执行。 5、大部分情况使用tasklet。 工作队列: 1、由内核线程去执行,换句话说总在进程上下文执行。 2、可以睡眠,阻塞。