国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統 > Linux > 正文

Linux內核設計與實現 讀書筆記

2024-06-28 13:21:09
字體:
來源:轉載
供稿:網友
linux內核設計與實現 讀書筆記

第三章 進程管理

1. fork系統調用從內核返回兩次: 一次返回到子進程,一次返回到父進程2. task_struct結構是用slab分配器分配的,2.6以前的是放在內核棧的棧底的;所有進程的task_struct連在一起組成了一個雙向鏈表3. 2.6內核的內核棧底放的是thread_info結構,其中有指向task_struct的指針;4. current宏可以找到當前進程的task_struct;X86是通過先找到thread_info結構,而PPC是有專門的寄存器存當前task_struct(R2)5. 內核棧大小一般是8KB6. 進程的五種狀態:TASK_RUNNING, TASK_INTERRUPTABLE, TASK_UNINTERRUPTABLE(處于改狀態的進程不能被kill,因為它可能正在等待很關鍵的數據,或者持有了信號量等), TASK_TRACED(被其他進程跟蹤狀態,具體狀態表現不明), TASK_STOPPED(收到SIG_STOP信號,停止進程,相當于暫停進程,但是也可以恢復)7. 運行上下文分為“進程上下文”和“中斷上下文”。系統調用時內核代表進程在進程上下文中執行代碼,這時current宏是有效的,指向進程的task_struct,而且系統調用時內核使用的頁表是用戶態進程的頁表;而在中斷上下文內核不代表任何進程執行代碼,而是執行一個中斷處理程序,不會有進程去干預這些中斷上下文,所以此時不存在進程上下文。8. 系統調用在陷入內核的瞬間應該是在中斷上下文的,因為是軟中斷,只是陷入內核后又用了進程上下文9. 在每個進程的task_struct結構中,有一個parent指針指向其父進程,有一個鏈表表示其所有的子進程,用這樣的結構構成了整個系統進程關系樹。10. 內核的雙向列表專用結構struct list_head11. 進程創建分為兩個步驟:fork和exec,fork用來創建進程的結構,通過寫時復制,父子進程共享進程空間(頁表),父進程和子進程的區別僅僅是PID,PPID,某些資源,統計量(task_struct的結構); exec讀出程序代碼并執行之。通過寫時復制,只有在需要寫入進程地址空間時,才為子進程創建自己的進程地址空間。*** fork的開銷其實就是復制父進程的頁表 和 為子進程分配task_struct 結構fork的過程: fork() -> clone() -> do_fork() -> copy_PRocess()copy_process()的過程:a) 為子進程分配內核棧,創建thread_info結構,創建一份與父進程相同的task_struct結構b) 更改thread_info ,task_struct結構中的部分字段,將子進程與父進程區分開來c) 將子進程的狀態設置為TASK_UNINTERRUPTIBLEd) 為子進程分配一個可用的PID(alloc_pid())e) 拷貝或共享父進程打開的文件,信號處理函數,進程地址空間等f) 設置子進程狀態為RUNNINGg) 返回一個指向子進程的指針一般系統會優先喚醒子進程,因為如果優先喚醒父進程,父進程就有可能會寫入,這樣會觸發寫時復制,而子進程一般會調用exec12. vforkvfork保證父進程在創建子進程后被阻塞,除非子進程執行了exec,或者子進程退出vfork在fork有寫時復制功能后的好處只有一個,就是:vfork不用拷貝子進程的頁表13. 線程線程在linux內核中的實現就是一個進程,只是線程會與別的線程共享進程地址空間,共享信號等等創建一個有四個線程的進程,會有四個進程被創建(四個內核棧和四個task_struct),只要指明這些task_struct中共享同一個進程地址空間即可線程的創建和進程的創建的代碼上的對比:線程創建: clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0)進程創建: clone(SIG_CHILD, 0)從線程創建的代碼也可以看出:線程創建時是共享父進程的 進程地址空間(CLONE_VM), 打開的文件(CLONE_FILES),文件系統信息(CLONE_FS), 信號處理函數(CLONE_SIGHAND)14. 內核線程內核進程需要在后臺執行一些操作,所以需要創建一些內核線程(kernel thread)內核線程和普通線程的主要區別是: 內核線程沒有獨立的地址空間(task_struct中的mm指針是NULL),共享使用內核態的頁表; 內核線程只在內核運行,從來不會調度到用戶態;內核線程和普通線程的相同點有: 同樣有狀態,被調度,也可以被搶占有哪些內核線程:flush,ksoftirqd等, 用ps -ef可以查看,其中CMD欄是[]擴起來的都是內核線程創建方法: kthread_create(); wake_up_process()用來喚醒創建的線程; kthread_run()可以創建并使之運行; kthread_stop()停止內核線程15. 孤兒進程如果父進程比子進程先執行完,父進程要在線程組中找一個新的進程作為子進程的父進程,或者找init進程作為子進程的父進程16. 進程消亡進程消亡時是通過exit()來實現的,進行exit之后內核依然會保留其task_struct結構,直到其父進程調用wait()或waitpid()回收
第四章 進程調度1. linux是搶占式多任務系統2. 通過調度程序選擇一個進程來執行, 調度程序來決定什么時候掛起一個進程的運行,以便讓其他進程得到允許機會,這種掛起操作叫做搶占。3. 進程在被搶占之前能獲得運行的時間叫做進程的時間片。進程的時間片是固定的,預先設置好的。4. yield(), 進程可以通過該函數讓渡被調度權5. 調度算法a) O(1)的調度程序O(1)調度程序對大服務器的工作負載下應用很理想,但是在交互式場景下不理想b) CFS完全公平調度算法改進了linux對交互式場景的不足6. IO消耗性進程和CPU消耗性進程linux調度程序通常更傾向于優先調度IO消耗性進程,但是也并未忽略CPU消耗性進程7. 進程優先級Linux采用了兩種表示進程優先級的方法:a) nice值,nice值本來是Unix的標準做法。在linux中,nice值代表的是時間片的比例,nice值越大優先級越低,范圍是-20到19b) 實時優先級,范圍是0到99,越大優先級越高8. 時間片時間片過長,導致對IO消耗性進程的支持不好;時間片過短,進程調度就花去了更多的時間9. CFS調度算法實際上分給每個進程的是處理器占用比,這個占用比也會受到nice值得影響例子:假設系統只有兩個進程,一個文本編輯程序(IO消耗性),一個視頻編解碼程序(CPU消耗性),系統初始時他們有同樣的nice值,所以在啟動后給它們分配的處理時間都是一樣的,都是50%,因為文本編輯器消耗很少的CPU,所以它的CPU時間占比遠小于本應該分配給他的50%,而視頻程序就占用了超過50%的CPU時間,所以當文本編輯程序需要運行時,調度程序發現它的CPU時間比它應得的少很多,所以馬上讓他搶占運行;當文本編輯器運行完畢后,又進入等待,所以它消耗的CPU時間依然少,這樣系統就能不斷的馬上響應文本編輯程序。CFS調度算法的主要思想是保證系統的公平使用,通過了這種方法可以自動的發現各個進程的CPU使用情況,根據這個使用情況動態的調整進程的調度和分配。CFS為每個進程被搶占前能運行的時間片的最小值是1ms。問題來了,一個運行了很久的IO消耗性進程和一個剛開始運行的CPU消耗性進程相比,可能會讓IO消耗性進程被調度的可能性變慢,所以是不是說如果進程執行的時間過長了,要重啟一下?10. Linux有多種調度器算法不同的進程被歸入不同的調度器類中schedule()從最高優先級的調度器中選擇一個最高優先級的進程來調度完全公平調度CFS是一種針對普通進程的調度器,linux中稱為SCHED_NORMAL還有實時進程調度器11. Linux是何時運行調度器的?a)b) linux是通過need_resched這個標識來表明是否要進行執行一次調度的,哪些地方會設置這個標志:schedule_tick(), try_to_wake_up()等; need_resched標志保存在進程的thread_info里,這是因為訪問current比訪問全局變量更快c) 在返回用戶空間或者中斷返回的時候,內核也會檢查need_resched標志,如果被設置,系統會在繼續運行之前調用調度程序d) 搶占發生的時間:d.1) 用戶搶占d.1.1) 從系統調用返回用戶空間時d.1.2) 從中斷服務程序返回用戶空間時d.2) 內核搶占d.2.1) 從中斷服務程序返回內核空間時d.2.2) 內核代碼再一次具有可搶占性的時候:這里包含以下的含義: 只有進程沒有持有鎖就可以被搶占,如果持有了鎖,系統是不可搶占的,在釋放鎖的時候且preempt_count減少到0的時候,說明當前可以被安全的搶占了,這時檢查need_resched標志進行搶占。d.2.3) 內核顯式調用schedule()d.2.4) 內核任務阻塞調度器入口:schedule()函數,作用是從最高優先級的調度器中選擇一個最高優先級的進程來調度12,睡眠和喚醒當進程要等待時將自己的進程狀態改成INTERRUPTIABLE或者UNINTERRUPTIABLE狀態,并把自己從調度紅黑樹中移出到等待隊列中,再調用schedule()調度下一個進程來運行睡眠時將進程掛到相應的等待隊列上:DEFINE_WAIT(wait);add_wait_queue(q, &wait);while(!condition){ prepare_to_wait(&q, &wait, TASK_INTERRUPTIABLE); if(signal_pending(current)) ... schedule();}finish_wait(&q, &wait); //把自己移出等待隊列喚醒wake_up()函數喚醒掛在等待隊列上的所有進程,把這些進程的狀態改成TASK_RUNNING,并把它加入到調度紅黑樹上,如果被喚醒的進程優先級比當前的優先級高,還要設置need_reschedule標志。喚醒要注意的是:可能存在虛假喚醒,可能是收到了信號喚醒了進程。所以在等待時要用一個while循環檢查是否滿足了條件,如果不滿足可能是虛假喚醒,必須繼續wait。13. 搶占和上下文切換上下文切換就是從一個進程切換到另一個進程去,用context_switch()函數完成,該函數在schedule()中被調用,該函數主要完成兩個工作:switch_mm():把進程的虛擬地址空間切換switch_to():切換進程的處理器狀態,保存、恢復棧信息和寄存器信息,還有其他任何與體系結構相關的狀態信息14. 實時調度器兩種實時調度策略:SCHED_FIFO和SCHED_RRSCHED_FIFO:先進先出,一直執行,直到自己釋放CPU或者等待,無時間片概念SCHED_RR: 與SCHED_FIFO類似,但是有時間片概念,在耗盡預先分配給它的時間片之后就重新調度如何設置進程是實時進程?
第五章 系統調用1. 系統調用是什么?為什么要引入系統調用?系統調用是用戶進程和硬件設備之間的一個中間層引入系統調用有三個原因:a) 給用戶提供一個統一的,抽象的接口和硬件設備打交道b) 通過系統調用這個中間層,防止用戶異常操作硬件設備c) 虛擬化的思想,用戶進程都是作為一個個單獨的實體運行在虛擬空間中,在系統和用戶進程之間提供這樣的一層接口也是出于這個考慮,類似一個硬件上安裝了多個虛擬機一樣2. API, POSIX, C庫POSIX是一套通用的API接口標準C庫實現了POSIX規定的絕大部分API用戶態調用流程:應用程序 -> C庫 -> 系統調用Linux系統調用也是作為C庫的一部分提供3. Unix接口設計的名言“提供機制而不是策略”—— 含義是:系統調用抽象了用于完成某種確定的目的的函數,至于這些函數怎么用完全不需要內核關心,是應用程序和C庫來關心的。其實設計任何API都有這樣的需求:只提供完成特定任務的接口,具體如何使用這個API是由使用者來關心的區別機制和策略會簡化開發,機制是“需要提供什么功能”,策略是“怎樣實現這些功能”。這樣可以利用相同的API來適應不同的需求。4. syscall tablesys_call_table中保存了所有系統調用號的處理函數5. 中斷陷入a) 通過軟中斷中斷號是128int $0x80b) sysenter指令x86提供的新的進入系統調用的方法,更快更專業6. 系統調用的返回值和errno每個系統調用都會有返回值,返回值一般是long類型,為0表示成功,負數表示失敗;返回值除了表示成功失敗以外,根據系統調用的具體實現,可以返回功能結果,如getpid()系統調用就返回piderrno全局變量內保存的是錯誤號,可以通過perror()來獲得錯誤描述。errno作為全局變量如何在多核上使用呢?7. 系統調用參數和返回值的傳遞系統調用時需要傳遞系統調用號和參數。系統調用號總是用eax傳遞,當參數個數小于5個時,用寄存器傳遞(ebx, ecx, edx, edi, esi), 當超過5個時,應該用一個單獨的寄存器存放指向所有參數的用戶空間地址的指針返回值是通過eax傳遞的8. 用戶空間和內核空間的數據拷貝copy_to_user(dst, src, size);copy_from_user(dst, src, size);其實直接拷貝也是可以的。這兩個函數主要是加了一些使用檢查,對用戶提供的指針進行檢查,不讓用戶空間通過系統調用來操作內核空間的地址注意copy_to_user和copy_from_user都可能引起阻塞,當數據被交換到硬盤上時就會發生這種情況,此時,進程就會休眠,直到被喚醒后繼續執行或者調用調度程序9. 系統調用要做很多檢查工作,因為輸入來自用戶態,不能讓用戶態的錯誤操作導致內核態數據的錯誤capable()函數可以做一些權限檢查10. 系統調用是可睡眠的和可搶占的可睡眠的保證了系統調用可以使用大部分的內核接口11. 函數可重入性系統調用要保證實現時可重入的,因為系統調用時允許被搶占的,所以當新的進程也調用該系統調用時保證可重入才不會出錯。12. 不靠C庫的支持,直接使用系統調用的方法例如:使用open系統調用#define NR_open 5_syscall3(long, open, const char *, filename, int, flags, int, mode)_syscall3是一個宏,它會設置好寄存器,并調用陷入指令。通過這個宏就創建了一個open()函數,返回值是long,有三個參數,這時就可以直接使用 long fd = open(filename, flags, mode); 調用系統調用了13. 最好不要新加系統調用,而是使用一些替代方案替代方案:a) 對于設備節點,可以使用ioctl自定義命令進行操作b) 對于信號量這種,其實也是文件描述符,所以也可以用ioctlc) 利用/proc或者/sysfs文件系統來和內核交互
第六章 關鍵內核數據結構1. 鏈表,隊列,映射,二叉樹2. 鏈表經典的list_head循環雙向鏈表struct list_head{ struct list_head *next; struct list_head *prev;};container_of宏,list_entry宏,可以通過這個宏方便的找到list_head所在的結構的首地址offset_of(type, member):獲得member在type結構中的offset偏移,container_of中用到了這個宏#defineoffsetof(struct_t,member)((size_t)(char*)&((struct_t*)0)->member)#define container_of(ptr, type, member) ({ /const typeof( ((type *)0)->member ) *__mptr = (ptr); /(type *)( (char *)__mptr - offsetof(type,member) );})#define list_entry(ptr, type, member) / container_of(ptr, type, member)INIT_LIST_HEAD(struct list_head); // 初始化list_head鏈表頭:LIST_HEAD()操作方法:list_add(new, head);list_add_tail(new, head);list_del(ptr);list_del_init(ptr); //刪除并初始化該list_headlist_move(list, head); // 從一個鏈表中刪除list,并加到head鏈表后面list_move_tail(list, head); // 從一個鏈表中刪除list_empty(head); //判斷是否為空遍歷鏈表:struct fox{ int i; struct list_head *list;};struct list_head *head = ...;struct fox *f;struct list_head *p;list_for_each(p, head) { // 循環遍歷鏈表 f = list_entry(p, struct fox, list);}另一個宏:list_for_each_entry(f, head, list); 可以實現和上面 list_for_each{ } 塊一樣的功能該宏的聲明如下:list_for_each_entry(pos, head, member);還有一個反向遍歷的:list_for_each_entry_reverse(pos, head, member);還有如果遍歷的同時要刪除的:list_for_each_entry_safe(pos, next, head, member); // 多了一個next的struct list_head指針,用來記錄nextlist_for_each_entry_safe_reverse(pos, next, head, member);如果要并發操作鏈表,必須使用鎖。3. 隊列FIFO。生產者消費者模型kfifo是內核的通用實現。創建隊列:struct kfifo fifo;int ret;int size = PAGE_SIZE;ret = kfifo_alloc(&fifo, size, GFP_KERNEL); //size必須是2的冪char buffer[PAGE_SIZE];kfifo_init(&fifo, &buffer, size);入隊列:unsigned int kfifo_in(struct kfifo *fifo, const void *from, unsigned int len); // 拷貝from指向的len大小的數據到fifo中出隊列:unsigned int kfifo_out(struct kfifo *fifo, void * to, unsigned int len); //拷貝出長度為len大小的數據到to中其他操作:見書本4. 映射與std::map類似,有以下操作:Add, Remove, Findlinux實現了一個專用的類似map的結構:是一個唯一的UID到一個指針的映射5. 二叉樹rbtree (看一下紅黑樹的原理)查找操作多的情況可以使用,如果查找的少,不如用鏈表
第七章 中斷和中斷處理1. 中斷上下文又稱原始上下文,該上下文中不可阻塞2. 內核在收到中斷之后要設置設備的寄存器 關閉中斷,設備的配置空間一般有中斷位,采用level中斷方式,必須把這個中斷位設置3. 上半部與下半部例如,網卡,上半部在收到中斷進入中斷上下文后要設置硬件寄存器,同時把數據快速的拷貝到內核空間4. request_irq注冊中斷IRQF_SHARED標志:共享中斷線IRQF_DISABLED標志:處理中斷時關閉其他中斷free_irq()釋放中斷5. 中斷上下文與進程無關,不能睡眠中斷上下文的棧:有兩種:每個cpu單獨的中斷棧;或者使用被中斷進程的內核棧一般進程的內核棧是兩頁,在32位機器上就是8KB,在64位的機器上就是16KB6. /proc/interrupts7. 中斷控制禁止中斷 : local_irq_disable() local_irq_enable();這兩個函數有缺陷:不能嵌套調用,所以有下面兩個函數:local_irq_save(flags) local_irq_restore(flags) 它們會保存中斷狀態(就是之前是被禁止還是被啟用的狀態)這些函數是當作臨界區鎖來用的,當軟中斷上下文和中斷上下文有共享數據時,就要用這些函數來充當鎖local_irq_disable()禁止了所有的中斷禁止指定irq上的中斷:disable_irq(irq) disable_irq_nosync(irq) enable_irq(irq) synchronized_irq(irq)這些函數可以嵌套,所以調用多少次disable就要調用多少次enable共享中斷線的irq不能被禁止,所以這些API主要在老式的設備上采用,PCIe設備強制要求共享中斷線8. 判斷當前是否在中斷上下文in_interrupt() : 內核正在執行中斷處理程序或者下半部時返回非0in_irq() : 內核正在執行中斷處理程序時返回非09. 中斷處理程序只能在一個CPU上運行
第八章 下半部1. 下半部是什么下半部是比中斷處理程序稍緩的任務,可以在中斷處理程序處理完最緊急的任務之后處理的任務2. 下半部可以有多種實現方法上半部只能用中斷處理程序實現,而下半部可以用下列方法實現:軟中斷,tasklet ,工作隊列3. 軟中斷系統最多能注冊32個軟中斷,目前內核總共用的軟中斷有9個一個軟中斷不能搶占另一個軟中斷。唯一可以搶占軟中斷的是中斷處理程序。不過,其他的軟中斷,即使是相同類型的軟中斷,也可以在別的處理器同時運行。通常,中斷處理程序會在返回前標記它的軟中斷,使其在稍后執行。這個步驟叫觸發軟中斷。那么,何時會執行待處理的軟中斷呢?a) 在中斷返回時b) 在ksoftirqd內核線程中c) 在顯示的檢查和執行待處理的軟中斷的代碼中,如網絡子系統中do_softirq()是喚醒軟中斷的函數,其簡化代碼如下:目前只有兩個子系統直接使用軟中斷:網絡子系統和SCSI子系統; tasklet也是用軟中斷實現的注冊軟中斷處理程序:open_softirq(softirq_no, softirq_handler);raise_softirq(softirq_no);軟中斷不能睡眠,能被中斷處理程序搶占。如果同一個軟中斷在它被執行的同時再次被觸發了,那么在其他CPU上可以同時執行其處理程序,這意味著要在軟中斷的上下文要采用鎖來保護,但是如果加鎖的話,使用軟中斷的意義就不大了。所以軟中斷中一般使用的是單處理器數據(僅屬于某一個處理器的數據)。所以軟中斷作為BH用的比較少,一般采用tasklet。4. tasklettasklet也是用軟中斷實現的,有兩個軟中斷和tasklet有關:HI_SOFTIRQ, TASKLET_SOFTIRQtasklet有兩個單處理器數據結構: tasklet_vec 和 tasklet_hi_vectasklet 可以保證同一時間里給定類別中只有一個tasklet會被執行,不同類別的tasklet可以同時執行,所以使用tasklet可以不用過多的考慮鎖的問題tasklet的使用:聲明:(name是tasklet的類別)DECLARE_TASKLET(name, func, data);DECLARE_TASKLET_DISABLED(name, funct, data); //聲明默認disable的tasklettasklet處理程序的格式:void tasklet_handler(unsigned long data)調度自己定義的tasklettasklet_schedule(&my_tasklet);啟用和禁用tasklettasklet_disable(&my_tasklet); // 如果指定的tasklet正在執行,則等到執行結束再返回tasklet_enable(&my_tasklet);一個tasklet總在調度它的CPU上執行,這是希望能更好的利用處理器的高速緩存一個tasklet只要在一個CPU上被執行了,就不會同時在另一個CPU上執行它,(因為有可能一個tasklet在執行的時候,中斷處理程序在另一個CPU上又激活了這個tasklet,這樣在另一個CPU上如果發現這個tasklet正在被執行,便不會再執行了)5. 軟中斷調度時機和ksoftirqd/n內核線程由于軟中斷可以被自行重新觸發,所以如果軟中斷中不斷觸發軟中斷,而且軟中斷立即被檢查執行,那么就會導致系統的CPU被軟中斷占用的過多。另一個方案是:自行重新觸發的軟中斷并不馬上被檢查執行,而是在下一次中斷處理程序返回后檢查執行。ksoftirqd/n是在每個cpu上都有一個的內核線程,只要有未處理的軟中斷,在空閑CPU上就會被調度執行。6. 工作隊列工作隊列把工作交給一個內核線程去執行,總是在進程上下文中執行。而軟中斷和tasklet可能在中斷上下文執行,也可能在進程上下文執行。因為總是在進程上下文執行,所以工作隊列可以重新調度甚至睡眠。如果下半部中要允許重新調度,那么可以使用工作隊列。工作者線程: events/nworker_thread()是核心函數:下面是run_workqueue()workqueue_struct, cpu_workqueue_struct, 以及work_struct之間的關系工作隊列的使用:DECLARE_WORK(name, void (*func) (void *), void *data); //靜態創建INIT_WORK(struct work_struct *work, void (*func) (void *), void *data); //動態創建工作隊列處理函數void work_handler(void * data);調度工作執行schedule_work(&work);schedule_delayed_work(&work, delay);問題:每個CPU上都有一個events/n內核線程,那么schedule_work(&work)時是把work掛到哪個CPU上的events list上的呢?7. 禁止下半部local_bh_disable()local_bh_enable()這兩個函數可以嵌套,即disable幾次就需要enable幾次;這是因為這兩個函數使用了preempt_count(搶占計數)來記錄引用計數
第九章 內核同步1. 造成并發的原因a) 中斷b) 軟中斷和taskletc) 內核搶占:內核任務別搶占d) 睡眠以及與用戶空間的同步:內核任務被用戶進程搶占e) SMP CPU:多處理器并行執行用戶態進程一般只需要考慮兩個因素:SMP CPU 和 進程搶占SMP CPU可能會并行執行進程搶占需要代碼支持可重入這兩種情況都主要是怕兩個進程(線程)同時訪問全局數據或共享數據,如果兩個進程(線程)不共享數據那么就肯定是安全代碼,如果要共享數據,就必須加鎖2. 中斷安全代碼,SMP安全代碼,搶占安全代碼(即可重入代碼)3. 只要是共享數據,就要加鎖;所以盡量不要共享數據;(個人想法:在要進入IO消耗性代碼時,可以考慮共享數據,例如:在要訪問數據庫時,因為訪問數據庫時肯定要有IO操作,多個線程要訪問同一個數據庫連接時要加鎖,因為反正進程要切換,而且用一個數據庫連接還可以做緩存。如果采用多個數據庫連接,那么緩存也變成了問題)4. 記住:給數據加鎖,不要給代碼加鎖5.6. 死鎖死鎖的原因:多個進程,多個資源;多個進程分別持有了一部分資源,又要請求其它資源;結果互相等待。7. 鎖的爭用和加鎖粒度高度爭用的鎖容易造成系統的瓶頸加鎖粒度:細粒度的鎖可以降低鎖的爭用
第十章 內核同步方法1. atomic_t 原子操作2. 自旋鎖DEFINE_SPINLOCK(mr_lock); // 靜態初始化spin_lock_init(&mr_lock); //動態初始化spin_lock(&mr_lock);...spin_unlock(&mr_lock);注意:在中斷上下文要用spin_lock_irqsave(&mr_lock, flags);...spin_unlock_irqrestore(&mr_lock, flags);如果能確定中斷在加鎖之前是激活的,那么可以用下面的APIspin_lock_irq(&mr_lock);...spin_unlock_irq(&mr_lock);非阻塞操作spin_try_lock(&mr_lock);spin_is_locked(&mr_lock);自旋鎖和下半部,在下半部中要用下面的API:spin_lock_bh()spin_unlock_bh()注意:如果下半部和進程上下文共享數據,就要用這個來加鎖如果下半部和中斷上下文共享數據,就要用spin_lock_irqsave/spin_unlock_irqrestore讀寫自旋鎖DEFINE_RWLOCK(lock)read_lock/read_unlockwrite_lock/write_unlock3. 信號量(semaphore)DECLARE_MUTEX(name)sema_init(sem, count)init_MUTEX(sem)init_MUTEX_LOCKED(sem)獲取信號量:down_interruptible() 如果無法獲得,該函數把進程狀態設為TASK_INTERRUPTIABLE,并進入睡眠down_trylock() 非阻塞獲得semdown() 無法獲得時進入不可中斷睡眠(TASK_UNINTERRUPTIABLE)up() 喚醒讀寫信號量4. 互斥體(mutex)計數是1的信號量DEFINE_MUTEX(mutex)mutex_init(&mutex)mutex_lock(&mutex)mutex_unlock(&mutex)比較:信號量和互斥體:一般應用中互斥體,除非互斥體無法滿足需求信號量和自旋鎖:中斷上下文只能用自旋鎖5. 完成變量思想和信號量類似,只是一種針對更簡單問題的一種解決方案init_completion(struct completion *);wait_for_completion()complete()6. 大內核鎖lock_kernel()unlock_kernel()盡量少用7. 順序鎖特定情況下使用,一般是寫很少,讀很多的時候,而且數據操作簡單時較好8. 禁止搶占preempt_disable()preempt_enable()preempt_enable_no_resched()preempt_count()9. 內存屏障由于編譯器會進行讀寫重排序,所以加入內存屏障來確保讀寫順序在SMP上常見,多個CPU操作同一個數據時,可能一個CPU似乎已經寫入了,但是另一個CPU讀出的還是原來的值,常常出現在多線程共享數據而又沒有加鎖的情況下rmb() 確保跨越rmb()的讀操作不會被重排序wmb() 確保跨越wmb()的寫操作不會被重排序mb() 確保跨越mb()的讀寫操作都不會被重排序smp_rmb() / smp_wmb() / smp_mb()
第12章 內存管理1. 整頁的分配和釋放2. kmalloc和slabkmalloc是基于slab分配器實現的。slab分配器主要解決的是分配不規則字節內存造成的內存碎片的,還有加快內存的分配和釋放時間kmem_cache_create()3. vmallocvmalloc和kmalloc類似,都是分配物理內存,但是kmalloc分配的物理內存和虛擬內存都一定是連續的;而vmalloc分配的虛擬內存是連續的,物理內存可能不連續另外,kmalloc的性能更好,因為vmalloc必須建立專門的頁表項,而kmalloc因為是直接映射的,所以無需頁表(TLB:緩存虛擬地址到物理地址的高速緩存)所以vmalloc在內核中一般只是在要分配大塊內存時使用,例如加載模塊時vfree(void *)是否vmalloc分配的內存4. 棧上的內存管理32位和64位體系結構的頁面大小分別是4KB和8KB,一般內核進程的棧是2頁,所以一般是8KB和16KB千萬不要在內核棧上面分配大量數據,容易造成棧溢出5. 高端內存的映射使用alloc_pages()分配的高端內存返回的是page*結構,因為高端內存沒有直接的邏輯地址映射,所以要建立頁表來映射永久映射: kmap/kunmap (可能睡眠)臨時映射: (在不能睡眠的情況下使用) kmap_atomic/kunmap_atomic (其實是有一組保留的映射地址)6.分配函數的選擇一般用kmalloc和_get_free_pages如果要從高端內存分配,用alloc_pages + kmap如果分配的內存較大,而且無需物理上連續,就用vmalloc如果要做分配管理,請用slab分配器
第十三章 虛擬文件系統1. VFS文件系統抽象層2. Unix的四種和文件系統相關的傳統抽象概念文件,目錄項,索引節點(inode,存儲文件的元數據),mount節點另外,文件系統的控制信息被放在了超級塊中===摘抄===
大部分UNIX文件系統種類具有類似的通用結構,即使細節有些變化。其中心概念是超級塊superblock, i節點inode, 數據塊data block,目錄塊directory block, 。其中超級塊中包含了關于該硬盤或分區上的文件系統的整體信息,如文件系統的大小(其準確信息依賴文件系統)等。 i節點包括除了名字外的一個文件的所有信息,名字與i節點數目一起存在目錄中,目錄條目包括文件名和文件的i節點數目。 i節點包括幾個數據塊的數目,用于存儲文件的數據。 i節點中只有少量數據塊數的空間,如果需要更多,會動態分配指向數據塊的指針空間。這些動態分配的塊是間接塊;為了找到數據塊,這名字指出它必須先找到間接塊的號碼。 
=========3. 超級塊超級塊記錄文件系統基本信息超級塊的操作主要是CRUD inode4. inodeinode存放的是文件或目錄的所有信息:幾個時間,引用計數,uid,gid,文件大小,權限一個inode就代表了一個文件:可以是普通文件,也可以是管道,塊設備,字符設備等inode的操作:CRUD文件,修改權限,truncate,mkdir, mknod, rename等等通過inode可以尋找到dentry對象5. dentry目錄項對象目錄和普通文件都是一個目錄項對象主要用來解析文件路徑目錄項結構中維護了整個文件目錄樹dentry的操作:判斷目錄對象是否有效,比較文件名,查找文件等6. 文件對象文件對象存放的是文件信息:文件路徑,文件操作file_Operations, 文件offset,頁高速緩存地址文件的操作: read/write/lseek/ioctl/fsync/open/close/mmap...
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 海丰县| 丰县| 越西县| 西峡县| 珠海市| 三门县| 城固县| 北宁市| 永德县| 正镶白旗| 茂名市| 民乐县| 平湖市| 遂昌县| 游戏| 靖边县| 会东县| 赤壁市| 彭泽县| 新晃| 江门市| 静宁县| 屏南县| 门头沟区| 察隅县| 甘南县| 绿春县| 博野县| 光泽县| 西畴县| 湟中县| 岢岚县| 商水县| 旺苍县| 甘孜县| 汶川县| 密山市| 岳阳县| 红原县| 景谷| 乐陵市|