《深入理解Linux內核》 讀書筆記


深入理解Linux內核 讀書筆記

一、概論

操作系統基本概念

  • 多用戶系統
    • 允許多個用戶登錄系統,不同用戶之間的有私有的空間
  • 用戶和組
    • 每個用於屬於一個組,組的權限和其他人的權限,和擁有者的權限不一樣。對應的是Linux的文件權限系統
  • 進程
    • 和程序的區別。幾個進程能並發執行同一個程序,一個進程能順序執行幾個程序
    • 程序更像是代碼片段,進程是執行代碼的容器
    • linux是搶占式操作系統,也就是一個進程只能占用CPU一段時間。非搶占式系統中,進程如果不釋放CPU,可以一直占用
  • 內核體系結構
    • Linux是單塊內核,同時提供模塊(module)功能
    • 模塊是指:例如一個程序,引用了一個系統模塊,這個系統模塊不會是這個進程單獨擁有,當其他程序也需要這個模塊時,內核會把這個模塊鏈接到其他程序。這樣可以節省內存,也就是這個模塊只會在內存中存在一份。模塊就是一組函數,或者一段代碼。

文件系統

  • 文件
    • 文件是以字節序列組成的信息載體(container)
    • 文件目錄是樹結構
    • 每個進程都有一個工作目錄,通過pwdx 進程ID 命令可以查看
  • 硬鏈接和軟連接
    • 鏈接類似window的快捷方式,創建一個文件,指向另一個文件
    • ln p1 p2 就是創建一個文件p2,指向p1
    • 硬鏈接只能指向文件,不能指向目錄,因為會導致循環指向
    • 硬鏈接只能指向同一個文件系統的文件(文件系統是物理划分,例如不同硬盤)
    • 軟鏈接沒有硬鏈接這些限制,創建方法是加-s參數
  • 文件類型
    • 普通文件
    • 目錄
    • 符號鏈接
    • 面向塊的設備文件
    • 面向字符的設備文件
    • 管道和命名管道(pipe named pipe)
    • 套接字(socket)
  • 文件描述符與索引節點
    • 每個文件都有一個索引節點(inode)的數據結構,用來存儲文件的描述信息,和文件的內容是區分開的。
      • inode有(通過ll命令看到的):
        • 文件類型
        • 硬鏈接個數
        • 文件長度
        • 文件擁有者的uid
        • 用戶組的id
        • 修改時間等
        • 訪問權限
  • 訪問權限和文件模式
    • 擁有者,組,其他人,各有讀寫執行3種權限
  • 文件操作
    • 打開文件
    • 移動光標
    • 關閉

Unix內核概述

  • 進程/內核模式

    • 進程有用戶態和內核態
    • 用戶態不能訪問內核的數據結構和內核程序
    • 兩種態會經常切換,例如在時刻A,進程在用戶態,在時刻B,進程在內核態
    • 從用戶態切換到內核態的情況:
      • 調用系統調用
      • 執行進程的CPU發送異常
      • 外圍設備向CPU發出中斷
      • 內核線程被執行
  • 進程

    • 每個進程有一個進程ID,pid
    • 內核切換執行的進程時,會保存舊進程的信息,包括:
      • 程序計數器和棧指針寄存器
      • 通用寄存器
      • 浮點寄存器
      • CPU狀態
      • 內存管理寄存器
  • 可重入內核

    • unix內核都是可重入的
    • 可重入是指,可以被重復進入,也就是可以同時有多個進程處於內核態
  • 進程地址空間

    • 每個進程有自己私有的地址空間
  • 同步和臨界區

    • 類似鎖
    • linux是搶占式內核,所以需要同步
    • 信號量
      • 每個資源都有一個信號量,類似int類型,初始值是1
      • 每個進程訪問資源,調用down方法,信號量減1,如果減1后,信號量小於0,進程被加入到訪問隊列中。如果大於等於0,進程可以訪問資源
      • 每個進程訪問完資源,調用up方法,信號量加1,如果信號量大於等於0,激活訪問隊列的第一個進程
      • 進程鎖,線程鎖的機制,應該都是這樣的
      • 這里要保證down和up的操作都是原子性的,不能並發
      • 要防止死鎖
      • 鎖里面的區域就是臨界區,也就是acquire和release之間的代碼
  • 信號和進程間通信

    • 信號和信號量是不一樣的
    • linux有20多種不同的信號,例如kill -9 中的 9就是一種信號
    • 進程收到信號后,可以
      • 忽略
      • 異步執行指定程序(新開一個線程?),這種需要事先定義信號處理函數。
    • 內核收到信號后,可以
      • 終止進程(例如kill - 9)
      • 忽略信號
      • 掛起進程
      • 恢復進程
    • 進程間通信(IPC)
      • 信號
      • 消息(msgget(),msgsnd())兩個系統調用,發信息和收信息,Python里面的進程間Queue應該就是用這個實現的
      • 共享內存(shmget shmdt)兩個系統調用
  • 進程管理

    • fork來啟動一個子進程,一般在啟動的時候復制父進程的數據和代碼,但是這樣效率較低,所以會使用寫時復制,也就是一開始父子進程共享內存,當其中一個進程需要修改數據時,才執行復制操作
    • exec用於啟動子進程
    • exit用於結束子進程
    • wait4用於父進程等待子進程結束
  • 內存管理

    • 虛擬內存,在物理內存(MMU)和程序之間的抽象,相當於訪問內存的代理。
    • 內核內存分配器,KMA,用於管理內存
    • 高速緩存 由於內存比硬盤快很多,所以從硬盤讀取得數據會緩存在內存,使下次可以快速訪問

