当前位置:文档之家› 深入理解Linux内核中断

深入理解Linux内核中断

深入理解Linux内核中断
深入理解Linux内核中断

深入理解Linux内核中断

一直认为,理解中断是理解内核的开始。中断已经远远超过仅仅为外围设备服务的范畴,它是现代体系结构的重要组成部分。

1、基本输入输出方式

现代体系结构的基本输入输出方式有三种:

(1)程序查询:

CPU周期性询问外部设备是否准备就绪。该方式的明显的缺点就是浪费CPU资源,效率低下。

但是,不要轻易的就认为该方式是一种不好的方式,通常效率低下是由于CPU在大部分时间没事可做造成的,这种轮询方式自有应用它的地方。例如,在网络驱动中,通常接口(Interface)每接收一个报文,就发出一个中断。而对于高速网络,每秒就能接收几千个报文,在这样的负载下,系统性能会受到极大的损害。

为了提高系统性能,内核开发者已经为网络子系统开发了一种可选的基于查询的接口NAPI(代表new API)。当系统拥有一个高流量的高速接口时,系统通常会收集足够多的报文,而不是马上中断CPU。

(2)中断方式

这是现代CPU最常用的与外围设备通信方式。相对于轮询,该方式不用浪费稀缺的CPU资源,所以高效而灵活。中断处理方式的缺点是每传送一个字符都要进行中断,启动中断控制器,还要保留和

恢复现场以便能继续原程序的执行,花费的工作量很大,这样如果需要大量数据交换,系统的性能会很低。

(3)DMA方式

通常用于高速设备,设备请求直接访问内存,不用CPU干涉。但是这种方式需要DMA控制器,增加了硬件成本。在进行DMA数据传送之前,DMA控制器会向CPU申请总线控制权,CPU如果允许,则将控制权交出,因此,在数据交换时,总线控制权由DMA控制器掌握,在传输结束后,DMA控制器将总线控制权交还给CPU。

2、中断概述

2.1、中断向量

X86支持256个中断向量,依次编号为0~255。它们分为两类:

(1)异常,由CPU内部引起的,所以也叫同步中断,不能被CPU 屏蔽;它又分为Faults(可更正异常,恢复后重新执行),Traps(返回后执行发生trap指令的后一条指令)和Aborts(无法恢复,系统只能停机);

(2)中断,由外部设备引起的。它又分为可屏蔽中断(INTR)和非可屏蔽中断(NMI)。

Linux对256个中断向量分配如下:

(1)0~31为异常和非屏蔽中断,它实际上被Intel保留。

(2)32~47为可屏蔽中断。

(3)余下的48~255用来标识软中断;Linux只用了其中一个,即128(0x80),用来实现系统调用。当用户程序执行一条int 0x80时,就会陷入内核态,并执行内核函数system_call(),该函数与具体的架构相关。

2.2、可屏蔽中断

X86通过两个级连的8259A中断控制器芯片来管理15个外部中断源,如图所示:

外部设备要使用中断线,首先要申请中断号(IRQ),每条中断线的中断号IRQn对应的中断向量为n+32,IRQ和向量之间的映射可以通过中断控制器商端口来修改。X86下8259A的初始化工作及IRQ与向量的映射是在函数init_8259A()(位于

arch/i386/kernel/i8259.c)完成的。

CPU通过INTR引脚来接收8259A发出的中断请求,而且CPU

可以通过清除EFLAG的中断标志位(IF)来屏蔽外部中断。当IF=0时,禁止任何外部I/O请求,即关中断(对应指令cli)。另外,中断控制器有一个8位的中断屏蔽寄存器(IMR),每位对应8259A中的一条中断线,如果要禁用某条中断线,相应的位置1即可,要启用,则置0。

IF标志位可以使用指令STI和CLI来设置或清除。并且只有当程序的CPL<=IOPL时才可执行这两条指令,否则将引起一般保护性异常(通常来说,in,ins,out,outs,cli,sti只有在CPL<=IOPL 时才能执行,这些指令称为I/O敏感指令)。

以下一些操作也会影响IF标志位:

(1)PUSHF指令将EFLAGS内容存入堆栈,且可以在那里修改。POPF可将已经修改过的内容写入EFLAGS寄存器。

(2)任务切换和IRET指令会加载EFLAGS寄存器。因此,可修改IF标志。

(3)通过中断门处理一个中断时,IF标志位被自动清除,从而禁止可尽屏蔽中断。但是,陷阱门不会复位IF。

2.3、异常及非屏蔽中断

异常就是CPU内部出现的中断,也就是说,在CPU执行特定指令时出现的非法情况。非屏蔽中断就是计算机内部硬件出错时引起的异常情况。从上图可以看出,二者与外部I/O接口没有任何关系。Intel把非屏蔽中断作为异常的一种来处理,因此,后面所提到的异

常也包括了非屏蔽中断。在CPU执行一个异常处理程序时,就不再为其他异常或可屏蔽中断请求服务,也就是说,当某个异常被响应后,CPU清除EFLAG的中IF位,禁止任何可屏蔽中断(IF不能禁止异常和非可屏蔽中断)。但如果又有异常产生,则由CPU锁存(CPU 具有缓冲异常的能力),待这个异常处理完后,才响应被锁存的异常。我们这里讨论的异常中断向量在0~31之间,不包括系统调用(中断向量为0x80)。

2.4、中断描述符表

2.4.1、中断描述符

在实地址模式中,CPU把内存中从0开始的1K字节作为一个中断向量表。表中的每个表项占四个字节,由两个字节的段地址和两个字节的偏移量组成,这样构成的地址便是相应中断处理程序的入口地址。但是,在保护模式下,由四字节的表项构成的中断向量表显然满足不了要求。这是因为,除了两个字节的段描述符,偏移量必用四字节来表示;要有反映模式切换的信息。因此,在保护模式下,中断向量表中的表项由8个字节组成,中断向量表也改叫做中断描述符表IDT(Interrupt Descriptor Table)。其中的每个表项叫做一个门描述符(gate descriptor),“门”的含义是当中断发生时必须先通过这些门,然后才能进入相应的处理程序。门描述符的一般格式如下:

中断描述符表中可放三类门描述符:

(1)中断门(Interrupt gate)

其类型码为110,它包含一个中断或异常处理程序所在的段选择符和段内偏移。控制权通过中断门进入中断处理程序时,处理器清IF标志,即关中断,以避免嵌套中断的发生。中断门中的DPL (Descriptor Privilege Level)为0,因此,用户态的进程不能访问Intel的中断门。所有的中断处理程序都由中断门激活,并全部限制在内核态。设置中断门的代码如下:

//n为中断向量号,addr为中断处理程序地址,位于arch/i386/kernel/traps.c void set_intr_gate(unsigned int n, void *addr)

{ //type=14,dpl=0,selector=__KERNEL_CS

_set_gate(idt_table+n,14,0,addr,__KERNEL_CS);

}

Idt_table为中断描述符表,其定义位于arch/i386/kernel/traps.c中,如下:

//中断描述符表

struct desc_struct idt_table[256] __attribute__((__section__(".da ta.idt"))) = { {0, 0}, };

//描述符结构

struct desc_struct {

unsigned long a,b;

};

(2)陷阱门(Trap gate)

其类型码为111,与中断门类似,其唯一的区别是,控制权通过陷

阱门进入处理程序时维持IF标志位不变,也就是说,不关中断。

其设置代码如下:

static void __init set_trap_gate(unsigned int n, void *addr)

{

_set_gate(idt_table+n,15,0,addr,__KERNEL_CS);

}

(2)任务门(Task gate)

IDT中的任务门描述符格式与GDT和LDT中的任务门格式相同,含有一个任务TSS段的选择符,该任务用于处理异常或中断,Linux 用于处理Double fault。其设置代码如下:

static void __init set_task_gate(unsigned int n, unsigned int gdt

_entry)

{

_set_gate(idt_table+n,5,0,0,(gdt_entry<<3));

}

它们各自的格式如下:

