創(chuàng)建進程
使用fork函數(shù)創(chuàng)建進程
int pid = fork();
在執(zhí)行此函數(shù)后,即從當前進程開了一個新的子進程莲趣。
pid=0表示當前是新進程趴樱,不為0代表當前為主進程,pid即子進程的編號拂到。
創(chuàng)建線程
- 線程復制執(zhí)行二進制指令
- 多進程缺點: 創(chuàng)建進程占用資源多; 進程間通信需拷貝內存, 不能共享
- 線程相關操作
- pthread_exit(A), A 是線程退出的返回值
- pthread_attr_t 線程屬性, 用輔助函數(shù)初始化并設置值; 用完需要銷毀
- pthread_create 創(chuàng)建線程, 四個參數(shù)(線程對象, 屬性, 運行函數(shù), 運行參數(shù))
- pthread_join 獲取線程退出返回值, 多線程依賴 libpthread.so
- 一個線程退出, 會發(fā)送信號給 其他所有同進程的線程
- 線程中有三類數(shù)據(jù)
- 線程棧本地數(shù)據(jù), 棧大小默認 8MB; 線程棧之間有保護間隔, 若誤入會引發(fā)段錯誤
- 進程共享的全局數(shù)據(jù)
- 線程級別的全局變量(線程私有數(shù)據(jù), pthread_key_create(key, destructer)); key 所有線程都可以訪問, 可填入各自的值(同名不同值的全局變量)
- 數(shù)據(jù)保護
- Mutex(互斥), 初始化; lock(沒搶到則阻塞)/trylock(沒搶到則返回錯誤碼); unlock; destroy
- 條件變量(通知), 收到通知, 還是要搶鎖(由 wait 函數(shù)執(zhí)行); 因此條件變量與互斥鎖配合使用
- 互斥鎖所謂條件變量的參數(shù), wait 函數(shù)會自動解鎖/加鎖
-
broadcast(通知); destroy
進程數(shù)據(jù)結構
- 內核中進程, 線程統(tǒng)一為任務, 由 taks_struct 表示
- 通過鏈表串起 task_struct
- task_struct 中包含: 任務ID; 任務狀態(tài); 信號處理相關字段; 調度相關字段; 親緣關系; 權限相關; 運行統(tǒng)計; 內存管理; 文件與文件系統(tǒng); 內核棧;
- 任務 ID; 包含 pid, tgid 和 *group_leader
- pid(process id, 線程的id); tgid(thread group id, 所屬進程[主線程]的id); group_leader 指向 tgid 的結構體
- 通過對比 pid 和 tgid 可判斷是進程還是線程
- 信號處理, 包含阻塞暫不處理; 等待處理; 正在處理的信號
- 信號處理函數(shù)默認使用用戶態(tài)的函數(shù)棧, 也可以開辟新的棧專門用于信號處理, 由 sas_ss_xxx 指定
- 通過 pending/shared_pending 區(qū)分進程和線程的信號
- 任務狀態(tài); 包含 state; exit_state; flags
- 準備運行狀態(tài) TASK_RUNNING
- 睡眠狀態(tài):可中斷; 不可中斷; 可殺
- 可中斷 TASK_INTERRUPTIBLE, 收到信號要被喚醒
- 不可中斷 TASK_UNINTERRUPTIBLE, 收到信號不會被喚醒, 不能被kill, 只能重啟
- 可殺 TASK_KILLABLE, 可以響應致命信號, 由不可中斷與 TASK_WAKEKILL 組合
- 停止狀態(tài) TASK_STOPPED, 由信號 SIGSTOP, SIGTTIN, SIGTSTP 與 SIGTTOU 觸發(fā)進入
- 調試跟蹤 TASK_TRACED, 被 debugger 等進程監(jiān)視時進入
- 結束狀態(tài)(包含 exit_state)
- EXIT_ZOMBIE, 父進程還沒有 wait()
- EXIT_DEAD, 最終狀態(tài)
- flags, 例如 PF_VCPU 表示運行在虛擬 CPU 上; PF_FORKNOEXEC _do_fork 函數(shù)里設置, exec 函數(shù)中清除
- 進程調度; 包含 是否在運行隊列; 優(yōu)先級; 調度策略; 可以使用那些 CPU 等信息.
- 運行統(tǒng)計信息, 包含用戶/內核態(tài)運行時間; 上/下文切換次數(shù); 啟動時間等;
- 進程親緣關系
- 擁有同一父進程的所有進程具有兄弟關系
- 包含: 指向 parent; 指向 real_parent; 子進程雙向鏈表頭結點; 兄弟進程雙向鏈表頭結點
- parent 指向的父進程接收進程結束信號
- real_parent 和 parent 通常一樣; 但在 bash 中用 GDB 調試程序時, GDB 是 real_parent, bash 是 parent
- 進程權限, 包含 real_cred 指針(誰能操作我); cred 指針(我能操作誰)
- cred 結構體中標明多組用戶和用戶組 id
- uid/gid(哪個用戶的進程啟動我)
- euid/egid(按照哪個用戶審核權限, 操作消息隊列, 共享內存等)
- fsuid/fsgid(文件操作時審核)
- 這三組 id 一般一樣
- 通過 chmod u+s program, 給程序設置 set-user-id 標識位, 運行時程序將進程 euid/fsuid 改為程序文件所有者 id
- suid/sgid 可以用來保存 id, 進程可以通過 setuid 更改 uid
- capability 機制, 以細粒度賦予普通用戶部分高權限 (capability.h 列出了權限)
- cap_permitted 表示進程的權限
- cap_effective 實際起作用的權限, cap_permitted 范圍可大于 cap_effective
- cap_inheritable 若權限可被繼承, 在 exec 執(zhí)行時繼承的權限集合, 并加入 cap_permitted 中(但非 root 用戶不會保留 cap_inheritable 集合)
- cap_bset 所有進程保留的權限(限制只用一次的功能)
- cap_ambient exec 時, 并入 cap_permitted 和 cap_effective 中
- 內存管理: mm_struct
- 文件與文件系統(tǒng): 打開的文件, 文件系統(tǒng)相關數(shù)據(jù)結構
- 用戶態(tài)/內核態(tài)切換執(zhí)行如何串起來
- 用戶態(tài)函數(shù)棧; 通過 JMP + 參數(shù) + 返回地址 調用函數(shù)
- 棧內存空間從高到低增長
- 32位棧結構: 棧幀包含 前一個幀的 EBP + 局部變量 + N個參數(shù) + 返回地址
- ESP: 棧頂指針; EBP: 椔肱ⅲ基址(棧幀最底部, 局部變量起始)
- 返回值保存在 EAX 中
- 64位棧結構: 結構類似
- rax 保存返回結果; rsp 棧頂指針; rbp 椥盅基指針
- 參數(shù)傳遞時, 前 6個放寄存器中(再由被調用函數(shù) push 進自己的棧, 用以尋址), 參數(shù)超過 6個壓入棧中
- 內核棧結構:
- Linux 為每個 task 分配了內核棧, 32位(8K), 64位(16K)
- 棧結構: [預留8字節(jié) +] pt_regs + 內核棧 + 頭部 thread_info
- thread_info 是 task_struct 的補充, 存儲于體系結構有關的內容
- pt_regs 用以保存用戶運行上下文, 通過 push 寄存器到棧中保存
- 通過 task_struct 找到內核棧
- 直接由 task_struct 內的 stack 直接得到指向 thread_info 的指針
- 通過內核棧找到 task_struct
- 32位 直接由 thread_info 中的指針得到
- 64位 每個 CPU 當前運行進程的 task_struct 的指針存放到 Per CPU 變量 current_task 中; 可調用 this_cpu_read_stable 進行讀取
進程調度
調度策略與調度類
- 進程包括兩類: 實時進程(優(yōu)先級高),普通進程余寥;實時進程(0-99); 普通進程(100-139)
- 兩種進程調度策略不同: task_struct->policy 指明采用哪種調度策略(有6種策略)
- 實時調度策略, 高優(yōu)先級可搶占低優(yōu)先級進程
- FIFO: 相同優(yōu)先級進程先來先得
- RR: 輪流調度策略, 采用時間片輪流調度相同優(yōu)先級進程
- Deadline: 在調度時, 選擇 deadline 最近的進程
- 普通調度策略
- normal: 普通進程
- batch: 后臺進程, 可以降低優(yōu)先級
- idle: 空閑時才運行
- 調度類: task_struct 中 * sched_class 指向封裝了調度策略執(zhí)行邏輯的類(有5種)
- stop: 優(yōu)先級最高. 將中斷其他所有進程, 且不能被打斷
- dl: 實現(xiàn) deadline 調度策略
- rt: RR 或 FIFO, 具體策略由 task_struct->policy 指定
- fair: 普通進程調度
- idle: 空閑進程調度
- 普通進程的 fair 完全公平調度算法 CFS(Linux 實現(xiàn))
- 記錄進程運行時間( vruntime 虛擬運行時間)
- 優(yōu)先調度 vruntime 小的進程
- 按照比例累計 vruntime, 使之考慮進優(yōu)先級關系
- 調度隊列和調度實體
- CFS 中需要對 vruntime 排序找最小, 不斷查詢更新, 因此利用紅黑樹實現(xiàn)調度隊列
- task_struct 中有 實時, deadline 和 cfs 三個調度實體, cfs 調度實體即紅黑樹節(jié)點
- 每個 CPU 都有 rq 結構體, 里面有 dl_rq, rt_rq 和 cfs_rq 三個調度隊列以及其他信息; 隊列描述該 CPU 所運行的所有進程
- 先在 rt_rq 中找進程運行, 若沒有再到 cfs_rq 中找; cfs_rq 中 rb_root 指向紅黑樹根節(jié)點, rb_leftmost指向最左節(jié)點
- 調度類如何工作
- 調度類中有一個成員指向下一個調度類(按優(yōu)先級順序串起來)
- 找下一個運行任務時, 按 stop-dl-rt-fair-idle 依次調用調度類, 不同調度類操作不同調度隊列
進程搶占
- 搶占式調度
- 兩種情況: 執(zhí)行太久, 需切換到另一進程; 另一個高優(yōu)先級進程被喚醒
- 執(zhí)行太久: 由時鐘中斷觸發(fā)檢測, 中斷處理調用 scheduler_tick
- 取當前進程 task_struct->task_tick_fair()->取 sched_entity cfs_rq 調用 entity_tick()
- entity_tick() 調用 update_curr 更新當前進程 vruntime, 調用 check_preempt_tick 檢測是否需要被搶占
- check_preempt_tick 中計算 ideal_runtime(一個調度周期中應該運行的實際時間), 若進程本次調度運行時間 > ideal_runtime, 則應該被搶占
- 要被搶占, 則調用 resched_curr, 設置 TIF_NEED_RESCHED, 將其標記為應被搶占進程(因為要等待當前進程運行
__schedule
)
- 另一個高優(yōu)先級進程被喚醒: 當 I/O 完成, 進程被喚醒, 若優(yōu)先級高于當前進程則觸發(fā)搶占
- try_to_wake_up()->ttwu_queue() 將喚醒任務加入隊列 調用 ttwu_do_activate 激活任務
- 調用 tt_do_wakeup()->check_preempt_curr() 檢查是否應該搶占, 若需搶占則標記
- 執(zhí)行太久: 由時鐘中斷觸發(fā)檢測, 中斷處理調用 scheduler_tick
- 搶占時機: 讓進程調用
__schedule
, 分為用戶態(tài)和內核態(tài)- 用戶態(tài)進程
- 時機-1: 從系統(tǒng)調用中返回, 返回過程中會調用 exit_to_usermode_loop, 檢查
_TIF_NEED_RESCHED
, 若打了標記, 則調用 schedule() - 時機-2: 從中斷中返回, 中斷返回分為返回用戶態(tài)和內核態(tài)(匯編代碼: arch/x86/entry/entry_64.S), 返回用戶態(tài)過程中會調用 exit_to_usermode_loop()->shcedule()
- 時機-1: 從系統(tǒng)調用中返回, 返回過程中會調用 exit_to_usermode_loop, 檢查
- 內核態(tài)進程
- 時機-1: 發(fā)生在 preempt_enable() 中, 內核態(tài)進程有的操作不能被中斷, 會調用 preempt_disable(), 在開啟時(調用 preempt_enable) 時是一個搶占時機, 會調用 preempt_count_dec_and_test(), 檢測 preempt_count 和標記, 若可搶占則最終調用
__schedule
- 時機-2: 發(fā)生在中斷返回, 也會調用
__schedule
- 時機-1: 發(fā)生在 preempt_enable() 中, 內核態(tài)進程有的操作不能被中斷, 會調用 preempt_disable(), 在開啟時(調用 preempt_enable) 時是一個搶占時機, 會調用 preempt_count_dec_and_test(), 檢測 preempt_count 和標記, 若可搶占則最終調用
- 用戶態(tài)進程
進程創(chuàng)建
- fork -> sys_call_table 轉換為 sys_fork()->
_do_fork
- 創(chuàng)建進程做兩件事: 復制初始化 task_struct; 喚醒新進程
- 復制并初始化 task_struct, copy_process()
- dup_task_struct: 分配 task_struct 結構體; 創(chuàng)建內核棧, 賦給
* stack
; 復制 task_struct, 設置 thread_info; - copy_creds: 分配 cred 結構體并復制, p->cred = p->real_cred = get_cred(new)
- 初始化運行時統(tǒng)計量
- sched_fork 調度相關結構體: 分配并初始化 sched_entity; state = TASK_NEW; 設置優(yōu)先級和調度類; task_fork_fair()->update_curr 更新當前進程運行統(tǒng)計量, 將當前進程 vruntime 賦給子進程, 通過 sysctl_sched_child_runs_first 設置是否讓子進程搶占, 若是則將其 sched_entity 放前頭, 并調用 resched_curr 做被搶占標記.
- 初始化文件和文件系統(tǒng)變量
- copy_files: 復制進程打開的文件信息, 用 files_struct 維護;
- copy_fs: 復制進程目錄信息, 包括根目錄/根文件系統(tǒng); pwd 等, 用 fs_struct 維護
- 初始化信號相關內容: 復制信號和處理函數(shù)
- 復制內存空間: 分配并復制 mm_struct; 復制內存映射信息
- 分配 pid
- dup_task_struct: 分配 task_struct 結構體; 創(chuàng)建內核棧, 賦給
- 喚醒新進程 wake_up_new_task()
- state = TASK_RUNNING; activate 用調度類將當前子進程入隊列
- 其中 enqueue_entiry 中會調用 update_curr 更新運行統(tǒng)計量, 再加入隊列
- 調用 check_preempt_curr 看是否能搶占, 若 task_fork_fair 中已設置 sysctl_sched_child_runs_first, 直接返回, 否則進一步比較并調用 resched_curr 做搶占標記
- 若父進程被標記會被搶占, 則系統(tǒng)調用 fork 返回過程會調度子進程
線程創(chuàng)建
- 線程的創(chuàng)建
- 線程是由內核態(tài)和用戶態(tài)合作完成的, pthread_create 是 Glibc 庫的一個函數(shù)
- pthread_create 中
- 設置線程屬性參數(shù), 如線程棧大小
- 創(chuàng)建用戶態(tài)維護線程的結構, pthread
- 創(chuàng)建線程棧 allocate_stack
- 取棧的大小, 在棧末尾加 guardsize
- 在進程堆中創(chuàng)建線程棧(先嘗試調用 get_cached_stack 從緩存回收的線程棧中取用)
- 若無緩存線程棧, 調用
__mmap
創(chuàng)建 - 將 pthread 指向椓祛恚空間中
- 計算 guard 內存位置, 并設置保護
- 填充 pthread 內容, 其中 specific 存放屬于線程的全局變量
- 線程棧放入 stack_used 鏈表中(另外 stack_cache 鏈表記錄回收緩存的線程棧)
- 設置運行函數(shù), 參數(shù)到 pthread 中
- 調用 create_thread 創(chuàng)建線程
- 設置 clone_flags 標志位, 調用
__clone
- clone 系統(tǒng)調用返回時, 應該要返回到新線程上下文中, 因此
__clone
將參數(shù)和指令位置壓入棧中, 返回時從該函數(shù)開始執(zhí)行
- 設置 clone_flags 標志位, 調用
- 內核調用
__do_fork
- 在 copy_process 復制 task_struct 過程中, 五大數(shù)據(jù)結構不復制, 直接引用進程的
- 親緣關系設置: group_leader 和 tgid 是當前進程; real_parent 與當前進程一樣
- 信號處理: 數(shù)據(jù)結構共享, 處理一樣
- 返回用戶態(tài), 先運行 start_thread 同樣函數(shù)
- 在 start_thread 中調用用戶的函數(shù), 運行完釋放相關數(shù)據(jù)
- 如果是最后一個線程直接退出
- 或調用
__free_tcb
釋放 pthread 以及線程棧, 從 stack_used 移到 stack_cache 中