softirq整理
(整理)rt_thread 的定时器管理源码分析.

1 前言rt-thread可以采用软件定时器或硬件定时器来实现定时器管理的,所谓软件定时器是指由操作系统提供的一类系统接口,它构建在硬件定时器基础之上,使系统能够提供不受数目限制的定时器服务。
而硬件定时器是芯片本身提供的定时功能。
一般是由外部晶振提供给芯片输入时钟,芯片向软件模块提供一组配置寄存器,接受控制输入,到达设定时间值后芯片中断控制器产生时钟中断。
硬件定时器的精度一般很高,可以达到纳秒级别,并且是中断触发方式。
软件定时器的精度取决于它使用的硬件定时器精度。
而rt-thread 操作系统在默认情况下是采用的硬件定时器的方式,用户可以通过修改宏定义#ifdefRT_USING_TIMER_SOFT来修改采用哪种。
2 rt-thread的定时器的基本工作原理在RT-Thread定时器模块维护两个重要的全局变量,一个是当前系统的时间rt_tick(当硬件定时器中断来临时,它将加1),另一个是定时器链表rt_timer_list,系统中新创建的定时期都会被以排序的方式插入到rt_timer_list(硬件定时器模式下使用)链表中,rt_timer_list的每个节点保留了一个定时器的信息,并且在这个节点加入链表时就计算好了产生时间到达时的时间点,即tick,在rt-thread系统中如果采用软件定时器模式,则存在一定时器线程rt_thread_timer_entry,不断获取当前TICK值并与定时器链表rt_timer_list上的定时器对比判断是否时间已到,一旦发现就调用对应的回调函数,即事件处理函数进行处理,而如果采用硬件定时器管理模式的话,则该检查过程放到系统时钟中断例程中进行处理,此时,是不存在定时器线程的。
如下图:注:如果采用软件定时器软件定时器,则该定时器链表为rt_soft_timer_list。
3 源码分析3.1 数据定义[cpp]view plaincopyprint?1. /**2. * timer structure3. */4. struct rt_timer5. {6. struct rt_object parent; //内核对象7.8. rt_list_t list; //链表节点9.10. void (*timeout_func)(void *parameter); //定时器超时例程11. void *parameter; //定时器例程的传入参数12.13. rt_tick_t init_tick; //定时器的超时时间,即总共多长时间将产生超时事件14. rt_tick_t timeout_tick; //定时器超时的时间点,即产生超时事件时那一该的时间点15. };16. t ypedef struct rt_timer *rt_timer_t;3.2 rt-thread的软件定时器模式软件定时器线程初始化及启动:[cpp]view plaincopyprint?1. /**2. * @ingroup SystemInit3. *4. * This function will initialize system timer thread5. */6. void rt_system_timer_thread_init(void)7. {8. #ifdef RT_USING_TIMER_SOFT//如果采用软件定时器管理模式,则启动定时器线程9. rt_list_init(&rt_soft_timer_list);//初始化软件定时器链表10.11. /* start software timer thread */12. rt_thread_init(&timer_thread,//初始化软件定时器线程,并启动13. "timer",14. rt_thread_timer_entry,15. RT_NULL,16. &timer_thread_stack[0],17. sizeof(timer_thread_stack),18. RT_TIMER_THREAD_PRIO,19. 10);20.21. /* startup */22. rt_thread_startup(&timer_thread);23. #endif24. }软件定时器线程如下:[cpp]view plaincopyprint?1. /* system timer thread entry */2. static void rt_thread_timer_entry(void *parameter)3. {4. rt_tick_t next_timeout;5.6. while (1)7. {8. /* get the next timeout tick */9. next_timeout = rt_timer_list_next_timeout(&rt_soft_timer_list);//得到软件定时器链表上的下一个定时器的超时时间点10. if (next_timeout == RT_TICK_MAX)//如果超过范围,则挂起当前线程,继续线程调度11. {12. /* no software timer exist, suspend self. */13. rt_thread_suspend(rt_thread_self());14. rt_schedule();15. }16. else17. {18. rt_tick_t current_tick;19.20. /* get current tick */21. current_tick = rt_tick_get();//获取当前时间点22.23. if ((next_timeout - current_tick) < RT_TICK_MAX/2)//离下个中断时间点还差些时候24. {25. /* get the delta timeout tick */26. next_timeout = next_timeout - current_tick;//计算还差多长时间27. rt_thread_delay(next_timeout);//休眠一段时间28. }29. }30.31. /* lock scheduler */32. rt_enter_critical();//时间到,进入临界区33. /* check software timer */34. rt_soft_timer_check();//检查是否该产生超时事件35. /* unlock scheduler */36. rt_exit_critical();//退出临界区37. }38. }检查是否产生中断函数rt_soft_timer_check函数如下定义:[cpp]view plaincopyprint?1. /**2. * This function will check timer list, if a timeout event happens, the3. * corresponding timeout function will be invoked.4. */5. void rt_soft_timer_check(void)6. {7. rt_tick_t current_tick;8. rt_list_t *n;9. struct rt_timer *t;10.11. RT_DEBUG_LOG(RT_DEBUG_TIMER, ("software timer check enter\n"));12.13. current_tick = rt_tick_get();//得到当前时间点14.15. for (n = rt_soft_timer_list.next; n != &(rt_soft_timer_list);)//得到下一定时器节点16. {17. t = rt_list_entry(n, struct rt_timer, list);//t指向rt_timer定时器18.19. /*20. * It supposes that the new tick shall less than the half duration of21. * tick max.22. */23. if ((current_tick - t->timeout_tick) < RT_TICK_MAX / 2)//如果当前的时间点超过定时器的超时时间点24. {25. RT_OBJECT_HOOK_CALL(rt_timer_timeout_hook, (t));//使用钩子函数26.27. /* move node to the next */28. n = n->next;//指向下一定时器29.30. /* remove timer from timer list firstly */31. rt_list_remove(&(t->list));//移除当前定时器32.33. /* call timeout function */34. t->timeout_func(t->parameter);//产生定时器超时事件,调用对应处理函数35.36. /* re-get tick */37. current_tick = rt_tick_get();//再次获取当前时间点38.39. RT_DEBUG_LOG(RT_DEBUG_TIMER, ("current tick: %d\n", current_tick));40.41. if ((t->parent.flag & RT_TIMER_FLAG_PERIODIC) &&//如果当前定时器是周期性定时器,则将其再次按序放入软件定时器链表42. (t->parent.flag & RT_TIMER_FLAG_ACTIVATED))43. {44. /* start it */45. t->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;//置标志为非激活状态46. rt_timer_start(t);//再次将定时器t放入软件定时器链表末尾47. }48. else49. {50. /* stop timer */51. t->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;//置标志为非激活状态52. }53. }54. else break; /* not check anymore */55. }56.57. RT_DEBUG_LOG(RT_DEBUG_TIMER, ("software timer check leave\n"));58. }上面代码中,为什么定时器里判断超时的条件是((current_tick - t→timeout_tick) < RT_TICK_MAX/2)?因为系统时钟溢出后会自动回绕。
记一次内核Softlockup分析【转】

记⼀次内核Softlockup分析【转】简介: softlockup 分析除⽐较常见的内核 panic 与 soft lockup 外,普通的内核死锁可能并不会对操作系统产⽣致命的影响,例如马上要分析到的这个 case —— 某个运维同学发现在 ECS 上执⾏ top 并按下 c 后会 hang 住,且⽆法响应任何命令。
经过观察,在 top 中按下 c 是打开/关闭进程启动时的完整命令,由于只是 top 进程 hang,新建⼀个 shell 可以观察到 top 进程处于 UN 状态,查看 stack 实际上是由于 rwsem_down_read_failed 被调度⾛了。
rwsem_down_read_failed 是尝试读取 rw_semaphore 信号量失败时会调⽤的函数,因此关键在于这个信号量具体是什么?⼜是谁拿⾛了这个信号量?话不多说,直接上 core。
信号量地址推导core ⾥抓到了好⼏个 UN 状态的 top,随便找⼀个看,是在从 proc ⽂件系统中读取 /proc/4424/cmdlinecrash> btPID: 28968 TASK: ffff88041a820fb0 CPU: 3 COMMAND: "top"#0 [ffff880387b8bd28] __schedule at ffffffff8168c1a5#1 [ffff880387b8bd90] schedule at ffffffff8168c7f9#2 [ffff880387b8bda0] rwsem_down_read_failed at ffffffff8168e1a5#3 [ffff880387b8be08] call_rwsem_down_read_failed at ffffffff81327618#4 [ffff880387b8be58] down_read at ffffffff8168b980#5 [ffff880387b8be70] proc_pid_cmdline_read at ffffffff8126f712#6 [ffff880387b8bf00] vfs_read at ffffffff811fe86e#7 [ffff880387b8bf38] sys_read at ffffffff811ff43f#8 [ffff880387b8bf80] system_call_fastpath at ffffffff81697809RIP: 00007f83249077e0 RSP: 00007fff1f5c99e8 RFLAGS: 00000246RAX: 0000000000000000 RBX: ffffffff81697809 RCX: ffffffffffffffffRDX: 0000000000020000 RSI: 0000000000c07700 RDI: 0000000000000009RBP: 0000000000020000 R8: 00007f8324866988 R9: 0000000000000012R10: 0000000000000007 R11: 0000000000000246 R12: 0000000000000000R13: 0000000000c07700 R14: 0000000000000000 R15: 0000000000c07700ORIG_RAX: 0000000000000000 CS: 0033 SS: 002bcrash> filesPID: 28968 TASK: ffff88041a820fb0 CPU: 3 COMMAND: "top"ROOT: / CWD: /rootFD FILE DENTRY INODE TYPE PATH0 ffff8804c0f47900 ffff88017f80ad80 ffff8807e05a7028 CHR /dev/tty11 ffff8804c0f47900 ffff88017f80ad80 ffff8807e05a7028 CHR /dev/tty12 ffff8804bfadbc00 ffff88017f80a240 ffff8807e05a4850 CHR /dev/null3 ffff8804c0f47900 ffff88017f80ad80 ffff8807e05a7028 CHR /dev/tty14 ffff8804bfadb400 ffff880449bc18c0 ffff8802e1bad750 REG /proc/stat5 ffff8804bfadb000 ffff8807dc5bf980 ffff88048fbfdf00 REG /proc/uptime6 ffff8803d3217200 ffff8807dc5befc0 ffff88048fbfd750 REG /proc/meminfo7 ffff8800686c5200 ffff8802e290c240 ffff8802e290ae60 REG /proc/loadavg8 ffff8800686c5000 ffff88017f808240 ffff88017f80c040 DIR /proc/9 ffff8804bf16c400 ffff8806afc70900 ffff8805366f1f00 REG /proc/4424/cmdline可以看到是 proc_pid_cmdline_read 在 down_read 的时候失败了,相关代码在 238 ⾏:203 static ssize_t proc_pid_cmdline_read(struct file *file, char __user *buf,204 size_t _count, loff_t *pos)205 {206 struct task_struct *tsk;207 struct mm_struct *mm;208 char *page;209 unsigned long count = _count;210 unsigned long arg_start, arg_end, env_start, env_end;211 unsigned long len1, len2, len;212 unsigned long p;213 char c;214 ssize_t rv;215216 BUG_ON(*pos < 0);217218 tsk = get_proc_task(file_inode(file));219 if (!tsk)220 return -ESRCH;221 mm = get_task_mm(tsk);222 put_task_struct(tsk);223 if (!mm)224 return 0;225 /* Check if process spawned far enough to have cmdline. */226 if (!mm->env_end) {227 rv = 0;228 goto out_mmput;229 }230231 page = (char *)__get_free_page(GFP_TEMPORARY);232 if (!page) {233 rv = -ENOMEM;234 goto out_mmput;235 }236237 down_read(&mm->mmap_sem);238 arg_start = mm->arg_start;239 arg_end = mm->arg_end;240 env_start = mm->env_start;241 env_end = mm->env_end;242 up_read(&mm->mmap_sem);......有多种⽅法可以找到这⾥的 &mm->mmap_sem。
Linux内核分析之调度算法

Linux内核分析之调度算法inux调度算法在2.6.32中采用调度类实现模块式的调度方式。
这样,能够很好的加入新的调度算法。
linux调度器是以模块方式提供的,这样做的目的是允许不同类型的进程可以有针对性地选择调度算法。
这种模块化结构被称为调度器类,他允许多种不同哦可动态添加的调度算法并存,调度属于自己范畴的进程。
每个调度器都有一个优先级,调度代码会按照优先级遍历调度类,拥有一个可执行进程的最高优先级的调度器类胜出,去选择下面要执行的那个程序。
linux上主要有两大类调度算法,CFS(完全公平调度算法)和实时调度算法。
宏SCHED_NOMAL主要用于CFS调度,而SCHED_FIFO和SCHED_RR主要用于实时调度。
如下面的宏定义:1./*2.* Scheduling policies3.*/4./*支援Real-Time Task的排程,包括有SCHED_FIFO與SCHED_RR.5.*/6.7./*(也稱為SCHED_OTHER): 主要用以排程8.一般目的的Task.*/9.#define SCHED_NORMAL 010.#define SCHED_FIFO 111./*task預設的Time Slice長度為100 msecs*/12.#define SCHED_RR 213./*主要用以讓Task可以延長執行的時間14.(Time Slice),減少被中斷發生Task Context-Switch15.的次數.藉此可以提高Cache的利用率16.(每次Context-Switch都會導致Cache-Flush). 比17.較適合用在固定週期執行的Batch Jobs任18.務主機上,而不適合用在需要使用者互19.動的產品(會由於Task切換的延遲,而20.感覺到系統效能不佳或是反應太慢).*/21.#define SCHED_BATCH 322./* SCHED_ISO: reserved but not implemented yet */23./*為系統中的Idle Task排程.*/24.#define SCHED_IDLE 5linux调度算法实现的高层数据结构主要有运行实体、调度类、运行队列,下面我们主要看看这几个数据结构的字段和意义。
Request_irq和setup_irq的区别

Linux 内核提供了两个注册中断处理函数的接口:setup_irq和request_irq。
这两个函数都定义在kernel/irq/manage.c里。
/** Internal function to register an irqaction - typically used to* allocate special interrupts that are part of the architecture.*/int setup_irq(unsigned int irq, struct irqaction *new);/** request_irq - allocate an interrupt line* This call allocates interrupt resources and enables the* interrupt line and IRQ handling.*/int request_irq(unsigned int irq,irqreturn_t (*handler)(int, void *, struct pt_regs *),unsigned long irqflags, const char *devname, void *dev_id)这两个函数有什么样的区别呢?先看看setup_irqSetup_irq通常用在系统时钟(GP Timer)驱动里,注册系统时钟驱动的中断处理函数。
下面举个列子, 如s3c2410 timer驱动:/* arch/arm/mach-s3c2410/time.c */static struct irqaction s3c2410_timer_irq = {.name = "S3C2410 Timer Tick",.flags = IRQF_DISABLED | IRQF_TIMER,.handler = s3c2410_timer_interrupt,};static void __init s3c2410_timer_init (void){s3c2410_timer_setup();setup_irq(IRQ_TIMER4, &s3c2410_timer_irq);}struct sys_timer s3c24xx_timer = {.init = s3c2410_timer_init,.offset = s3c2410_gettimeoffset,.resume = s3c2410_timer_setup};struct sys_timer s3c24xx_timer = {.init = s3c2410_timer_init,.offset = s3c2410_gettimeoffset,.resume = s3c2410_timer_setup};可以看到,setup_irq的使用流程很简单。
名词解释 软中断

名词解释软中断
软中断是一种在操作系统内核中使用的机制,用于处理与硬件设备无关的事件和任务。
它是一种特殊类型的中断,与传统的硬件中断不同,软中断是由操作系统软件触发和处理的。
软中断的主要作用是实现一些操作系统内核的功能,例如处理网络数据包、定时器管理、文件系统操作等。
软中断通常由内核中的某个特定线程或函数调用触发,并在内核上下文中运行。
软中断的触发可以是由于某个特定事件的发生,也可以是通过系统调用或内核函数调用来触发。
一旦软中断被触发,操作系统内核会立即中断当前进程的执行,并跳转到软中断处理程序中执行相应的任务。
与硬件中断相比,软中断的处理过程是在内核上下文中进行的,因此它具有更高的特权级别,并能够直接访问和操作操作系统内核的数据结构和资源。
另外,软中断的执行过程通常比硬件中断的处理过程更快,因为它不需要涉及硬件设备的处理。
软中断的设计使得操作系统能够更好地管理和调度各种任务和事件,提高系统的响应性能和效率。
它为操作系统提供了一种灵活和可扩展的机制,可以快速响应各种事件并进行相应的处理。
总之,软中断是操作系统内核中一种用于处理与硬件设备无关的事件和任务的机制,它允许操作系统快速响应和处理各种事件,并提供了一种灵活和可扩展的方式来管理系统的任务和资源。
AIX+下的+core+dump+分析

本文简要介绍了AIX 平台下core dump 产生的原理以及相关定位方法。
Core dump 基本知识本节主要探讨core dump 产生的背景知识。
对这部分不感兴趣的读者可以直接阅读第二章,了解基本的core dump 定位手段。
起源软件是人思维的产物。
智者千虑,必有一失,人的思维总有缺陷,反映到软件层面上就是程序bug。
程序bug 的终极体现就是core dump,core dump 是软件错误无法恢复的产物。
生成过程进程core dump 与系统dump 的产生,从程序原理上来说是基本一致的。
dump 的生成一般是在系统进行中断处理时进行的,下面简单介绍一下中断机制。
操作系统的中断机制操作系统是由中断驱动的。
广义的中断一般分为两类,中断(Interrupts) 和异常(Exceptions)。
中断可在任何时候发生,与CPU 正在执行什么指令无关,中断主要由I/O 设备、处理器时钟(分时系统依赖时钟中断划分时间片)或定时器等硬件引发,可以被允许或取消。
而异常是由于CPU 执行了某些指令引起的,可以包括存储器存取违规、除0 或者特定调试指令等,内核也将系统服务视为异常。
系统对这两类中断的处理基本上是相同的。
每个中断都会唯一对应到一个中断处理程序,在该中断触发时,相应的处理程序就会被执行。
例如应用进程进行系统调用时,就会触发一个软件异常,进入中断处理函数,完成从用户态到系统态的迁移并进入相应系统调用的入口点。
应用进程coredump 也是一个类似的过程。
应用进程core dump 生成过程在进程运行出现异常行为时,例如无效地址访问、浮点异常、指令异常等,将导致系统转入内核态进行异常处理(即中断处理),向相应的进程发出特定信号例如SIGSEGV、SIGFPE、SIGILL 等。
如果应用进程注册了相应信号的处理函数(例如可通过sigaction 注册信号处理函数),则调用相应处理函数进行处理(应用程序可以选择记录信息后生成core dump 并退出);否则将采取默认动作,例如SIGSEGV 的默认动作是生成core dump 并退出程序。
Linux内核数据包处理流程
*定义一个名为e100_driver的PCI设备
* 1、设备的探测函数为e100_probe;
* 2、设备的id_table表为e100_id_table
*/
static struct pci_driver e100_driver = {
.name = DRV_NAME,
.id_table = e100_id_table,
if (!request_region(pci_resource_start(pdev, bar),
pci_resource_len(pdev, bar), res_name))
goto err_out;
}
else if (pci_resource_flags(pdev, bar) & IORESOURCE_MEM) {
void (*shutdown) (struct pci_dev *dev);
struct device_driver driver;
struct pci_dynids dynids;
};
因为在系统引导的时候,PCI设备已经被识别,当内核发现一个已经检测到的设备同驱动注册的id_table中的信息相匹配时,它就会触发驱动的probe函数,以e100为例:
printk(KERN_INFO PFX "%s, %s\n", DRV_DESCRIPTION, DRV_VERSION);
printk(KERN_INFO PFX "%s\n", DRV_COPYRIGHT);
}
return pci_module_init(&e100_driver);
}
一切顺利的话,注册的e100_probe函数将被内核调用,这个函数完成两个重要的工作:
IRQ中断处理流程
IRQ中断处理流程基于Linux2.6.30.4分析IRQ中断的处理流程。
1.中断⼊⼝/* arch/arm/kenel/entry-armv.S*/b vector_irq + stubs_offset2.vector_irqvector_stub 宏展开即为vector_irq, 参考。
/** Interrupt dispatcher*/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).long __irq_svc @ 3 (SVC_26 / SVC_32)3.__irq_user__irq_usr:usr_entry /*3.1*/kuser_cmpxchg_check#ifdef CONFIG_TRACE_IRQFLAGSbl trace_hardirqs_off#endifget_thread_info tsk /*3.2*/#ifdef CONFIG_PREEMPT/**r8<--old preempt_count*r7<--preempt_count+1*preempt_count<--r7*/ldr r8, [tsk, #TI_PREEMPT] @ get preempt countadd r7, r8, #1 @ increment itstr r7, [tsk, #TI_PREEMPT]#endifirq_handler /*3.3*/#ifdef CONFIG_PREEMPT/**r0<--new preempt_count*preempt<--old preempt_count*/ldr r0, [tsk, #TI_PREEMPT]str r8, [tsk, #TI_PREEMPT]teq r0, r7strne r0, [r0, -r0]#endif#ifdef CONFIG_TRACE_IRQFLAGSbl trace_hardirqs_on#endifmov why, #0b ret_to_user /*3.4*/UNWIND(.fnend )ENDPROC(__irq_usr)3.1__user_entry.macro usr_entryUNWIND(.fnstart )UNWIND(.cantunwind ) @ don't unwind the user space/* DEFINE(S_FRAME_SIZE, sizeof(struct pt_regs));*/sub sp, sp, #S_FRAME_SIZE /**/stmib sp, {r1 - r12}ldmia r0, {r1 - r3}add r0, sp, #S_PC @ here for interlock avoidancemov r4, #-1 @ """"""""str r1, [sp] @ save the "real" r0 copied@ from the exception stack@@ We are now ready to fill in the remaining blanks on the stack: @@ r2 - lr_<exception>, already fixed up for correct return/restart@ r3 - spsr_<exception>@ r4 - orig_r0 (see pt_regs definition in ptrace.h)@@ Also, separately save sp_usr and lr_usr@stmia r0, {r2 - r4}/*“^”符号表⽰访问user mode的寄存器*/stmdb r0, {sp, lr}^@@ Enable the alignment trap while in kernel mode@alignment_trap r0@@ Clear FP to mark the first stack frame@zero_fp.endm这⾥⾯⽤到pt_regs结构保存栈上的数据,8字节对齐/** This struct defines the way the registers are stored on the* stack during a system call. Note that sizeof(struct pt_regs)* has to be a multiple of 8.*/struct pt_regs {long uregs[18];};与之相关的宏定义如下DEFINE(S_FRAME_SIZE, sizeof(struct pt_regs));DEFINE(S_R0, offsetof(struct pt_regs, ARM_r0)); DEFINE(S_R1, offsetof(struct pt_regs, ARM_r1)); DEFINE(S_R2, offsetof(struct pt_regs, ARM_r2)); DEFINE(S_R3, offsetof(struct pt_regs, ARM_r3)); DEFINE(S_R4, offsetof(struct pt_regs, ARM_r4)); DEFINE(S_R5, offsetof(struct pt_regs, ARM_r5)); DEFINE(S_R6, offsetof(struct pt_regs, ARM_r6)); DEFINE(S_R7, offsetof(struct pt_regs, ARM_r7)); DEFINE(S_R8, offsetof(struct pt_regs, ARM_r8)); DEFINE(S_R9, offsetof(struct pt_regs, ARM_r9)); DEFINE(S_R10, offsetof(struct pt_regs, ARM_r10)); DEFINE(S_FP, offsetof(struct pt_regs, ARM_fp)); DEFINE(S_IP, offsetof(struct pt_regs, ARM_ip)); DEFINE(S_SP, offsetof(struct pt_regs, ARM_sp)); DEFINE(S_LR, offsetof(struct pt_regs, ARM_lr)); DEFINE(S_PC, offsetof(struct pt_regs, ARM_pc)); DEFINE(S_PSR, offsetof(struct pt_regs, ARM_cpsr)); DEFINE(S_OLD_R0, offsetof(struct pt_regs, ARM_ORIG_r0)); #define ARM_cpsr uregs[16]#define ARM_pc uregs[15]#define ARM_lr uregs[14]#define ARM_sp uregs[13]#define ARM_ip uregs[12]#define ARM_fp uregs[11]#define ARM_r10 uregs[10]#define ARM_r9 uregs[9]#define ARM_r8 uregs[8]#define ARM_r7 uregs[7]#define ARM_r6 uregs[6]#define ARM_r5 uregs[5]#define ARM_r4 uregs[4]#define ARM_r3 uregs[3]#define ARM_r2 uregs[2]#define ARM_r1 uregs[1]#define ARM_r0 uregs[0]#define ARM_ORIG_r0 uregs[17]macos3.2 get_thread_info tsktsk即r9寄存器的别名,内核中为寄存器声明的别名如下/** These are the registers used in the syscall handler, and allow us to* have in theory up to 7 arguments to a function - r0 to r6.** r7 is reserved for the system call number for thumb mode.** Note that tbl == why is intentional.** We must set at least "tsk" and "why" when calling ret_with_reschedule.*/scno .req r7 @ syscall numbertbl .req r8 @ syscall table pointerwhy .req r8 @ Linux syscall (!= 0)tsk .req r9 @ current thread_infoget_thread_info tsk的作⽤是获取sp地址保存在tsk(r9)中,即r9中保存当前任务的thread_info结构的地址。
virtio_net数据包的收发
virtio-net数据包的收发virtio设备创建vring的创建流程Ring的内存分布发送接收virtio设备创建在virtio设备创建过程中,形成的数据结构如图所示:从图中可以看出,virtio-netdev关联了两个virtqueue,包括一个send queue和一个receive queue,而具体的queue的实现由vring来承载。
针对virtqueue 的操作包括:int virtqueue_add_buf( struct virtqueue *_vq, struct scatterlist sg[], unsigned int out, unsigned int in, void *data, gfp_t gfp)add_buf()用于向queue中添加一个新的buffer,参数data是一个非空的令牌,用于识别buffer,当buffer内容被消耗后,data会返回。
virtqueue_kick()Guest 通知host 单个或者多个buffer 已经添加到queue 中,调用virtqueue_notify(),notify 函数会向queue notify(VIRTIO_PCI_QUEUE_NOTIFY)寄存器写入queue index 来通知host。
void *virtqueue_get_buf(struct virtqueue *_vq, unsigned int *len)返回使用过的buffer,len为写入到buffer中数据的长度。
获取数据,释放buffer,更新vring描述符表格中的index。
virtqueue_disable_cb()Guest不再需要知道一个buffer已经使用了,也就是关闭device的中断。
驱动会在初始化时注册一个回调函数,disable_cb()通常在这个virtqueue回调函数中使用,用于关闭再次的回调函数调用。
virtqueue_enable_cb()与disable_cb()刚好相反,用于重新开启设备中断的上报。
软中断和硬中断(转)
软中断和硬中断(转)1、中断:通常被定义成⼀个事件,该事件改变处理器执⾏的指令顺序。
这样的事件与cpu芯⽚外部电路产⽣的电信号相对应。
2、中断的产⽣:每个能够发出中断请求的硬件设备控制器都有⼀条称为IRQ的输出线(中断线)。
所有的IRQ线都与⼀个中断控制器的输⼊引脚相连,中断控制器与cpu的intr引脚相连。
3、中断向量:每个中断由0-255之间的⼀个8位数来标识。
称为中断向量。
4、中断描述符表:IDT是⼀个系统表,它与每⼀个中断或者异常向量相联系,每⼀个向量在表中有相应的中断处理程序的⼊⼝地址。
cpu的idtr寄存器执⾏IDT表的物理基地址。
5、中断的硬件处理:在内核被init进程初始化后,cpu运⾏在保护模式下。
当执⾏⼀条指令后,sc和eip这对寄存器包含了下⼀条将要执⾏的指令的逻辑地址。
在执⾏这条指令之前,cpu控制单元会检查在运⾏前⼀条指令时是否发⽣了⼀个中断。
如果发⽣了,cpu控制单元处理中断。
软中断:软中断是利⽤硬件中断的概念,⽤软件⽅式进⾏模拟,实现宏观上的异步执⾏效果。
硬中断是外部设备对cpu的中断。
软中断通常是硬中断服务程序对内核的中断。
(中断服务程序和中断处理程序不同)信号则是由内核或者其他进程对某个进程的中断。
理解:因为每个进程空间或者线程空间都是在⼀定程度上相对逻辑独⽴的,类似于外部设备DMA相对于cpu。
所以从理论上说,是可以实现中断的。
扩展:信号量与消息队列都是提供给⽤户程序的,是内核服务的⼀种封装。
进程内核栈和⽤户栈:每个进程都有两个栈:⽤户栈和内核栈。
当进程在⽤户空间运⾏时,cpu堆栈指针寄存器⾥⾯的内容是⽤户堆栈地址。
同理,进程在内核空间运⾏时,eip值是内核栈空间地址。
进程⽤户栈和内核栈的切换,当进程因为中断或者系统调⽤⽽陷⼊内核态执⾏时,进程所使⽤的堆栈也要从⽤户栈转到内核栈。
检查信号中断时在内核态即将进⼊⽤户态的时候,⽽不是任何时候都检查的。
软中断时软件实现的中断,也就是程序运⾏时其他程序对它的中断。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
侧重于网络的软中断
一. 初始化
软中断有以上几种类型,最常见的是tasklet,它使用HI_SOFTIRQ和
TASKLET_SOFTIRQ,;而我们网络代码使用的是 NET_TX_SOFTIRQ
&&NET_RX_SOTFIRQ,顾名思义,他们分别用于数据包的发送和接收。在下文中我
们将他们叫做”softirq的类型“。
我们网络所用softirq初始化的代码在 /net/core/Dev.c 中的 net_dev_init函
数中,下面截取关键部分。
上面的函数是为了兼容老的没有使用NAPI的代码,用了NAPI后处理缓存区
主要使用napi结构里的poll方法;为了照顾旧代码同时还带有一些NAPI的特征,
这里就把queue->backlog.poll = process_backlog。
还有个很重要的结构体 “struct softnet_data ”。为了应对多核处理器,每个
cpu都有一个softnet_data结构体,用于存放每个CPU独立的softirq信息。结构
体里的struct napi_struct 结构是为了兼容老代码,而新代码中net_device中的
私有数据会有这个结构体。
Open_softirq只做了一件事儿,“softirq_vec[nr].action = action;”注册
NET_TX_SOFTIRQ类型的软中断处理函数。
二.调用
In_interrupt:如果CPU正在服务于一个硬件中断或者softirq,或者抢占功
能是关闭的,返回TRUE。
In_softirq: 如果CPU正在服务于一个软件中断,返回TRUE。
1. 产生未决(pending)软中断
我们从网卡的收报文过程来分析,如何产生的pending状态的软中断。
1) 硬件中断。
2)在do_irq中调用硬件的中断处理函数,在e1000e中,这个中断
处理函数为 “static irqreturn_t e1000_intr(int irq, void *data)”。
在e1000_intr的最后会调用以上函数。
Napi_schedule_prep会检查napi的状态
(NAPI_STATE_SCHED, /* Poll is scheduled */
NAPI_STATE_DISABLE, /* Disable pending */)检查napi实例
(每个napi_struct 在此称为一个实例)是否已经调度,或者是
disable的。
禁止中断,之后将此napi_struct 实例挂在某一个CPU的 softnet_data
的poll_list链表上,之后执行__rase_softirq_irqoff(NET_RX_SOFTIRQ)
它将NET_RX_SOFTIRQ类型在pending 位图
2. 处理pending 中的软中断
下面看看do_softirq函数,此函数负责处理pengding的软中断。
进入__do_softirq继续分析。
一旦所所有类型softirq处理函数都处理完,它会再次检查是否有任何
新的软中断又进入了调度(前边pengding 清零之后,硬件do_IRQ又触发
NET_RX_SOFTIRQ事件),如果有至少一个未决的软中断,那么回重复上述
流程,最多重复MAX_SOFTIRQ_RESTART次。(担心高流量的时候会一直执
行,一直占用CPU)
3. do_softirq会在何处执行呢(在何处清理pending软中断呢)
1) 在 硬件中断do_Irq 中的irq_exit里。
2) Ksoftirqd内核线程中。(understanding networt 209)
软中断处理程序执行的时候,允许响应中断,但是它自己不能休眠。
在一个处理程序运行的时候,当前处理器上的软中断被禁止。但其它处理器仍可
执行别的软中断。实际上,如果同一个软中断在它被执行的时候再次被触发了,
那么另一个处理器可以同时运行其处理程序。这意味着任何共享数据---甚至是仅
在软中断处理程序内部使用的全局变量---都需要严格的锁保护。这点很麻烦所以
tasklet更被青睐。
而tasklet的特点是,同一个程序的多个实例(也就是两个CPU不能同时执
行一个程序)不能再多个处理器上同时运行。