此外,在Linux中还有系统门(System gate),用于处理用户态下的异常overflow,bound以及系统调用int 0x80;以及系统中断门(system interrupt gate),用来处理int3,这样汇编指令int3就能在用户态下调用。

static void __init set_system_gate(unsigned int n, void *addr) {

_set_gate(idt_table+n,15,3,addr,__KERNEL_CS);

}

//设置系统调用门描述符,在trap.c中被trap_init()调用

set_system_gate(SYSCALL_VECTOR,&system_call);

//设置系统中断门

static inline void set_system_intr_gate(unsigned int n, void *add r)

{

_set_gate(idt_table+n, 14, 3, addr, __KERNEL_CS);

}

//位于arch/i386/kernel/traps.c

void __init trap_init(void)

{

set_trap_gate(0,÷_error);

set_intr_gate(1,&debug);

set_intr_gate(2,&nmi);

//系统中断门

set_system_intr_gate(3, &int3); /* int3-5 can be called from all */

//系统门

set_system_gate(4,&overflow);

set_system_gate(5,&bounds);

set_trap_gate(6,&invalid_op);

set_trap_gate(7,&device_not_available);

set_task_gate(8,GDT_ENTRY_DOUBLEFAULT_TSS);

set_trap_gate(9,&coprocessor_segment_overrun);

set_trap_gate(10,&invalid_TSS);

set_trap_gate(11,&segment_not_present);

set_trap_gate(12,&stack_segment);

set_trap_gate(13,&general_protection);

set_intr_gate(14,&page_fault);

set_trap_gate(15,&spurious_interrupt_bug);

set_trap_gate(16,&coprocessor_error);

set_trap_gate(17,&alignment_check);

#ifdef CONFIG_X86_MCE

set_trap_gate(18,&machine_check);

#endif

set_trap_gate(19,&simd_coprocessor_error);

set_system_gate(SYSCALL_VECTOR,&system_call);

}

2.4.2、中断描述表初始化

中断描述表的最终初始化是init/main.c中的start_kernel()中完成的

asmlinkage void __init start_kernel(void)

{

//陷阱门初始化

trap_init();

//中断门初始化

init_IRQ();

//软中断初始化

softirq_init();

}

中断门的设置是在init_IRQ()中完成的,如下:

//位于arch/i386/kernel/i8259.c

void __init init_IRQ(void)

{

//调用init_ISA_irqs

pre_intr_init_hook();

//设置中断门

for (i = 0; i < (NR_VECTORS - FIRST_EXTERNAL_VECTOR); i++) { int vector = FIRST_EXTERNAL_VECTOR + i;

if (i >= NR_IRQS)

break;

//跳过系统调用的向量

if (vector != SYSCALL_VECTOR)

set_intr_gate(vector, interrupt[i]);

}

}

3、内核的中断处理

3.1、中断处理入口

由上节可知,中断向量的对应的处理程序位于interrupt数组中,下面来看看interrupt:

341 .data #数据段

342 ENTRY(interrupt)

343 .text

344

345 vector=0

346 ENTRY(irq_entries_start)

347 .rept NR_IRQS #348-354行重复NR_IRQS次

348 ALIGN

3491: pushl $vector-256 #vector在354行递增

350jmp common_interrupt #所有的外部中断处理函数的统一部分,以后再讲述351 .data

352 .long 1b #存储着指向349行的地址,但是随着348行-354被gcc展开,每次的值都不同

353 .text

354 vector=vector+1

355 .endr #与347行呼应

356

357 ALIGN

#公共处理函数

common_interrupt:

SAVE_ALL /*寄存器值入栈*/

movl %esp,%eax /*栈顶指针保存到eax*/

call do_IRQ /*处理中断*/

jmp ret_from_intr /*从中断返回*/

分析如下:

首先342行和352行都处于.data段,虽然看起来它们是隔开的,但实际上被gcc安排在了连续的数据段内存中,同理在代码段内存中,354行与350行的指令序列也是连续存储的。另外,348-354行会被gcc展开NR_IRQS次,因此每次352行都会存储一个新的指针,该指针指向每个349行展开的新对象。最后在代码段内存中连续存储了NR_IRQS个代码片断,首地址由

irq_entries_start指向。而在数据段内存中连续存储了NR_IRQS 个指针,首址存储在interrupt这个全局变量中。这样,例如IRQ

号是0 (从init_IRQ()调用,它对应的中断向量是

FIRST_EXTERNAL_VECTOR)的中断通过中断门后会触发interrput[0],从而执行:

pushl 0-256

jmp common_interrupt

的代码片断,进入到Linux内核安排好的中断入口路径。

3.2、数据结构

3.2.1、IRQ描述符

Linux支持多个外设共享一个IRQ,同时,为了维护中断向量和中断服务例程(ISR)之间的映射关系,Linux用一个irq_desc_t 数据结构来描述,叫做IRQ描述符。除了分配给异常的32个向量外,其余224(NR_IRQS)个中断向量对应的IRQ构成一个数组irq_desc[],定义如下:

//位于linux/irq.h

typedef struct irq_desc {

unsigned int status; /* IRQ status */

hw_irq_controller *handler;

struct irqaction *action; /* IRQ action list */

unsigned int depth; /* nested irq disables */

unsigned int irq_count; /* For detecting broken interr upts */

unsigned int irqs_unhandled;

spinlock_t lock;

} ____cacheline_aligned irq_desc_t;

//IRQ描述符表

extern irq_desc_t irq_desc [NR_IRQS];

“____cacheline_aligned”表示这个数据结构的存放按32字节(高速缓存行的大小)进行对齐,以便于将来存放在高速缓存并容易存取。status:描述IRQ中断线状态,在irq.h中定义。如下:

#define IRQ_INPROGRESS 1 /* 正在执行这个IRQ的一个处理程序*/

#define IRQ_DISABLED 2 /* 由设备驱动程序已经禁用了这条IRQ中断

线 */

#define IRQ_PENDING 4 /* 一个IRQ已经出现在中断线上,且被应答,但还没有为它提供服务 */

#define IRQ_REPLAY 8 /* 当Linux重新发送一个已被删除的IRQ时 */ #define IRQ_AUTODETECT 16 /* 当进行硬件设备探测时,内核使用这条IRQ中断线 */

#define IRQ_WAITING 32 /*当对硬件设备进行探测时,设置这个状态以标记正在被测试的irq */

#define IRQ_LEVEL 64 /* IRQ level triggered */

#define IRQ_MASKED 128 /* IRQ masked - shouldn't be seen again */

#define IRQ_PER_CPU 256 /* IRQ is per CPU */

handler:指向hw_interrupt_type描述符,这个描述符是对中断控制器的描述。下面有描述。

action:指向一个单向链表的指针,这个链表就是对中断服务例程进行描述的irqaction结构。下面有描述。

depth:如果启用这条IRQ中断线,depth则为0,如果禁用这条IRQ中断线不止一次,则为一个正数。每当调用一次

disable_irq(),该函数就对这个域的值加1;如果depth等于0,该函数就禁用这条IRQ中断线。相反,每当调用enable_irq()函数时,该函数就对这个域的值减1;如果depth变为0,该函数就启用这条IRQ中断线。

lock:保护该数据结构的自旋锁。

IRQ描述符的初始化:

//位于arch/i386/kernel/i8259.c

void __init init_ISA_irqs (void)

{

int i;

#ifdef CONFIG_X86_LOCAL_APIC

init_bsp_APIC();

#endif

//初始化8259A

init_8259A(0);

//IRQ描述符的初始化

for (i = 0; i < NR_IRQS; i++) {

irq_desc[i].status = IRQ_DISABLED;

irq_desc[i].action = NULL;

irq_desc[i].depth = 1;

if (i < 16) {

/*

* 16 old-style INTA-cycle interrupts: */

irq_desc[i].handler = &i8259A_irq_type; } else {

/*

* 'high' PCI IRQs filled in on demand

*/

irq_desc[i].handler = &no_irq_type;

}

}

}