二、內存尋址

  • 內存地址
    • 內存地址有3種
      • 邏輯地址,由一個段(segment)和偏移量(offset)組成,用來指明一個操作數,或者一條指令的地址
      • 線性地址。是一個32位無符號整數(在32位系統中是這樣),從0x00000000到0xffffffff。內存相當於一個超大的列表,下標(地址)是一個32位整數,值就是內存的內容,值得大小是1字節
      • 物理地址。內存芯片級的地址
    • 邏輯地址,經過分段單元,轉換為線性地址,線性地址,經過分頁單元,轉換為物理地址
  • 分段單元(用於把邏輯地址,轉換為線性地址)
    • 概念
      • 段選擇符,也叫段標識符,也就是上面說的段,程序傳入給分段單元。有字段:
        • index,表示段描述符在GDT或者LDT中下標
        • TI,表示段描述符在GDT中還是LDT中
        • RPL,特權級
      • 段描述符,8字節,存放在GDT或者LDT中,有字段
        • Base表示段在內存中首字節的線性地址
        • S,0表示系統段,1表示普通段
        • DPL,特權級,0表示只有內核態才能訪問,3表示內核態和用戶態都能訪問。(cs寄存器中,有一個兩位的字段,指明CPU的當前特權級,0表示內核級,3表示用戶級。所以通過這個機制,可以限制用戶態的進程不能訪問內核態的內存數據
        • D或者B,表示這是代碼段,還是數據段
      • GDT,是全局段列表,item是段描述符
      • LDT,是局部段列表,item是段描述符
    • 轉換流程
      1. 傳入邏輯地址給分段單元,邏輯地址包含段選擇符和偏移量
      2. 查看段選擇符的TI字段,決定是從GDT中還是LDT中獲取段描述符,假如是GDT
      3. 查看段選擇符的index字段,假如是2,從gdtr寄存器中獲取GDT列表的首字節地址,假如是0x00002000,計算段描述符的位置=0x00002000+2*8,=0x00002016 (每個段描述符8字節),所以段描述符在內存的0x00002016-0x00002024位置
      4. 查看段描述符的Base字段,假如是0x00003000,加上偏移量,假如是100,得到線性地址是0x00003100

三、進程

進程,輕量級進程(LWP)和線程

  • 進程是程序執行時的一個實例
  • 線程 是進程里面的一個執行流,線程的切換時在用戶態進行的。但是這樣就不能做到並發了
  • 輕量級進程,類似線程,但是切換時在內核態進行

所以Linux的做法是(TODO 這一塊還不是很明白)

  • 把線程和輕量級進程關聯起來,所以線程和輕量級進程是等價的
  • 對內核來說,進程和LWP是一樣的,使用同樣的調度方法
  • LWP之間可以共享部分數據

進程描述符

  1. 進程描述符是一個數據結構(c的struct,類似Python的字典)

  2. 進程描述符有字段:

    1. state 狀態
      1. 可運行狀態(TASK_RUNNING),要么在運行,要么准備運行
      2. 可中斷的等待狀態(TASK_INTERRUPTIBLE)進程被掛起(睡眠),表示它在等待一個事件的發生,例如等待某個系統資源。當這個系統資源可用,內核會產生一個硬件中斷,來喚醒進程
      3. 不可中斷的等待狀態(TASK_UNINTERRUPTIBLE),和可中斷的等待狀態類似,這個狀態較少用到
      4. 暫停狀態(TASK_TOPPED)進程被暫停執行,當進程收到信號SIGSTOP,SIGSTP,SIGTTIN SIGTTOU信號后,會進入暫停狀態
      5. 跟蹤狀態(TASK_TRACED)當進程被另一個進程跟蹤,例如執行ptrace命令,
      6. 僵死狀態(EXIT_ZOMBIE)進程的執行被終止,但是父進程還沒有發布wait4或者waitpid命令來獲取進程信息。這時內核不會自動丟棄進程的信息,因為父進程可能還需要這些信息
        10.僵死撤銷狀態
    2. thread_info 進程的基本信息
    3. fs_struct 當前目錄
    4. signal_struct 收到的信號
    5. pid 進程的ID。順序遞增,最大是32767,超過后,從1開始獲取閑置的PID值。進程里面的線程,也擁有自己的pid,同時每個線程有一個tgid(thread group id),表示線程組ID,這個ID等於進程中第一個線程的pid。
      1. 一個進程里面至少有一個線程

進程鏈表

  • 一個進程描述符表示一個進程
  • Linux把所有進程放在一個雙向鏈表里面,每個item是一個進程描述符
  • TASK_RUNNING狀態的進程鏈表
    • 由於CPU在進行進程切換時,需要快速知道下一個執行的進程是什么,所以Linux把所有可以執行的進程都放在一個單獨的鏈表。
    • 由於不同進程有不同的優先級,所以linux的做法是
      • 由於有140種優先級(優先級用prio表示,0-139),所以用140個鏈表來保存
      • 用一個140長度的位圖(bitmap)來表示140個鏈接中,哪些有數據
      • 所以獲取下一個優先級最高的進程的做法是:
        • 查看位圖,看第一個=1的位的下標是多少,例如是15
        • 訪問第15個鏈表,queue[15],獲取第一個元素

進程間的關系

進程描述符里面有特定的字段,記錄每個進程的父進程,兄弟進程和子進程

  • real_parent 父進程的描述符指針,如果父進程不存在,指向進程1
  • parent 當前父進程,通常和real_parent一致,指引當進程被追蹤時不一致
  • children 鏈表,記錄所有子進程
  • sibling 有prev和next兩個元素,表示上一個兄弟進程,和下一個兄弟進程

pidhash

有時候內核需要根據pid來獲取進程描述符
所以內核會保存一個pidhash數據結構,是個hash表(c里面的hash表的實現和redis的hash表實現類似),key是pid,value是進程描述符

進程切換

進程切換,任務切換,上下文切換是一樣的

每個進程都有自己的地址空間(在內存),但是進程之間是共享寄存器的,所以進程的切換需要(硬件上下文是寄存器的數據):

  • 保存prev進程的硬件上下文
  • 用next硬件上下文替換prev

上面的操作使用一個switch_to宏來實現,傳入參數prev,next,prev。傳入兩次prev是怕切換上下文后,把第一個prev丟了。

創建進程

Linux進程的特性:

  • 寫時復制
  • 輕量級進程允許父子進程共享很多數據結構

創建進程的系統調用:

  • close()

    • fn 子進程創建后執行的函數,函數結束,子進程終止
    • arg 傳給函數的數據
    • 其他還有很多參數
  • fork close函數的封裝

  • vfork close函數的封裝

內核進程

內核進程是一直運行在內核態的

進程0
進程0是linux啟動后的第一個進程,由它創建進程1
進程1
進程1也叫init進程,進程1會一直運行知道linux關閉

撤銷進程

進程執行完指定的代碼后,就會終止,這時必須通知內核回收進程的資源。
一般是exit系統調用,c編譯程序會自己動把exit函數插入到main函數最后
內核可以強迫整個線程組死掉(例如收到kill -9)

進程刪除
當進程終止后,進程會進入僵死狀態,直到父進程調用wait4來獲取進程的狀態數據,然后進程就會被刪除。
如果父進程已經不存在,進程會交給init進程托管,init進程會定期執行wait4命令來查看進程的狀態,如果進程已經終止,就會刪除這個進程


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM