動機
不記實際上怎麼跑與跑了什麼,只記設計與方便未來查詢的部分 畢竟這是基於linux2.6的
整體心得
讀完上一本工作原理來讀這本剛好,可以互補
推薦讀 中斷: ch7~10 mem: ch12&ch15
省略掉 ch1, 14, 20
簡體版的翻譯錯誤有點多要小心,覺得怪就去看原文
ch1
skip
ch2
folder
folder | descrition |
---|---|
arch | x86, amd64之類的 |
block | block device 的 IO層 |
crypto | 加密API |
Documentation | 文件 |
drivers | device driver |
firmware | driver需要的fw |
fs | file system |
include | kernel的header file |
init | kernel引導與初始化 |
ipc | 跨proc溝通 |
kernel | 核心的kernel code(sched之類的) |
lib | 通用kernel function |
mm | 記憶體管理與virtual memory |
net | 網路 |
samples | 範例 |
scripts | compile kernel用的腳本 |
security | 安全模組 |
sound | 聲音 |
usr | userspace的code |
tools | linux的開發工具 |
virt | 虛擬化 |
make
make menuconfig
- 調linux的設定
make defconfig
- 把menuconfig的設定設成預設
make oldconfig
- load從其他地方來的
.config
- load從其他地方來的
make
- compile
- 在kernel code的根目錄會產生System.map,裡面有symbol,需要可以看
kernel dev的特點
- 沒有libc與stdlib
- linux有自己的工具,可以看include裡面的header,像
/inlucde/linux
就是linux kernel的函數
- linux有自己的工具,可以看include裡面的header,像
- 用gnu c
- inline
- asmembly 內嵌
- likely, unlikely之類的if優化
- 沒有mem保護
- 沒有seg fault,只剩panic
- 幾乎不能用floating point
- kernel stack十分小,並隨arch不同而有改變
- 同步很重要
- preempt
- smp(多對稱處理器,多cpu)
ch3
proc
相關struct
- struct task_struct
- 在
<linux/sched.h>
- proc的DS,有
- pid
- addr space
- 打開的文件 等…
- 在
- struct thraed_info
- 在
<asm/thraed_info.h>
- 放在kernel stack上,用以找到task_struct
- 為什麼不直接放task_struct
- 放proc中與arch有關的訊息
- 可以想成goroutines的struct P
- 在
proc state
- 正在跑、可以跑
- TASK_RUNNING
- 睡
- TASK_INTERRUPTABLE
- 等interrupt或其他或signal
- TASK_UNINTERRUPTABLE
- 不管signal,其他與TASK_INTERRUPTABLE一樣
- TASK_INTERRUPTABLE
clone syscall
- fork
clone(SIGCHID, 0)
- kthread
clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0)
- VM: virtual mem
- FS: file system
- FILES: opend files
- SIGHAND: signal handler
- vfork (會sync執行的fork)
clone(CLONE_VFORK | CLONE_VM | SIGCHID, 0)
ch4
proc scheduler
proc吃?
- IO
- scheduler應該要多拉吃IO的proc
- 不然response time會變慢
- 但throughput會低
- scheduler應該要多拉吃IO的proc
- cpu
- scheduler應該多拉吃cpu的proc
- 不然throughput會變慢
- 但response time會變慢
- scheduler應該多拉吃cpu的proc
time slice
- 一個proc能佔cpu多少時間
- 太長
- 對吃cpu有利
- 對吃IO不利
- 高throughput
- 差response time
- cxt switch的總計花費時間小
- 太短
- 對吃cpu不利
- 對吃IO有利
- 低throughput
- 好response time
- cxt switch的總計花費時間多
- 太長
proc優先級
- nice
- -20~19
- 越小越優先
- real-time priority
- 0~99
- 越大越優先
- RT proc永遠優先於普通proc
- 兩個是各自獨立的系統
公平調度(CFS)
- 概念
- 依據cpu的使用比例分配
- 分配到的時間與系統負載密切相關
- 搶的時機是如果有proc花的使用比比當前proc少,就換上去
- 每個proc預設拿到
1/n
個cpu時間- n是proc個數
- 就是想像一共有
proc個數 個cpu
- cpu時間在書中叫
目標延遲
- nice就是原本的
1/n
再乘上nice換出來的比值
- 依據cpu的使用比例分配
- 實作
- 計時
- 在
<linux/sched.h>
- struct_sched_entity
- vruntime
- 會記錄運行時間
- 以及以proc總數做標準化
- vruntime
- 在
- choose proc
- 挑vruntime最小的
- sched的interface
schedule()
- 如果要睡覺
- 就是設定proc state再call這個
- 假喚醒
- 如果state是TASK_INTERRUPTABLE就會被signal叫醒
- 所以要檢查喚醒後發生什麼事
- 如果要睡覺
- 計時
- cxt switch
- 在
<asm/mmu_context.h>
- switch_mm換virtual mem
- 在
<asm/system.h>
- switch_to換cpu state
- 在
- preempt
- 用一個flag,
need_resched
去看該不該被搶 - 誰可以設?
- scheduler
- proc自己
- preempt發生時機
- user preempt
- 從syscall回到userspace
- 從中斷處理程序回到userspace
- kernel preempt
- 中斷處理程序結束,且回到kernel space之前
- kernel的code再一次可以被搶的時候
need_resched
被設preempt_count
是0 (在thread_info)
- kernel的code被block時
- kernel的code調用
schedule()
- user preempt
- 用一個flag,
- RT schedule
- SCHED_FIFO
- proc一直跑,除非
- block
- 自己想換
- proc一直跑,除非
- SCHED_RR
- 有time slice的SCHED_FIFO
- 低優先無法搶高優先即使對方的time slice花完了
- 都是soft rt
- SCHED_FIFO
ch5
syscall在上一篇筆記就說了,所以這裡focus在一些之前沒提到的東西
- asmlinkage
- 叫compiler只能從stack拿參數
- 參數驗證
- ptr的addr是userspace的addr
- ptr的addr在proc的addr space中
- 讀寫要符合原本的讀寫限制
- 參數有效
- pid有效
- 檔案有效
- process context
- syscall是在process cxt
- 會睡覺
- 會被搶
- 在你想多加一個syscall(衝動)之前,可以
- 用dev file實現read, write,配合ioctl
- 創其他操作介面,像semaphore可以像file一樣操作
- sysfs
ch6
list_elment
- 在
<linux/list.h>
- Linked List (bidiretion)
- 用法是在自己的struct中放一個list_elment
- 因為c沒泛型!!
- c應該叫mem描述語言才對
- 沒有特別需求時優先用list
kfifo
- 在
<linux/kfifo.h>
- Queue
- 遇到 consumer/producer時用
idr
- 在
<linux/idr.h>
- Hash table
uid
(int) ->void *
- 要mapping時就用
rbtree
- 在
<linux/rbtree.h>
- 紅黑樹
- 沒有find與insert,要自己寫
- 因為c沒泛型!!
- 大量data與反覆search就用
ch7
中斷(IRQ)
- 就是一個數字
- 異常與中斷很像
- 但異常要與cpu時鐘同步
中斷上下文
- 不會block(睡覺)
- 當他出現時,一定是
- 打斷了某個proc
- 甚至是,不同irq stack的irq
上半段 與 下半段
- 上半段: 馬上處理
- 下半段: 原本中斷中可以之後再處理的
request_irq
- 註冊irq
- 會sleep
reenter?
- irq被call時,該irq會被mask起來,其他cpu看不到
IRQF_SHARED
- 多個dev用同一個irq
- 所以
- dev的addr唯一,也就是可以區分不同device
- code有辦法對應不同device
/procs/interrupts
- PC上所有irq的資訊
中斷控制器
- 停用
- cpu上的整個中斷
- 這樣這個cpu上就不會被preempt
- 但其他cpu還是可以中斷!!
- 所有cpu的某個中斷
- cpu上的整個中斷
- 看
<asm/system.h>
與<asm/irq.h>
ch8
中斷,下半段
softirq
- 同一個softirq可以同時跑在其他cpu上
- 靜態註冊 (compile-time)
- 在
<linux/interrupt.h>
- struct softirq_action
- softirq放在array中(32個)
- 在
- 中斷上下文
- softirq沒辦法搶其他的softirq
- softirq只會被irq搶!!
- 跑什麼softirq
- irq handler在return前會標
- 什麼時候跑softirq?
- irq handler在return時
- 在softirqd中
- 有人去看還有哪些softirq還沒跑與直接執行softirq的時候
tasklet
- 同一個tasklet不可以同時跑在其他cpu上
- 動態註冊
- 在
<linux/interrupt.h>
- struct tasklet_struct
- 有執行狀態
- 所以其他cpu不會跑正在跑的tasklet
- tasklet放在cpu上的linked list
- tasklet_vec
- tasklet_hi_vec (hi是high)
- 所以可以動態加
- 在
- 中斷上下文
- tasklet就是掛在兩個softirq上
- 把task往裡面放,需要時就跑
ksoftirqd
- 每個cpu上會有一個
- 處理大量softirq
- 在最低nice的kthread中去調用還沒跑的softirq
work queue
- proc上下文
- 可以allocate大mem
- semaphore
- 用block IO
- 每個cpu一個queue與kthread,跑task
- 我們需要一個可以看到所有cpu的struct
- 在
<linux/workqueue.h>
- struct workqueue_struct
- 放workqueue訊息
- struct workqueue_struct
- proc叫
event/n
- 在
<kernel/workqueue.c>
- struct cpu_workqueue_struct
- 就是queue
- struct cpu_workqueue_struct
- 在
<linux/workqueue.h>
- struct work_struct
- 就是task
- struct work_struct
- 每個cpu上會有一個
- n是cpu編號
- 在
- 在
- 我們需要一個可以看到所有cpu的struct
how to lock
- proc cxt與bottom half分享data
- 鎖bottom half
- 拿鎖
- irq cxt與bottom half分享data
- 鎖irq
- 拿鎖
- irq -> softirq/tasklet -> proc
- 如果要和上一層share data
- 鎖上一層!!
- 防smp與preempt
- 拿鎖
- 鎖上一層!!
- 可以看Unreliable Guide To Locking有詳細的解釋
- 如果要和上一層share data
ch9&10
怎麼等鎖
- busy waiting
- 都能用
- sleep
- only in proc ctx
要同步的原因是
- in userspace
- preempt
- re schedule
- in kernel space
- irq
- softirq/tasklet
- preempt
- sleep & sync with userspace
- smp
struct什麼時候加鎖
- kernel struct大部分都要
- 有其他proc可以access
- 任何人都看的到的
原子性與順序性
- 原子性
- 執行期間不打斷,只有兩個case
- 沒有跑
- 跑完了
- 執行期間不打斷,只有兩個case
- 順序性
- ABC分散在不同中,也要ABC去跑
- 用lock只能保證,ABC一起跑,一起不跑
- 用semaphore可以幹出來
- 主要用barrier
- ABC分散在不同中,也要ABC去跑
讀寫鎖(RW)
- 只能有下面的其中一種case
- 1寫
- 多讀
- 有利於讀
- 只要一堆讀,寫就只要等就飽了
工具
- atomic_t
- spinlock
- busy waiting
- 使用時不能sleep
- 不然就是增加整體的response time
- 如果lock時間小於2次ctx switch才有利
- no recur
- 不能鎖了又鎖
- 在irq中用的話
- 先關irq
- 不然被中斷,另一irq又拿同一個鎖的話…
- 有RW版
- 禁止preempt
- 持有spinlock就是禁止preempt
- busy waiting
- semaphore
- sleep
- only in proc ctx
- 任何持有人都可以上鎖或解鎖!!
- 可以超過一個人到critical zone
- 有RW版
- sleep
- mutex
- sleep
- only in proc ctx
- 上鎖的人才能解鎖
- 只有一個人能到critical zone
- 可以recur
- 可以處理priority inversion
- sleep
- complete variable
- cond var
- seq lock
- 實作
- 有一個acc
- 當write拿鎖時,會
acc++
- read時就是確認開始與結束的acc值是不是一樣
- 確認中間沒有write
- 也可以透過acc是不是偶數看寫結束語否
- 有利於寫
- 實作
- mem barrier
- barrier前面的code跑完前,絕對不會跑後面去
- 起因於
- 指令重排,所以有些指令會跑到後面去
- 前半部的code不會到reorder到後面去
priority inversion
- 一般來說是在RT才會提,但想到就記一下
- 情境
- pri最小(A)拿到lock中被中斷
- pri高的(C)想拿lock,但拿不到!!
- 像spinlock會busy waiting
- pri高的跑不動,time slice吃完
- 有下一個pri比較低的(B),來了,也做完了!?
- 但pri高的還沒做完阿!!
- 起因
- lock的效果會影響優先權,但是沒有納入原有優先權的系統中
- 像只要有人拿了spiclock,那他在這一塊就是最優先的
- 不管其他優先權怎麼設,bust waiting就是wait,沒有轉圜的機會
- 同時也無法打斷,不然就不是crtical zone了
- lock的效果會影響優先權,但是沒有納入原有優先權的系統中
- 解法
- 大方向: crtical zone要先跑!!
- 要改crtical zone的優先權
- 改到與現在最高的一樣高
- 改到沒有人比crtical zone高
- 要改crtical zone的優先權
- Priority inheritance
- 在crtical zone時,遇到更高的proc同時要進來
- 把crtical zone的優先權拉到與那個proc一樣高
- 跑完crtical zone就降回去
- Priority ceiling protocol
- 在crtical zone時拉到最高
- 跑完crtical zone就降回去
- 大方向: crtical zone要先跑!!
ch11
怎麼算1秒
- 系統計時器會依一定頻率(tick rate)打中斷
- 這樣kernel知道兩次中斷之間的間隔時間(tick)
- HZ就是1sec打幾次
- HZ可以改!!
- HZ越高
- 時間精準度越高
- 連帶與時間有關的都會變準
- poll, select
- preempt
- 連帶與時間有關的都會變準
- system load上升
- 時間精準度越高
- 這是kernel用的
- userspace的叫USER_HZ
- 1sec = tick * HZ
開機多久了
- jiffies
- 紀錄系統計時器中斷打了幾次
- jiffies/HZ => 開機多久
硬體
- RTC
- bios上存時間(幾點幾分之類的)的
- 就算電腦關機,還是藉由CMOS繼續跑
- 開機時初始化xtime變數
- 系統計時器
- 由kernel設定tick rate
關於proc的計時
- 每一次中斷時就把當前proc狀態(idle, run…)的變數遞增一次
- 如果在這中斷之間多次換狀態?
- 不管,現在他在這個狀態,這一段時間都是這個狀態的
- 如果在這中斷之間多次換狀態?
如何delay
delay執行,不應該在有lock時或是關閉中斷後發生
- busy wait
- base on jiffies
- short delay
- 如果要的delay比tick小
- 用
udelay(),ndelay(),mdelay()
- 裡面其實也是loop,但是kernel知道他會跑多久且不用jiffies
schedule_timeout()
- 讓task睡,在超過指定時間後跑
- 沒辦法說很精確在時間到就馬上跑
- soft rt
- 讓task睡,在超過指定時間後跑
ch12
kernel space的mem
整體
- phy
- mem
- byte
- word
- cpu
- page
- mem
- kernel
- struct zone
- 在
<linux/mmzone.h>
- struct page
- 在
<linux/mm_types.h>
- 對應到實體的page
- 再從page換成addr
- alloc_pages -> page_address
- kmalloc
- 不能要求
ZONE_HIGHEM
- 因為可能還沒分配邏輯addr
- 用alloc_pages
- 不能要求
- 上面都是
- 連續的page
- 可以sleep時用
GFP_KERNEL
- 不可以sleep時用
GFP_ATOMIC
- vmalloc
- 可能不連續的page
- 在
- 不同page在不同位置,所以有不同區
- 4種
- ZONE_DMA
- ZONE_DMA32
- only for 32bits device
- ZONE_NORMAL
- ZONE_HIGHEM
- high mem用,不能用永久map到kernel addr空間
- 想想kernel都是用低的mem,所以高的其實算是userspace的
- 如果要用就要map
- 永久: kmap (low與high都能用,會睡覺)
- 臨時: kmap_atomic (不會睡,但是會被下一個)
- 如果要用就要map
- 在
- slab
- 在快取記憶體上(kmem_cache)開一個list當成object pool
- 之後重複利用裡面已經存在的obj
- slab就是list上的node,node裡面放obj
- struct slab
- 在
<mm/slab.c>
- 在
- 在快取記憶體上(kmem_cache)開一個list當成object pool
- struct zone
額外
- stack
- 每個proc有
- kernel stack
- 1~2 page (根據arch)
- 原本中斷也是用被中斷的proc的stack
- kernel stack本來就很小,還是別吧
- 最後有了interrupt stack
- user stack
- 就一般的stack
- kernel stack是為了與user stack分開
- 想想兩個混在一起會發生什麼事
- kernel stack
- 每個cpu有
- interrupt stack
- 每個proc有
- percpu
- 每個cpu自己的data
- 因為是cpu自己的,所以
- cache失效降低
- 不用擔心smp
- preempt還是要怕
ch15
user space的mem
整體
- proc的userspace的mem空間
- struct mm_struct
- 在
<linux/sched.h>
- 可以到
/proc/<pid>/maps
看proc現在有什麼 - 主要兩個
- struct vm_area_struct
- 在
<linux/mm_types.h>
- 就是這裡的page,加上權限與狀態之類的
- 在mm_struct有兩個obj放vm_area_struct
- list: 方便iterate
- rbtree: 方便找定點
- 在
- pgd_t
- phy的page表
- 三層 (pgd -> pmd -> pte)
- 用這個換出實際addr
- phy的page表
- struct vm_area_struct
- 在
- struct mm_struct
額外
- kthread的mm
- kthread天生沒有mm (不然怎麼有k)
- 當需要的時候會從proc的ptr拿到
- 當schedule會存前一個proc的mm (active_mm)
- 當kthread需要(像page table),就直接用active_mm看
- mmap
- 把file放到proc的userspace的mem空間
- 生一個addr出來
- 把file放到proc的userspace的mem空間
ch13
virtual file system的流程
- userspace
- virtual file system
- 真的file system
- phy
VFS的attribute
- superblock
- 真的file system (ext4, brtfs…)
- struct file_system_type
- 在
<linux/fs.h>
- 放fs的訊息(kernel關心的)
- 在
- struct vfsmount
- 在
<linux/mount.h>
- fs的統計數據與安裝訊息(root, dev等等)與安裝點
- 可以當成實際fs的入口
- 在
- struct file_system_type
- 放fs的訊息(VFS關心的)
- struct super_block
- 在
<linux/fs.h>
- 在
- 真的file system (ext4, brtfs…)
- inode
- file or folder
- 放file/folder的訊息
- struct inode
- 在
<linux/fs.h>
- 在
- dentry
- path的一部份
/usr/bin/vi
的/
,usr
,bin
,vi
- 其實可以當成inode,但是為了查找path,所以獨立出來
- struct dentry
- 在
<linux/dcache.h>
- 在
- state
- 被用
- 有對應的file
- 也正在被ref
- 還沒被用
- 有對應的file
- 沒被ref
- 不在
- 根本沒這個file
- 總不能每次要等整個找完才丟沒有指定的檔案吧
- 同時,因為有這個與slab,在之後真的有檔案也可以直接快取
- 根本沒這個file
- 被用
- path的一部份
- file
- opend file
- struct file
- 在
<linux/fs.h>
- 在
看看proc
- 每個proc都有
- struct files_struct
- 在
<linux/fdtable.h>
- 紀錄 開過的檔案
- 在
- struct fs_struct
- 在
<linux/fs_struct.h>
- 紀錄 pwd, 現在跑的檔案
- 在
- struct files_struct
- 每個namespace都有
- struct mmt_namespace
- 在
<linux/mmt_namespace.h>
- 放vfsmount,讓同一namespace的去找
- 在
- struct mmt_namespace
ch14
skip
ch16
快取策略
- nowrite
- 不管cache,直接寫到mem
- cache直接失效
- write-through
- 同時更新cache與mem
- write back
- 先更cache
- 再找時間更mem
快取無效策略
- LRU
- 從list中拿掉最少用到的
- LRU/2
- 兩條list
- 熱的
- 不熱的
- 變成熱的方式
- 在不熱的中
- 有被用到
- 如何調整兩條list
- 熱的比不熱的長
- 把一些去掉
- 怎麼去掉
- fifo (不是最少用到)
- 熱的list就是原本LRU中記錄頻率的腳色
- 只要在熱的list就是有用過
- 兩條list
page快取(上一篇心得的file快取)
- struct address_space
- 在
<linux/fs.h>
- 應該叫page_cache_entity
- 在
- 這是write back
- 什麼時候寫回去
- cache要沒了
- cache放太久了
- 有人call
sync(),fsync()
- 誰去寫
- 靠flusher的多thread去寫 (原本是單thread)
- 但還是會被底下的IO給bound
- 靠flusher的多thread去寫 (原本是單thread)
- 什麼時候寫回去
ch17
編module
makefile
obj-m += foo.o
# obj-m += foo.o 加在sourcetree中的makefile中
foo-objs := foo-main.o foo-utils.o
- 編
make -C /kernel/path SUBDIRS=$PWD modules
- 安裝
make modules_install
- 生相依訊息
depmod
depmod -A
only for new added modules
- 加編譯選項
- 改Kconfig
export what?
- 參數
static int a = 1;
module_param_named(exported_name, a, int, 0644);
- symbol table
EXPORT_SYMBOL(your_func);
EXPORT_SYMBOL_GPL(your_func);
device model & sysfs
- device model
struct kobject
- 在
<linux/kobject.h>
- 就是obj
- 與list一樣要嵌到其他struct上才有用
- 在sysfs中會變成folder
- 他的參數會變成file
- struct attribute
- 在
<linux/sysfs.h>
- 在
- 會有值,可讀寫
- struct sysfs_ops
- 在
<linux/sysfs.h>
- 有read/write可以實作
- 在
- struct sysfs_ops
- struct attribute
- 他的參數會變成file
- 在
struct ktype
- 在
<linux/kobject.h>
- 就是放methods
- 在
struct kset
- 在
<linux/kobject.h>
- 就是class(obj的base,js的prototype)
- 在
- sysfs
- block
- 列出註冊的block dev
- bus
- 列出系統的bus
- class
- 列出依func排列的dev
- net, block, ppp, rtc等等
- 列出依func排列的dev
- dev
- 列出註冊的dev
- block, char
- 列出註冊的dev
- devices
- 列出dev的topo
- platform, system, virtual等等
- 列出dev的topo
- firmware
- 與系統有關的low-level子系統
- ACPI, EDD, EFI
- 與系統有關的low-level子系統
- fs
- 列出filesystem
- kernel
- 列出kernel狀態與option
- modules
- 列出載入的module
- power
- 列出電源管理的資料
- block
ch18&19
開發tips
- 保留一個能動的,一個用新版,其他用舊版
- 用uid(user)去切
- 加個condition
- 加統計量
- 限制output的頻率
- 每個幾秒印一次
- 只印幾次
- 輸出自己的錯誤訊息
- 我自己會在一定看的到的地方(像家目錄),放log
align
由大到小去排
// total: 12
struct A {
char a; // 1+3
unsigned long b; // 4+0
unsigned short c; // 2+1+1
char d;
};
// total: 8
struct B {
unsigned long b; // 4+0
unsigned short c; // 2+1+1
char a;
char d;
};
big/little endian
- 變成binary: 會變成abcd
- 最左是最高有效位
- big-endian: 最高有效位放arr第一個
- little-endian: 最低有效位放arr第一個
最高有效位與最低有效位的技法: 下坡(反斜線),所以最高在左邊
一些數字
- 不要假設HZ、page的長度
- 沒有寫明長度的type的長度都是不確定的
- 除了
- char是1byte (ansi c)
- int通常是32位
- 除了
ch20
skip