从这段程序可以看出,初始化时,让所有的中断线都处于禁用状态;每条中断线上还没有任何中断服务例程(action为0);因为中断线被禁用,因此depth为1;对中断控制器的描述分为两种情况,一种就是通常所说的8259A,另一种是其它控制器。

3.2.2、中断控制器描述符hw_interrupt_type

这个描述符包含一组指针,指向与特定的可编程中断控制器电路(PIC)打交道的低级I/O例程,定义如下:

//位于linux/irq.h

struct hw_interrupt_type {

const char * typename;

unsigned int (*startup)(unsigned int irq);

void (*shutdown)(unsigned int irq);

void (*enable)(unsigned int irq);

void (*disable)(unsigned int irq);

void (*ack)(unsigned int irq);

void (*end)(unsigned int irq);

void (*set_affinity)(unsigned int irq, cpumask_t dest);

};

typedef struct hw_interrupt_type hw_irq_controller;

Linux除了支持常见的8259A芯片外,也支持其他的PIC电路,如SMP IO-APIC、PIIX4的内部8259 PIC及SGI的Visual Workstation Cobalt (IO-)APIC。8259A的描述符如下:

//位于arch/i386/kernel/i8259.c

static struct hw_interrupt_type i8259A_irq_type = {

"XT-PIC",

startup_8259A_irq,

shutdown_8259A_irq,

enable_8259A_irq,

disable_8259A_irq,

mask_and_ack_8259A,

end_8259A_irq,

NULL

};

在这个结构中的第一个域“XT-PIC”是一个名字。接下来,8259A_irq_type包含的指针指向五个不同的函数,这些函数就是对PIC编程的函数。前两个函数分别启动和关闭这个芯片的中断线。但是,在使用8259A芯片的情况下,这两个函数的作用与后两个函数是一样的,后两个函数是启用和禁用中断线。

mask_and_ack_8259A函数通过把适当的字节发往8259A I/O 端口来应答所接收的IRQ。end_8259A_irq在IRQ的中断处理程序结束时被调用。

3.2.3、中断服务例程描述符irqaction

为了处理多个设备共享一个IRQ,Linux中引入了irqaction数据结构。定义如下:

//位于linux/interrupt.h

struct irqaction {

irqreturn_t (*handler)(int, void *, struct pt_regs *);

unsigned long flags;

cpumask_t mask;

const char *name;

void *dev_id;

struct irqaction *next;

int irq;

struct proc_dir_entry *dir;

};

handler:指向一个具体I/O设备的中断服务例程。这是允许多个设备共享同一中断线的关键域。

flags:用一组标志描述中断线与I/O设备之间的关系。

SA_INTERRUPT:中断处理程序必须以禁用中断来执行

SA_SHIRQ:该设备允许其中断线与其他设备共享。

SA_SAMPLE_RANDOM:可以把这个设备看作是随机事件发生源;因此,内核可以用它做随机数产生器。(用户可以从

/dev/random 和/dev/urandom设备文件中取得随机数而访问这种特征)

SA_PROBE:内核在执行硬件设备探测时正在使用这条中断线。name:I/O设备名(通过读取/proc/interrupts文件,可以看到,在列出中断号时也显示设备名。)

dev_id:指定I/O设备的主设备号和次设备号。

next:指向irqaction描述符链表的下一个元素。共享同一中断线的每个硬件设备都有其对应的中断服务例程,链表中的每个元素就是对相应设备及中断服务例程的描述。

irq:IRQ线。

3.2.4、中断服务例程(Interrupt Service Routine)

在Linux中,中断服务例程和中断处理程序(Interrupt Handler)

是两个不同的概念。可以这样认为,中断处理程序相当于某个中断向量的总处理程序,它与中断描述表(IDT)相关;中断服务例程(ISR)在中断处理过程被调用,它与IRQ描述符相关,一般来说,它是设备驱动的一部分。

(1) 注册中断服务例程

中断服务例程是硬件驱动的组成部分,如果设备要使用中断,相应的驱动程序在初始化的过程中可以通过调用request_irq函数注册中断服务例程。

//位于kernel/irq/manage.c

/*irq:IRQ号

**handler:中断服务例程

**irqflags:SA_SHIRQ,SA_INTERRUPT或SA_SAMPLE_RANDOM

**devname:设备名称,这些名称会被/proc/irq和/proc/interrupt使用

**dev_id:主要用于设备共享

*/

int request_irq(unsigned int irq,

irqreturn_t (*handler)(int, void *, struct pt_regs *),

unsigned long irqflags, const char * devname, void *dev_i d)

{

struct irqaction * action;

int retval;

/*

* Sanity-check: shared interrupts must pass in a real dev-ID, * otherwise we'll have trouble later trying to figure out

* which interrupt is which (messes up the interrupt freeing

* logic etc).

*/

if ((irqflags & SA_SHIRQ) && !dev_id)

return -EINVAL;

if (irq >= NR_IRQS)

return -EINVAL;

if (!handler)

return -EINVAL;

//分配数据结构空间

action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC);

if (!action)

return -ENOMEM;

action->handler = handler;

action->flags = irqflags;

cpus_clear(action->mask);

action->name = devname;

action->next = NULL;

action->dev_id = dev_id;

//调用setup_irq完成真正的注册,驱动程序也可以调用它来完成注册

retval = setup_irq(irq, action);

if (retval)

kfree(action);

return retval;

}

来看实时时钟初始化函数如何使用request_irq():

//位于driver/char/rtc.c

static int __init rtc_init(void)

{

request_irq(RTC_IRQ, rtc_int_handler_ptr, SA_INTERRUPT, "rtc", NU LL);

}

再看看时钟中断初始化函数:

//位于arch/i386/mach_default/setup.c

static struct irqaction irq0 = { timer_interrupt, SA_INTERRUPT, CPU_MASK_NONE, "timer", NULL, NULL};

//由time_init()调用

void __init time_init_hook(void)

{

setup_irq(0, &irq0);

}

3.3、中断处理流程

整个流程如下:

所有I/O中断处理函数的过程如下:

(1)把IRQ值和所有寄存器值压入内核栈;

(2) 给与IRQ中断线相连的中断控制器发送一个应答,这将允许在这条中断线上进一步发出中断请求;

(3)执行共享这个IRQ的所有设备的中断服务例程(ISR);

(4)跳到ret_from_intr()处结束。

3.3.1、保存现场与恢复现场

ARM-Linux下的GPIO中断程序.

ARM-Linux下的GPIO中断程序 [日期:2011-03-22] 来源:Linux社区作者:cskywit 今日为了调试ARM板上的GPIO引脚中断效果,以便在后续项目使用ARM与ZLG7290按键LED中断芯片连接中随意选择空闲的GPIO引脚来作为ZLG7290的中断信号线,特意编写了一个小的Linux GPIO中断驱动程序下载到开发板上做实验。经验证,这种软件中断方式也还差强人意。下面贴出自己编写的不成熟的代码,见笑(<-_->)。 实验的硬件电路为ARM GPIO的PB17连接一个共阴LED,PB18与PB19连接,PB18由中断驱动设置为低电平触发,PB19由GPIO驱动程序控制,上层应用程序通过驱动控制PB19高低电平变化,从而引发PB18发生中断,中断程序中控制PB17的LED亮和灭。 Linux中断驱动部分: /* * PB18_IRQTest.c * This is a test program for sam9260, using PB19(J5_18 pin) input a signal to PB18(J5_16 pin), * PB18 receive this signal as IRQ and make the LED linking on PB17((J5_14 pin)) turn on or turn off * * @Author: Cun Tian Rui * @Date :March.18.2011 */ #include #include #include #include #include #include #include #include #include #include #include #include #include

armlinux内核中ARM中断实现详解.

