linux kernel 学习笔记
主要是这书:《linux内核设计与实现》还有几本别的
---------------
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 | 自下而上子系统: ---------------------------------------------- 系统调用 | IPC ---------------------------------------------- 内存管理 | 虚拟文件系统 | 网络协议栈 ---------------------------------------------- 进程调度 | 中断调度 | 同步/timer ---------------------------------------------- ## 指令/汇编 | ## 设备驱动 ---------------------------------------------- boot: 0x7C00/保护模式 ----> 分段/分页,物理地址/逻辑地址/虚拟地址 ---------------------------------------------- 保护模式: 实模式与保护模式的主要区别在于“内存管理”和“系统安全性” 1 实模式20位地址总线,只能访问1M内存。 2 实模式:段地址+段偏移。保护模式:段选择子+段偏移(GDT)。 3 执行虚拟内存与分页机制。 4 支持指令级任务管理和特权级。 切换步骤: 1. 设置GDT。 2. 设置PE位。 3. 代码跳转。 MMU = 分段单元 + 分页单元 权限位在段描述符里,MMU负责权限判定。 二级页表之所以剩内存是因为第二级是按需动态分配的。 ***??? 为什么要分段 进程调度: ***** 状态机??***** 运行队列,等待队列。 ** 进程调度的时机:** 硬中断处理结束的时候: 在interrupt数组调用完do_IRQ()函数,然后返回到内核空间时调用。 这里有两个条件: 1. 必须是32-256的中断,因为依赖interrupt框架。 2. 如果是自己写idt注册的硬中断就不调用,比如用_set_gate()函数。 3. 使用desc描述符即request_irq()接口的中断,都满足条件1. 见代码: entry_64.S::interrupt() 好几个出口,例:retint_kernel()->preempt_schedule_irq() do_IRQ()函数并不调用: preemt_enable函数会调schedule(),但是2.6的代码这里并不调。 比如在irq_exit()中专门调用了preempt_enable_no_resched()。 软中断处理结束的时候: 框架也不调用。 软中断的处理进程softirqd会调。 处理时钟中断的时候:依赖硬中断框架。 时钟中断代码逻辑里会调用 scheduler_tick(),这地方只是设置need_sched标记,并不做调度。 系统调用结束的时候:这里会调用schedule()。 entry_64.S::system_call() 其他系统事件或中断触发的时候:很多地方会调用,比如 网卡收包的软中断处理里。 总结: 1. 32~256号硬中断返回内核空间时。 2. 系统调用返回用户空间时。 3. 其他业务相关的系统事件或中断触发时(非常多),比如:网卡收包的软中断处理里。 如果用户进程没有系统调用,所在CPU也没绑定中断,操作系统如何调度? 该CPU的时钟中断返回时。单CPU上的时钟中断可以关吗?? 硬中断: 一共256个中断号。外部中断从0x20(32)开始,除0x80(128)是系统调用外。 数据结构: x86架构 irq_desc数组在哪定义的: kernel/irq/handle.c:242 有红黑树和数组两个实现,取决于一个宏,数组是更通用的架构无关实现。 idt_table 定义在 arch/x86/kernel/head_64.S interrupt数组定义在 /arch/x86/kernel/entry_64.S:755,数组长度是32~256,每一个函数都初始化为do_IRQ() used_vectors 已注册硬中断的bitmap标记。长度256个bit vector_irq 每CPU的,长度256的 int 数组。初值是-1 用于存储中断号与中断向量号之间的映射。 IDT初始化: cpu_init()->load_idt(&idt_descr) --> idt_table idt_table是per cpu的。 idt_table的内容值由 _set_gate()函数写入。 执行函数: do_IRQ() 【所有外部中断,默认全进这个函数,然后查irq_desc】 interrupt()-> commo->interrupt()->do_IRQ()->handle_irq() ->__do_IRQ() 执行 irq_desc的action->handler() 或者 -> desc->handle_irq()->handle_level_irq()/handle_edge_irq() handle_level_irq()/handle_edge_irq() 这两个handler主要用于处理与“中断控制器的交互” 最终还是要调用__do_IRQ()。 初始化函数:trap_init() init_IRQ() -> native_init_IRQ() -> init_ISA_irqs() 前16个desc数组设置成chip8259A -> apic_intr_init() -> 从32开始并且没设回调的赋值interrupt数组给IDT,也就是do_IRQ()。 ACPI IO_ACPI 2个 for 循环注册,setup_IO_APIC()->setup_IO_APIC_irqs()->set_irq_chip_and_handler_name( handle_edge_irq ) 8259A set_irq_chip_and_handler_name(irq, &i8259A_chip, handle_level_irq APIC_init_uniprocessor() 网卡中断是怎么注册到中断控制器里去的??? 安装中断处理程序:request_irq() 注册handler函数给action->handler() 动态分配一个irq编号:create_irq_nr() *中断处理程序可以抢占下半部和进程上下文。 CPU对中断的处理是用轮询的方式,即不断查询是否有中断到来。CPU在每一个指令执行之后都会查一下。 https: //www.cnblogs.com/upnote/p/15646121.html 软中断: 两个关键数据结构: 1. 保存irq action的数组 struct softirq_action softirq_vec[NR_SOFTIRQS] // NR_SOFTIRQS = 10 2. 每核变量 irq_stat->__softirq_pending; 用位标记对应的irq是否被触发。raise一个irq就会置这个位。 所有软中断: cat /proc/softirqs 就能看见 HI_SOFTIRQ=0, TIMER_SOFTIRQ, NET_TX_SOFTIRQ, NET_RX_SOFTIRQ, BLOCK_SOFTIRQ, BLOCK_IOPOLL_SOFTIRQ, TASKLET_SOFTIRQ, SCHED_SOFTIRQ, HRTIMER_SOFTIRQ, RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */ NR_SOFTIRQS 执行契机: run_ksoftirqd 线程内。 irq_exit() 中断退出时。 执行函数:__do_softirq() 注册函数:open_softirq() -> 给softirq_vec[]数组(长度10)赋值。 跟INT指令是什么关系? 跟INT指令没关系。 软中断到达是进程上下文,还是中断上下文? 如果是进程上下文,是哪个进程的上下文? 下半部禁用? local_hb_disable() *下半部可以抢占进程上下文。 软中断不会抢占另一个软中断。 tasklet: 调度函数:tasklet_schedule, tasklet_hi_schedule 或者叫激活/注册,tasklet会执行一次,然后结束,而不是循环执行。 handler: tasklet_action, tasklet_hi_action (用open_softirq注册成了softirq) 执行一次之后就从list里清除了。 同类tasklet不会同时执行。 workqueue: kernel/workqueue.c 初始化 init_workqueues() 线程主函数 worker_thread() 激活 schedule_work() 同步: 原子 单指令都是原子的,如int32/64赋值。但是实现锁,一般都需要 ”判断+赋值“原子,除了seq锁。 原子操作指令是实现复杂锁的基础,二者要有1. 1. CAS 2. 内存总线锁 自旋锁,读写自旋锁 (不能睡眠)(读写锁对写者不友好) 信号量,读写信号量,互斥锁,完成变量 (可以睡眠)(不能用在中断上下文) (完成变量可以用初值为0的信号量实现, 初值为1的二值信号量等价于互斥锁。 初值为0的二值信号量等价于完成变量。) seq锁,设计精巧,(写者友好。大量读的的读写锁会饿死写者) 写者之间的互锁还是依赖一个自旋锁,但是和读写锁的区别是,读者不给这个锁加锁, 所有读者不会把写者锁住。 内存屏障,编译屏障 禁止抢占 中断中使用自旋锁需要同时使用禁止抢占。 也有场景:不使用自旋锁,只想使用禁止抢占。 时钟: **硬中断** 主要函数:scheduler_tick() 中断注册:setup_default_timer_irq() 将0号(0x20)中断注册成函数timer_interrupt(). global_clock_event->event_handler = tick_handle_periodic(); irq0就是这个timer_interrupt hpet设备通过hpet_setup_irq函数动态注册了一个request_irq(), 回调函数是hpet_interrupt_handler() event_handler = tick_handle_periodic(); hpet设备的irq编号是create_irq()函数申请的。 中断处理:timer_interrupt()->tick_handle_periodic() **延时执行** timer: kernel/timer.c 在软中断中执行。 触发函数:run_local_timers():触发TIMER_SOFTIRQ类型的软中断。 执行函数:run_timer_softirq() 时间轮算法: https: //juejin.cn/post/7083795682313633822 多层时间轮在进位的时候会有性能问题。 初始化:init_timers() hrtimer: posix_timer: 忙等待: while (time_after(jiffies, delay)) ; cond_resched(), volatile udelay() schedule_timeout(): 先设置状态:set_current_state(TASK_INTERRUPTIBLE) 或 set_current_state(TASK_UNINTERRUPTIBLE) 是用timer实现的。 等待一段时间或者一个事件发生。 可以用来实现sleep 内存: ***??? MMU 是现代操作系统实现“虚拟内存、内存保护和多任务处理”的基础。 page/zone buddy ****???? alloc_pages() free_pages() kmalloc()/kfree()【性能好】, vmalloc()/vfree()【物理机页不连续,可能睡眠】 GFP_KERNEL / GFP_ATOMIC slab 【高速缓存】 ***??? kmalloc在slab之上。 内核栈: 中断栈,alloca() kmap() / kunmap() get_cpu() / put_cpu() alloc_percpu()/free_percpu()/get_cpu_var()/put_cpu_var() https: //s3.shizhz.me/linux-mm/addressing VFS: A 超级块对象 linux/include/linux/fs.h:: super_block 索引节点对象 linux/include/linux/fs.h:: inode 目录项对象 linux/include/linux/dcache.h::dentry dcache: 加速目录查找,icache(inode cache)也一起被缓存。 文件对象 linux/include/linux/fs.h:: file 文件对象(进程A) 文件对象(进程B) \ / \ / v v 目录项对象(唯一) | v 索引节点对象(唯一) aio ?? 忠告锁?? 其他 挂载点 linux/mount.h::vfsmount 进程相关 linux/fdtable.h::files_struct 所有进程文件对象的集合 被task_struct引用 linux/fs_struct.h::fs_struct 块设备: 块设备随机读取,字符设备顺序读取。 块大小是2的整数倍,且不能超过一个页的大小。 缓存区 linux/buffer_head.h::buffer_header 用于描述物理磁盘块和内存缓冲区直接的映射关系。(2.6后已废,由bio替代) bio linux/blk_types.h 被task_struct引用 请求队列 linux/blkdev.h::request_queue IO调度程序: block/ *-iosched.c 合并,排序,缓存后提交给硬盘。目的是缩短磁盘寻址时间。 电梯调度,CFQ调度,空操作调度,等。 进程地址空间: 进程好像可以访问所有物理内存。甚至远远大于。 平坦flat(与分段相对而言) 使用同一个地址空间的两个进程,就是线程。 有效区域:访问为有效区域就是“段错误” 有效区域内分段:代码段,数据段等。 内核线程:没有进程地址空间,没有用户上下文,不访问用户空间内存。需要页表时借用上一个进程的地址?。 内存描述符: linux/include/linux/mm_types.h::mm_struct 表示进程地址空间 被task_struct引用 内存区域: linux/include/linux/mm_types.h::vm_area_struct 地址空间中的一个连续段,段和段不可重叠 结构体成员:vm_flags 全局共享不可写区域,用来节省物理内存,例如libc.so 页表: include/asm-generic/page.h::pgd_t 用于虚拟内存到物理内存的转换。页表索引是虚拟内存地址,页表表项是物理内存地址。 linux为了节省表项本身占用的物理内存空间,实现3级页表:pgd_t -> pmd_t -> pte_t TLB是表项的高速缓存,由硬件实现。(MMU??) 页缓存:通过内存访问加快硬盘访问速度 三种策略:nowrite,write-through,write-back 脏页:写入了缓存但是没用写入硬盘的缓存页。 address_space数据结构,是物理内存维度的全局一份。 include/linux/fs.h 读取使用LRU算法,写入需要单独的线程做回写动作。 回写线程:fluser。为防止阻塞每设备一个。5.10的内核叫: kworker/flush和kworker/writeback 设备与模块 几种设备:字符设备(流式访问),块设备(随机寻址访问),网络设备(套接字API访问),杂项设备,伪设备。 模块 设备树:kobject,ktype,kset include/linux/kobject.h sysfs,kobject与目录项一对一,devcies目录尤其重要。 HAL:https: //www.freedesktop.org/wiki/Software/hal/ 事件:kobject_uevent 调试 printk,日志限速, gdb vmlinux /proc/kcore 其他 indent命令可以方便的调整缩进 --------------------- 附录: --------------------- 硬中断号的划分: /* * Linux IRQ vector layout. * * There are 256 IDT entries (per CPU - each entry is 8 bytes) which can * be defined by Linux. They are used as a jump table by the CPU when a * given vector is triggered - by a CPU-external, CPU-internal or * software-triggered event. * * Linux sets the kernel code address each entry jumps to early during * bootup, and never changes them. This is the general layout of the * IDT entries: * * Vectors 0 ... 31 : system traps and exceptions - hardcoded events * Vectors 32 ... 127 : device interrupts * Vector 128 : legacy int80 syscall interface * Vectors 129 ... 237 : device interrupts * Vectors 238 ... 255 : special interrupts * * 64-bit x86 has per CPU IDT tables, 32-bit has one shared IDT table. * * This file enumerates the exact layout of them: */ 0到31的定义: arch/x86/kernel/traps.c set_intr_gate(0, ÷_error); set_intr_gate_ist(1, &debug, DEBUG_STACK); set_intr_gate_ist(2, &nmi, NMI_STACK); /* int3 can be called from all */ set_system_intr_gate_ist(3, &int3, DEBUG_STACK); /* int4 can be called from all */ set_system_intr_gate(4, &overflow); set_intr_gate(5, &bounds); set_intr_gate(6, &invalid_op); set_intr_gate(7, &device_not_available); #ifdef CONFIG_X86_32 set_task_gate(8, GDT_ENTRY_DOUBLEFAULT_TSS); #else set_intr_gate_ist(8, &double_fault, DOUBLEFAULT_STACK); #endif set_intr_gate(9, &coprocessor_segment_overrun); set_intr_gate(10, &invalid_TSS); set_intr_gate(11, &segment_not_present); set_intr_gate_ist(12, &stack_segment, STACKFAULT_STACK); set_intr_gate(13, &general_protection); set_intr_gate(14, &page_fault); set_intr_gate(15, &spurious_interrupt_bug); set_intr_gate(16, &coprocessor_error); set_intr_gate(17, &alignment_check); #ifdef CONFIG_X86_MCE set_intr_gate_ist(18, &machine_check, MCE_STACK); #endif set_intr_gate(19, &simd_coprocessor_error); --------------------- 定时器实现 ---------------------- https: //mp.weixin.qq.com/s?__biz=MzIzODIzNzE0NQ==&mid=2654418061&idx=1&sn=e62b95e82d267e37fbab09d1ef835f55&chksm=f2fff03bc588792dba1942420c171b930aaa6fd6dada75acfe55f5245c70a5d54e751539f668&scene=21#wechat_redirect --------------------- 进程状态机 --------------------- https: //astrisk.github.io/linuxkernel/2017/05/05/linux-kernel-process-state/ |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 字符编码:从基础到乱码解决