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, &divide_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/

  

posted on   toong  阅读(9)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 字符编码:从基础到乱码解决

统计

点击右上角即可分享
微信分享提示