linux-2.6.26内核中ARM中断实现详解(1) 作者:刘洪涛,华清远见嵌入式学院金牌讲师,ARM ATC授权培训讲师。 看了一些网络上关于linux中断实现的文章,感觉有一些写的非常好,在这里首先感谢他们的无私付出,然后也想再补充自己对一些问题的理解。先从函数注册引出问题吧。 一、中断注册方法 在linux内核中用于申请中断的函数是request_irq(),函数原型在 Kernel/irq/manage.c中定义: int request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id) irq是要申请的硬件中断号。 handler是向系统注册的中断处理函数,是一个回调函数,中断发生时,系统调用这个函数,dev_id参数将被传递给它。 irqflags是中断处理的属性,若设置了IRQF_DISABLED (老版本中的 SA_INTERRUPT,本版zhon已经不支持了),则表示中断处理程序是快速处理程序,快速处理程序被调用时屏蔽所有中断,慢速处理程序不屏蔽;若设置了IRQF_SHARED (老版本中的SA_SHIRQ),则表示多个设备共享中断,若设置了IRQF_SAMPLE_RANDOM(老版本中的 SA_SAMPLE_RANDOM),表示对系统熵有贡献,对系统获取随机数有好处。(这几个flag是可以通过或的方式同时使用的) dev_id在中断共享时会用到,一般设置为这个设备的设备结构体或者NULL。 devname设置中断名称,在cat /proc/interrupts中可以看到此名称。 request_irq()返回0表示成功,返回-INVAL表示中断号无效或处理函数指针为NULL,返回-EBUSY表示中断已经被占用且不能共享。 关于中断注册的例子,大家可在内核中搜索下request_irq。 在编写驱动的过程中,比较容易产生疑惑的地方是: 1、中断向量表在什么位置?是如何建立的? 2、从中断开始,系统是怎样执行到我自己注册的函数的? 3、中断号是如何确定的?对于硬件上有子中断的中断号如何确定? 4、中断共享是怎么回事,dev_id的作用是? 本文以2.6.26内核和S3C2410处理器为例,为大家讲解这几个问题。

探究linux内核,超详细解析子系统

探究linux内核,超详细解析子系统 Perface 前面已经写过一篇《嵌入式linux内核的五个子系统》,概括性比较强,也比较简略,现在对其进行补充说明。 仅留此笔记,待日后查看及补充!Linux内核的子系统 内核是操作系统的核心。Linux内核提供很多基本功能,如虚拟内存、多任务、共享库、需求加载、共享写时拷贝(Copy-On-Write)以及网络功能等。增加各种不同功能导致内核代码不断增加。 Linux内核把不同功能分成不同的子系统的方法,通过一种整体的结构把各种功能集合在一起,提高了工作效率。同时还提供动态加载模块的方式,为动态修改内核功能提供了灵活性。系统调用接口用户程序通过软件中断后,调用系统内核提供的功能,这个在用户空间和内核提供的服务之间的接口称为系统调用。系统调用是Linux内核提供的,用户空间无法直接使用系统调用。在用户进程使用系统调用必须跨越应用程序和内核的界限。Linux内核向用户提供了统一的系统调用接口,但是在不同处理器上系统调用的方法

各不相同。Linux内核提供了大量的系统调用,现在从系统 调用的基本原理出发探究Linux系统调用的方法。这是在一个用户进程中通过GNU C库进行的系统调用示意图,系 统调用通过同一个入口点传入内核。以i386体系结构为例,约定使用EAX寄存器标记系统调用。 当加载了系统C库调用的索引和参数时,就会调用0x80软件中断,它将执行system_call函数,这个函数按照EAX 寄存器内容的标示处理所有的系统调用。经过几个单元测试,会使用EAX寄存器的内容的索引查system_call_table表得到系统调用的入口,然后执行系统调用。从系统调用返回后,最终执行system_exit,并调用resume_userspace函数返回用户空间。 linux内核系统调用的核心是系统多路分解表。最终通过EAX寄存器的系统调用标识和索引值从对应的系统调用表 中查出对应系统调用的入口地址,然后执行系统调用。 linux系统调用并不单层的调用关系,有的系统调用会由

linux中断总结

1.Linux中断的注册与释放: 在, , 实现中断注册接口: int request_irq(unsigned int irq,irqreturn_t (*handler)(int, void *, struct pt_regs *), unsigned long flags, const char *dev_name, void *dev_id); void free_irq(unsigned int irq, void *dev_id); 函数参数说明 unsigned int irq:所要注册的中断号 irqreturn_t (*handler)(int, void *, struct pt_regs *):中断服务程序的入口地址。unsigned long flags:与中断管理有关的位掩码选项,有三组值: 1. SA_INTERRUPT :快速中断处理程序,当使用它的是后处理器上所有的其他中断都被禁用。 2. SA_SHIRQ :该中断是在设备之间可共享的 3. SA_SAMPLE_RANDOM :这个位表示产生的中断能够有贡献给 /dev/random 和 /dev/urandom 使用的加密池.(此处不理解) const char *dev_name:设备描述,表示那一个设备在使用这个中断。 void *dev_id:用作共享中断线的指针. 它是一个独特的标识, 用在当释放中断线时以及可能还被驱动用来指向它自己的私有数据区(来标识哪个设备在中断) 。这个参数在真正的驱动程序中一般是指向设备数据结构的指针.在调用中断处理程序的时候它就会传递给中断处理程序的void *dev_id。(这是我的理解)如果中断没有被共享, dev_id 可以设置为 NULL, 但是使用这个项指向设备结构不管如何是个好主意. 我们将在"实现一个处理"一节中看到dev_id 的一个实际应用。 中断号的查看可以使用下面的命令:“cat /proc/interrupts”。 /proc/stat 记录了几个关于系统活动的低级统计量, 包括(但是不限于)自系统启动以来收到的中断数. stat 的每一行以一个文本字串开始, 是该行的关键词; intr 标志是我们在找的. 第一个数是所有中断的总数, 而其他每一个代表一个单个 IRQ 线, 从中断 0 开始. 所有的计数跨系统中所有处理器而汇总的. 这个快照显示, 中断号 4 已使用 1 次, 尽管当前没有安装处理. 如果你在测试的驱动请求并释放中断在每个打开和关闭循环, 你可能发现/proc/stat 比 /proc/interrupts 更加有用. 以下是一个统计中断时间间隔的中断服务程序。 irqreturn_t short_interrupt(int irq, void *dev_id, struct pt_regs *regs) { static long mytime=0; static int i=0; struct net_device *dev=(struct net_device *)dev_id; if(i==0){ mytime=jiffies; }else if(i<20){ mytime =jiffies- mytime; printk("Request on IRQ %d time %d\n",irq , mytime); mytime=jiffies;

简析linux内核的内核执行流程图

简析linux核的执行流程 ----从bootsect.s到main.c(核版本0.11)Linux启动的第一阶段(从开机到main.c) 3个任务: A、启动BIOS,准备实模式下的中断向量表和中断服务程序。 B、从启动盘加载操作系统程序到存。 C、为执行32的main函数做过渡准备。 存变化如下: ①、0xFE000到0xFFFFF是BIOS启动块,其中上电后第一条指令在0xFFFF0。 ②、而后0x00000到0x003FF总共1KB存放中断向量表,而接下去的地址到0x004FF共256B存放BIOS数据,从0x0E05B 开始的约8KB的存中存放中断服务程序。 ③、利用BIOS中断0x19h把硬盘的第一扇区bootsect.s的代码加载到存中,即0x07c00处,后转到该处执行。 ④、将bootsect.s的代码复制到0x90000处。 ⑤、利用中断0x13h将setup.s程序加载到存0x90200处。 ⑥、再将剩余的约240个扇区的容加载到0x10000~0x2EFFF 处。 ⑦、开始转到setup.s处执行,第一件事就利用BIOS提供的中断服务程序从设备上获取核运行的所需系统数据并存在0x90000的地址处,这时将原来bootsect.s的代码覆盖得只剩2Byte的空间。

⑧、关中断并将系统代码复制到0x00000处,将原来放在这里的中断向量表与BIOS数据区覆盖掉,地址围是 0x00000~0x1EFFF。同时制作两表与两寄存器。 ⑨开地址线A20,寻址空间达到4GB,后对8259重新编程,改变中断号。 ⑩、转到head.s(大小是25K+184B)执行,执行该程序完后是这样的: 0x00000~0x04FFF:页目录与4个页表,每一项是4KB,共20KB;0x05000~0x05400:共1KB的空间是软盘缓冲区; 0x05401~0x054b8:共184B没用; 0x054b9~0x05cb8:共2KB的空间存中断描述符表; 0x05cb9~0x064b8:共2KB的空间存全局描述符表; 之后就是main函数的代码了! 第二阶段、从main.c函数到系统准备完毕阶段。 第一步:创建进程0,并让进程0具备在32位保护模式下载主机中的运算能力。流程是: 复制根设备和硬盘参数表(main.c中的102、110、111行) 物理存规划格局(main.c的112行~126行,其中有 rd_init函数定义在kernel/ramdisk.c中,此函数用于虚拟盘初始化;而mem_init函数是用于存管理结构初始化,定义在mem/memory.c中,该函数页面使用

LINUX内核时钟中断机制

Linux内核的时钟中断机制 opyright © 2003 by 詹荣开 E-mail:zhanrk@https://www.doczj.com/doc/7313170103.html, Linux-2.4.0 Version 1.0.0,2003-2-14 摘要:本文主要从内核实现的角度分析了Linux 2.4.0内核的时钟中断、内核对时间的表示等。本文是为那些想要了解Linux I/O子系统的读者和Linux驱动程序开发人员而写的。 关键词:Linux、时钟、定时器 申明:这份文档是按照自由软件开放源代码的精神发布的,任何人可以免费获得、使用和重新发布,但是你没有限制别人重新发布你发布内容的权利。发布本文的目的是希望它能对读者有用,但没有任何担保,甚至没有适合特定目的的隐含的担保。更详细的情况请参阅GNU 通用公共许可证(GPL),以及GNU自由文档协议(GFDL)。 你应该已经和文档一起收到一份GNU通用公共许可证(GPL)的副本。如果还没有,写信给:The Free Software Foundation, Inc., 675 Mass Ave, Cambridge,MA02139, USA 欢迎各位指出文档中的错误与疑问。 前言 时间在一个操作系统内核中占据着重要的地位,它是驱动一个OS内核运行的“起博器”。一般说来,内核主要需要两种类型的时间: (1)、在内核运行期间持续记录当前的时间与日期,以便内核对某些对象和事件作时间标记(timestamp,也称为“时间戳”),或供用户通过时间syscall进行检索。 (2)、维持一个固定周期的定时器,以提醒内核或用户一段时间已经过去了。 PC机中的时间是有三种时钟硬件提供的,而这些时钟硬件又都基于固定频率的晶体振荡

Linux内核与跟文件系统的关系

Linux内核与根文件系统的关系 开篇题外话:对于Linux初学者来说,这是一个很纠结的问题,但这也是一个很关键的问题!一语破天机:“尽管内核是Linux 的核心,但文件却是用户与操作系统交互所采用的主要工具。这对Linux 来说尤其如此,这是因为在UNIX 传统中,它使用文件I/O 机制管理硬件 设备和数据文件。” 一.什么是文件系统 文件系统指文件存在的物理空间,linux系统中每个分区都是一个文件系统,都有自己的目 录层次结构。 Linux文件系统中的文件是数据的集合,文件系统不仅包含着文件中的数据而且还有文件系统的结构,所有Linux 用户和程序看到的文件、目录、软连接及文件保护信息等都存储在其 中。这种机制有利于用户和操作系统的交互。 每个实际文件系统从操作系统和系统服务中分离出来,它们之间通过一个接口层:虚拟文件系统或VFS来通讯。VFS使得Linux可以支持多个不同的文件系统,每个表示一个VFS 的通用接口。由于软件将Linux 文件系统的所有细节进行了转换,所以Linux核心的其它部分及系统中运行的程序将看到统一的文件系统。Linux 的虚拟文件系统允许用户同时能透明地安装 许多不同的文件系统。 在Linux文件系统中,EXT2文件系统、虚拟文件系统、/proc文件系统是三个具有代表性的 文件系统。 二.什么是根文件系统 根文件系统首先是一种文件系统,该文件系统不仅具有普通文件系统的存储数据文件的功能,但是相对于普通的文件系统,它的特殊之处在于,它是内核启动时所挂载(mount)的第一个文件系统,内核代码的映像文件保存在根文件系统中,系统引导启动程序会在根文件系统挂载之后从中把一些初始化脚本(如rcS,inittab)和服务加载到内存中去运行。我们要明白文件系统和内核是完全独立的两个部分。在嵌入式中移植的内核下载到开发板上,是没有办法真正的启动Linux操作系统的,会出现无法加载文件系统的错误。 那么根文件系统在系统启动中到底是什么时候挂载的呢?先将/dev/ram0挂载,而后执行/linuxrc.等其执行完后。切换根目录,再挂载具体的根文件系统.根文件系统执行完之后,也就是到了Start_kernel()函数的最后,执行init的进程,也就第一个用户进程。对系统进行各 种初始化的操作。 根文件系统之所以在前面加一个”根“,说明它是加载其它文件系统的”根“,既然是根的话,那么如果没有这个根,其它的文件系统也就没有办法进行加载的。它包含系统引导和使其他文件系统得以挂载(mount)所必要的文件。根文件系统包括Linux启动时所必须的目录和关键性的文件,例如Linux启动时都需要有init目录下的相关文件,在Linux挂载分区时Linux 一定会找/etc/fstab这个挂载文件等,根文件系统中还包括了许多的应用程序bin目录等,任何包括这些Linux 系统启动所必须的文件都可以成为根文件系统。Linux启动时,第一个必须挂载的是根文件系统;若系统不能从指定设备上挂载根文件系统,则系统会出错而退出启动。成功之后可以自动或手动挂载其他的文件系统。因此,一个系统中可以同时存在不同的文件系统。在Linux 中将一个文件系统与一个存储设备关联起来的过程称为挂载(mount)。使用mount 命令将一个文件系统附着到当前文件系统层次结构中(根)。在执行挂装时,要提供文件系统类型、文件系统和一个挂装点。根文件系统被挂载到根目录下“/”上后,在根目录下就有根文件系统的各个目录,文件:/bin /sbin /mnt等,再将其他分区挂接到/mnt 目录上,/mnt目录下就有这个分区的各个目录,文件。

Linux中断处理流程

Linux中断处理流程 先从函数注册引出问题吧。 一、中断注册方法 在linux内核中用于申请中断的函数是request_irq(),函数原型在Kernel/irq/manage.c中定义: int request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id) irq是要申请的硬件中断号。 handler是向系统注册的中断处理函数,是一个回调函数,中断发生时,系统调用这个函数,dev_id参数将被传递给它。 irqflags是中断处理的属性,若设置了IRQF_DISABLED (老版本中的SA_INTERRUPT,本版zhon已经不支持了),则表示中断处理程序是快速处理程序,快速处理程序被调用时屏蔽所有中断,慢速处理程 序不屏蔽;若设置了IRQF_SHARED (老版本中的SA_SHIRQ),则表示多个设备共享中断,若设置了IRQF_SAMPLE_RANDOM(老版本中的 SA_SAMPLE_RANDOM),表示对系统熵有贡献,对系统获取随机数有好处。(这几个flag是可以通过或的方式同时使用的) dev_id在中断共享时会用到,一般设置为这个设备的设备结构体或者NULL。devname设置中断名称,在cat /proc/interrupts中可以看到此名称。 request_irq()返回0表示成功,返回-INVAL表示中断号无效或处理函数指针为NULL,返回-EBUSY表示中断已经被占用且不能共享。 关于中断注册的例子,大家可在内核中搜索下request_irq。 在编写驱动的过程中,比较容易产生疑惑的地方是: 1、中断向量表在什么位置?是如何建立的? 2、从中断开始,系统是怎样执行到我自己注册的函数的? 3、中断号是如何确定的?对于硬件上有子中断的中断号如何确定? 4、中断共享是怎么回事,dev_id的作用是? 本文以2.6.26内核和S3C2410处理器为例,为大家讲解这几个问题。 二、异常向量表的建立 在ARM V4及V4T以后的大部分处理器中,中断向量表的位置可以有两个位置:一个是0,另一个是0xffff0000。可以通过CP15协处理器c1寄存器中V位(bit[13])控制。V和中断向量表的对应关系如下: V=0 ~ 0x00000000~0x0000001C V=1 ~ 0xffff0000~0xffff001C arch/arm/mm/proc-arm920.S中 .section ".text.init", #alloc, #execinstr __arm920_setup: ...... orr r0, r0, #0x2100 @ ..1. ...1 ..11 (1) //bit13=1 中断向量表基址为0xFFFF0000。R0的值将被付给CP15的C1.

Linux内核编码风格(编程代码风格推荐)

这是翻译版本,英文原版是linux源码Documentation文件夹下的CodingStyle 一个良好风格的程序看起来直观、美观,便于阅读,还能有助于对程序的理解,特别在代码量比较大情况下更显现编码素质的重要性。相反没有良好的风格的代码读起来难看、晦涩,甚至有时候一个括号没对齐就能造成对程序的曲解或者不理解。我曾经就遇见过这样的情况,花费了很多不必要的时间在程序的上下文对照上,还debug了半天没理解的程序。后来直接用indent -kr -i8给他转换格式来看了。特此转过来一个关于代码风格的帖子分享一下~ Linux内核编码风格 这是一份简短的,描述linux内核首选编码风格的文档。编码风格是很个人化的东西,而且我也不愿意把我的观点强加给任何人,不过这里所讲述的是我必须要维护的代码所遵守的风格,并且我也希望绝大多数其他代码也能遵守这个风格。所以请至少考虑一下本文所述的观点。 首先,我建议你打印一份GNU的编码规范,然后不要读它。烧掉它,这是一个很高调的具有象征意义的姿态。 Anyway, here goes: 第一章:缩进 制表符是8个字符,所以缩进也是8个字符。有些异端运动试图将缩进变为4(乃至2)个字符深,这跟尝试着将圆周率PI的值定义为3没什么两样。 理由:缩进的全部意义就在于清楚的定义一个控制块起止于何处。尤其是当你盯着你的屏幕连续看了20小时之后,你将会发现大一点的缩进将会使你更容易分辨缩进。 现在,有些人会抱怨8个字符的缩进会使代码向右边移动的太远,在80个字符的终端屏幕上就很难读这样的代码。这个问题的答案是,如果你需要3级以上的缩进,不管缩进深度如何你的代码已经有问题了,应该修正你的程序。 简而言之,8个字符的缩进可以让代码更容易阅读,还有一个好处是当你的函数嵌套太深的时候可以向你提出告警。请留意这个警告。 在switch语句中消除多级缩进的首选的方式是让“switch”和从属于它的“case”标签对 齐于同一列,而不要“两次缩进”“case”标签。比如: switch (suffix) { case 'G': case 'g': mem <<= 30;

linux中断实例

你的第一个中断程序: 本文通过一个简单的中断程序来描述一般中断程序的基本框架。完整代码在这里。 中断程序一般会包含在某个设备的驱动程序中,因此,接下来的程序本质上还是一个内核模块。说到内核模块,你应该知道首先去看什么了吧?对了,就是内核模块加载函数。 view source print? 01 static int __init myirq_init() 02 { 03 printk("Module is working..\n"); 04 if(request_irq(irq,myirq_handler,IRQF_SHARED,devname, &mydev)!=0) 05 { 06 printk("%s request IRQ:%d failed..\n",devname,irq); 07 return -1; 08 } 09 printk("%s rquest IRQ:%d success..\n",devname,irq); 10 return 0; 11 } 在内核加载函数中,我们除了显示一些信息外,最重要的工作就是申请一根中断请求线,也就是注册中断处理程序。很明显,这一动作是通过 request_irq函 数来完成的。这个函数的原型如下: view source print? 1 static int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev); 第一个参数是中断号,这个中断号对应的就是中断控制器上IRQ线的编号。 第二个参数是一个irq_handler_t类型个函数指针: view source print? 1 typedef irqreturn_t (*irq_handler_t)(int, void *); handler所指向的函数即为中断处理程序,需要具体来实现。 第三个参数为标志位,可以取IRQF_DISABLED、IRQF_SHARED和 IRQF_SAMPLE_RANDOM之一。在本实例程序中取 IRQF_SHARED,该标志表示多个 设备共享一条IRQ线,因此相应的每个设备都需要各自的中断服务例程。一般某个中断线上的中断服务程序在执行时会屏蔽请求该线的其他中断,如果取

Linux内核与驱动开发实验教材

内核与驱动开发实验教材 中程在线 实验一嵌入式开发环境的建立 实验目的 掌握嵌入式开发环境的构建,熟悉课程实验的开发板 掌握安装交叉编译工具的安装方法 掌握的烧写方法 掌握的编译方法 实验内容 安装交叉编译工具 编译 烧写 生成映像 基础知识 交叉编译工具 嵌入式系统的开发中,开发环境被称为主机。因为嵌入式目标系统的资源局限性,不可能完成构建系统的任务,所以需要主机使用交叉编译工具来构建目标系统。 实验使用交叉编译器,与桌面系统采用的编译器是不同,因为实验开发板采用的是处理器。 编译器将使用下列工具 , 与通常在平台上使用的工具不同,交叉编译工具编译处理的执行文件只能够在平台上运行。 嵌入式系统构建 一个嵌入式系统从软件的角度看通常可以分为四个层次: .引导加载程序()。引导加载程序是系统加电后运行的第一段软件代码。 . 内核。特定于嵌入式板子的定制内核以及内核的启动参数。 . 文件系统。包括根文件系统和建立于内存设备之上文件系统。通常用来作为。 .用户应用程序。特定于用户的应用程序。

主要的功能有: 初始化硬件,初始化, , , , 。 启动,这是最重要的功能,保存内核映像到中,并跳转到内核起始地址。 映像下载,下载内核映像和文件系统到,下载只能通过以太网进行。如命令完成文件下载。 内存控制,如命令可以烧写。 机中的引导加载程序由(其本质就是一段固件程序)和位于硬盘中的(比如,和等)一起组成。在完成硬件检测和资源分配后,将硬盘中的读到系统的中,然后将控制权交给。的主要运行任务就是将内核映象从硬盘上读到中,然后跳转到内核的入口点去运行,也即开始启动操作系统。 而在嵌入式系统中,通常并没有像那样的固件程序(注,有的嵌入式也会内嵌一段短小的启动程序),因此整个系统的加载启动任务就完全由来完成。在实验开发板(基于3C)的嵌入式系统中,系统在上电或复位时都从地址处开始执行,而在这个地址处安排的通常就是系统的程序。 简单地说,就是在操作系统内核运行之前运行的一段小程序。通过这段小程序,我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境。 通常,是严重地依赖于硬件而实现的,特别是在嵌入式世界。因此,在嵌入式世界里建立一个通用的几乎是不可能的。尽管如此,我们仍然可以对归纳出一些通用的概念来,以指导用户特定的设计与实现。 内核是所有系统的中心软件组件。整个系统的能力完全受内核本身能力的限制。 由于内核支持多个架构,由于架构的差异性,每种架构都有不同的团队在维护,所以必须根据架构来选择供应内核的网站。见下表: 架构最合适的内核网站下载方式 等 内核源代码目录树结构说明如下: :包含和硬件体系结构相关的代码,每种平台占一个相应的目录。和位相关的代码存放在目录下,其中比较重要的包括(内核核心部分)、(内存管理)、(浮点单元仿真)、(硬件相关工具函数)、(引导程序)、(总线)和(相关状态)。 :常用加密和散列算法(如、等),还有一些压缩和校验算法。 :关于内核各部分的通用解释和注释。 :设备驱动程序,每个不同的驱动占用一个子目录。 :各种支持的文件系统,如、、等。 :头文件。其中,和系统相关的头文件被放置在子目录下。 :内核初始化代码(注意不是系统引导代码)。 :进程间通信的代码。 :内核的最核心部分,包括进程调度、定时器等,和平台相关的一部分代码放在*目录下。:库文件代码。 :内存管理代码,和平台相关的一部分代码放在*目录下。 :网络相关代码,实现了各种常见的网络协议。

Linux内核版本号及源代码目录树结构

Linux 内核版本号及源代码目录树结构 一、linux内核版本号的命名机制 Linux内核版本有两种:稳定版和开发版。稳定的内核具有工业级的强度,可以广泛地应用和部署。新的稳定内核相对于较旧的只是修正一些bug或加入一些新的驱动程序。而开发版内核由于要试验各种解决方案,所以变化很快。这两种版本是相互关联,相互循环的。 Linux内核的命名机制: num.num.num 其中第一个数字是主版本号,第二个数字是次版本号,第三个数字是修订版本号。如果次版本号是偶数,那么该内核就是稳定版的;若是奇数,则是开发版的。头两个数字合在一齐可以描述内核系列。如稳定版的2.6.0,它是2.6版内核系列。最新的内核源代码可以在https://www.doczj.com/doc/7313170103.html,以tar包或者增量补丁的形式下载.。 Linux还有各种发行版本,除了最熟悉的Redhat,Debian,Bluepoint,红旗,还有 Slackware,Mandarke,Turbo。 二、linux源代码目录树结构 Linux用来支持各种体系结构的源代码包含大约4500个C语言程序,存放在270个左右的子目录下,总共大约包含200万行代码,大概占用58MB磁盘空间。 在阅读源码之前,还应知道Linux内核源码的整体分布情况。现代的操作系统一般由进程管理、内存管理、文件系统、驱动程序和网络等组成。Linux内核源码的各个目录大致与此相对应,其组成如下: arch目录包括了所有和体系结构相关的核心代码。它下面的每一个子目录都代表一种Linux支持的体系结构,例如i386就是Intel CPU及与之相兼容体系结构的子目录。PC机一般都基于此目录。 include目录包括编译核心所需要的大部分头文件,例如与平台无关的头文件在include/linux子目录下。

linux 中断分析

Linux系统中有很多不同的硬件设备。你可以同步使用这些设备,也就是说你可以发出一个请求,然后等待 一直到设备完成操作以后再进行其他的工作。但这种方法的效率却非常的低,因为操作系统要花费很多的等待时间。一个更为有效的方法是发出请求以后,操作系统继续其他的工作,等设备完成操作以后,给操作系统发送一个中断,操作系统再继续处理和此设备有关的操作。 在将多个设备的中断信号送往CPU的中断插脚之前,系统经常使用中断控制器来综合多个设备的中断。这样即可以节约CPU的中断插脚,也可以提高系统设计的灵活性。中断控制器用来控制系统的中断, 它包括屏蔽和状态寄存器。设置屏蔽寄存器的各个位可以允许或屏蔽某一个中断,状态寄存器则用来返回系统中正在使用的中断。 大多数处理器处理中断的过程都相同。当一个设备发出中段请求时,CPU停止正在执行的指令,转而跳到包括中断处理代码或者包括指向中断处理代码的转移指令所在的内存区域。这些代码一般在CPU的中断方式下运行。在此方式下,将不会再有中断发生。但有些CPU的中断有自己的优先权,这样,更高优先权的中断则可以发生。这意味着第一级的中断处理程序必须拥有自己的堆栈,以便在处理更高级别的中断前保存CPU的执行状态。当中断处理完毕以后,CPU将恢复到以前的状态,继续执行中断处理前正在执行的指令。 中断处理程序十分简单有效,这样,操作系统就不会花太长的时间屏蔽其他的中断。 [设置Softirq] cpu_raise_softirq是一个轮训,唤醒ksoftirqd_CPU0内核线程, 进行管理 cpu_raise_softirq |__cpu_raise_softirq |wakeup_softirqd |wake_up_process ·cpu_raise_softirq [kernel/softirq.c] ·__cpu_raise_softirq [include/linux/interrupt.h] ·wakeup_softirq [kernel/softirq.c]

Linux中断(interrupt)子系统之一:arch相关的硬件封装层

Linux中断(interrupt)子系统之一:arch相关的硬件封装层Linux的通用中断子系统的一个设计原则就是把底层的硬件实现尽可能地隐藏起来,使得驱动程序的开发人员不用关注底层的实现,要实现这个目标,内核的开发者们必须把硬件相关的内容剥离出来,然后定义一些列标准的接口供上层访问,上层的开发人员只要知道这些接口即可完成对中断的进一步处理和控制。对底层的封装主要包括两部分: 实现不同体系结构中断入口,这部分代码通常用asm实现; 中断控制器进行封装和实现; 本文的内容正是要讨论硬件封装层的实现细节。我将以ARM体系进行介绍,大部分的代码位于内核代码树的arch/arm/目录内。 1. CPU的中断入口 我们知道,arm的异常和复位向量表有两种选择,一种是低端向量,向量地址位于0x00000000,另一种是高端向量,向量地址位于0xffff0000,Linux选择使用高端向量模式,也就是说,当异常发生时,CPU会把PC指针自动跳转到始于0xffff0000开始的某一个地址上: ARM的异常向量表地址异常种类FFFF0000复位FFFF0004未定义指令FFFF0008软中断(swi)FFFF000CPrefetch abortFFFF0010Data abortFFFF0014保留FFFF0018IRQFFFF001CFIQ 中断向量表在arch/arm/kernel/entry_armv.S中定义,为了方便讨论,下面只列出部分关键的代码: [plain] view plain copy .globl __stubs_start __stubs_start: vector_stub irq, IRQ_MODE, 4 .long __irq_usr @ 0 (USR_26 / USR_32) .long __irq_invalid @ 1 (FIQ_26 / FIQ_32) .long __irq_invalid @ 2 (IRQ_26 / IRQ_32)

Linux内核源码分析方法

Linux内核源码分析方法 一、内核源码之我见 Linux内核代码的庞大令不少人“望而生畏”,也正因为如此,使得人们对Linux的了解仅处于泛泛的层次。如果想透析Linux,深入操作系统的本质,阅读内核源码是最有效的途径。我们都知道,想成为优秀的程序员,需要大量的实践和代码的编写。编程固然重要,但是往往只编程的人很容易把自己局限在自己的知识领域内。如果要扩展自己知识的广度,我们需要多接触其他人编写的代码,尤其是水平比我们更高的人编写的代码。通过这种途径,我们可以跳出自己知识圈的束缚,进入他人的知识圈,了解更多甚至我们一般短期内无法了解到的信息。Linux内核由无数开源社区的“大神们”精心维护,这些人都可以称得上一顶一的代码高手。透过阅读Linux 内核代码的方式,我们学习到的不光是内核相关的知识,在我看来更具价值的是学习和体会它们的编程技巧以及对计算机的理解。 我也是通过一个项目接触了Linux内核源码的分析,从源码的分析工作中,我受益颇多。除了获取相关的内核知识外,也改变了我对内核代码的过往认知: 1.内核源码的分析并非“高不可攀”。内核源码分析的难度不在于源码本身,而在于如何使用更合适的分析代码的方式和手段。内核的庞大致使我们不能按照分析一般的demo程序那样从主函数开始按部就班的分析,我们需要一种从中间介入的手段对内核源码“各个击破”。这种“按需索取”的方式使得我们可以把握源码的主线,而非过度纠结于具体的细节。 2.内核的设计是优美的。内核的地位的特殊性决定着内核的执行效率必须足够高才可以响应目前计算机应用的实时性要求,为此Linux内核使用C语言和汇编的混合编程。但是我们都 知道软件执行效率和软件的可维护性很多情况下是背道而驰的。如何在保证内核高效的前提下提高内核的可维护性,这需要依赖于内核中那些“优美”的设计。 3.神奇的编程技巧。在一般的应用软件设计领域,编码的地位可能不被过度的重视,因为开发者更注重软件的良好设计,而编码仅仅是实现手段问题——就像拿斧子劈柴一样,不用太多的思考。但是这在内核中并不成立,好的编码设计带来的不光是可维护性的提高,甚至是代码性能的提升。 每个人对内核的了理解都会有所不同,随着我们对内核理解的不断加深,对其设计和实现的思想会有更多的思考和体会。因此本文更期望于引导更多徘徊在Linux内核大门之外的人进入Linux的世界,去亲自体会内核的神奇与伟大。而我也并非内核源码方面的专家,这么做也只是希望分享我自己的分析源码的经验和心得,为那些需要的人提供参考和帮助,说的“冠冕堂皇”一点,也算是为计算机这个行业,尤其是在操作系统内核方面贡献自己的一份绵薄之力。闲话少叙(已经罗嗦了很多了,囧~),下面我就来分享一下自己的Linix内核源码分析方法。 二、内核源码难不难? 从本质上讲,分析Linux内核代码和看别人的代码没有什么两样,因为摆在你面前的一般都不是你自己写出来的代码。我们先举一个简单的例子,一个陌生人随便给你一个程序,并要你看完源码后讲解一下程序的功能的设计,我想很多自我感觉编程能力还可以的人肯定觉得这没什么,只要我耐心的把他的代码从头到尾看完,肯定能找到答案,并且事实确实是如此。那么现在换一个假设,如果这个人是Linus,给你的就是Linux内核的一个模块的代码,你还会觉得依然那么 轻松吗?不少人可能会有所犹豫。同样是陌生人(Linus要是认识你的话当然不算,呵呵~)给 你的代码,为什么给我们的感觉大相径庭呢?我觉得有以下原因:

linux操作系统( 课后习题答案)复习进程

l i n u x操作系统(课后习题答案)

精品文档 收集于网络,如有侵权请联系管理员删除 1.简述linux 的内核版本号的构成。 答:由3个部分数字构成,其形式如下 Major.minor.patchlevel major :表示主版本号,通常在一段时间内比较稳定。minor :表示次版本号,如果是偶数,代表这个内核版本是正式版本,可以公开发行;而如果是奇数,则代表这个内核版本是测试版本,还不太稳定仅供测试。patchlevel :表示修改号,这个数字越大,则表明修改的次数越多,版本相对更完善。 2.如何理解linux 发行版本含义?它由哪些基本软件构成? 答:linux 的基础是其内核,但光有内核是无法满足用户需要的,必须构成发行套件,即发行版。 系统引导管理程序(Boot Manager 、用户界面、X-Window 系统、系统管理、Internet 服务、文件和打印服务、应用程序、工具和库程序 3.linux 的运用领域主要有哪些?答: Intranet 、服务器、嵌入式系统、集群计算机等方面 4.Linux 主要特点。 答:多用户、多任务、多平台、漂亮的用户界面 、硬件支持、强大的通信和联网功能 、应用程序支持 4.X Window 由哪3个部分组成?分别有何功能? 答:Server (服务器)、Client (客服端)、通信通道 Server :控制实际显示器和输入设备的程序。Client :Client 是使用系统窗口功能的一些应用程序。通信通道:负责Server 与Client 之间的信息传输。 5.什么是桌面环境?linux 下的桌面环境主要有哪两种? 答:为用户管理系统、配置系统、运行应用程序等提供统一的操作平台。Linux 最常用的桌面环境:KDE 和GNOME 。 6.在GNOME 桌面环境下如何获取帮助信息? 答:(1)GNOME 桌面环境提供帮助浏览器程序help ,单击【主菜单】——【帮助】命令即可启动,单击文字链接可查看相关的联机帮助信息。(2)如果已安装文档光盘,则可单击【主菜单】——【文档】命令,选择查看已安装的文档。(3)当运行运用程序时,单击该程序的【帮助】——【目录】或者【目录内容】也可查看该程序的帮助信息。(4)默认情况下linux 所安装的每一个应用程序都会在/usr/share/doc 目录下放置该程序的帮助信息文件。因此,用户可直接浏览次目录中相关程序的帮助信息。 7.如何从GNOME 桌面环境切换到KDE 桌面环境?答:在GNOME 中启动运行命令,然后输入命令switchdesk ,打开switchdesk 桌面切换工具,然后选择KDE 命令,设置完成后需要重新启动系统才生效。 8.I/O 设备怎样分类?从资源的角度来看,I/O 设备可分为哪几类设备? 答:按设备的所属关系两类:系统设备 、用户设备 。按设备的信息交换的单位两类:字符设备 、块设备。 按设备的共享属性可分为三类:独占设备 、共享设备 、虚拟设备 。 9.设备管理的目标和功能是什么? 答: 选择和分配I/O 设备以便进行数据传输操作。控制I/O 设备和CPU (或内存)之间交换数据。为用户提供一个友好的透明接口,把用户和设备硬件特性分开,使得用户在编制应用程序时不必涉及 具体设备,由系统按用户的要求来对设备的工作进行控制。提高设备和设备之间、CPU 和设备之间以及进程和进程之间的并行操作程度,以使操作系统获得最佳效率。 功能:提供和进程管理系统的接口、进行设备分配 、实现设备和设备、设备和CPU 等之间的并行操作 、进行缓冲管理 、设备控制与驱动 10.什么是DMA 方式?简述采用DMA 方式进行数据传输的过程。 答:DMA 方式是:在外部设备和内存之间开辟直接的数据交换通路。(1)外设可通过DMA 控制器向CPU 发出DMA 请求: (2)CPU 响应DMA 请求,系统转变为DMA 工作方式,并把总线控制权交给DMA 控制器; (3)由DMA 控制器发送存储器地址,并决定传送数据块的长度; (4)执行DMA 传送; (5)DMA 操作结束,并把总线控制权交还CPU 。 11.什么叫通道技术?通道的作用是什么? 答:通道是独立于中央处理器的,专门负责数据I/O 传输工作的理单元。通道对外部设备实行统一管理,它代替CPU 对I/O 操作进行控制,从而使CPU 和外部设备可以并行工作。所以通道又称为I/O 处理机。 12.什么是缓冲?为什么要引入缓冲?答:缓冲技术是用在外部设备与其他硬件部件之间的一种数据暂存技术,它利用存储器件在外部设备中设置了数据的一个存储区域,称为缓冲区。引入缓冲区的主要原因:(1)缓和CPU 与I/O 设备间速度不匹配的矛盾。(2)减少对CPU 的中断频率,放宽对CPU 中断响应时间的限制。(3)提高CPU 和I/O 设备之间的并行性。 13.linux 中用户可分为哪几种类型,有何特点?答:分为下面两种类型:1、用户帐号:所谓的“用户”可以是实际的人员。每个用户帐号都包含一个惟一的识别码(User ID,UID )以及组群识别码(Group ID,GID )。 2、组群帐号:所谓的“组群”是一种逻辑性的单位,主要集合特定的用户,并授予所有组群成员文件相同的权限,如读取、写入、或运行 14.linux 用哪些属性信息来说明一个用户账号? 答:登录名、口令、用户标识号、组标识号、用户名、用户主目录、命令解释程序。 15.创建一个用户账号需要哪些步骤? 答:比如,需要创建一个用户账号user02,主目录为/home/user02,登录时使用bash 作为其shell 程序。可以使用以下命令:# useradd -d /home/user02 -s /bin/bash user02 要添加新用户,点击「添加用户」按钮。一个如右图所示的窗口就会出现。在适当的字段内键入新用户的用户名和全称。在「口令」和「确认口令」字段内键入口令。口令必须至少有六个字符。 16.linux 用哪些属性信息来说明一个用户组?答:组名、口令、组标识号、用户列表。 17.linux 用户账号管理上有哪些常用的命令? 答:显示自身的用户名—whoami 、显示当前所有用户登录信息—w 、显示当前所有用户登录信息—who 、查找并显示用户信息—

相关主题
文本预览
相关文档 最新文档