1. 程式人生 > >kernel筆記——內核編譯與進程管理

kernel筆記——內核編譯與進程管理

代碼 IE x86 nis 版本 href 界面 fork函數 linux下

內核與操作系統

由於一些商業操作系統設計上的缺陷以及日益龐雜,“操作系統”的概念對很多人而言變得含糊不清。在進一步討論Linux內核的話題前,我們先區分“內核”與“操作系統”這兩個概念。

  • 操作系統:指在整個系統中完成最基本功能和系統管理的部分,包括內核、設備驅動、文件管理工具、系統管理工具、shell命令行或其他用戶界面(gnome/KDE等)
  • 內核:是操作系統的核心,完成進程管理、cpu調度、內存管理、中斷處理等功能

一般我們編寫的應用程序,跑在操作系統上,完成文字編輯、音樂播放、網頁遊覽等特定功能。

內核編譯

內核源碼一般放在/usr/src目錄下,我們也可以從這裏獲取所需內核版本的源碼包。編譯內核的第一步是配置內核功能,例如配置是否支持對稱多處理器(SMP),可通過設置CONFIG_SMP的值。

通常我們使用"make menuconfig"命令進行配置,其提供了友好的配置界面:

保存配置後,源碼目錄下將生成.config配置文件,打開該文件,可以看到其內容為各種選項設置:

  1. CONFIG_X86_64=y
  2. CONFIG_64BIT=y
  3. CONFIG_X86=y
  4. CONFIG_SEMAPHORE_SLEEPERS=y
  5. CONFIG_MMU=y
  6. ……

我們也可以使用當前的內核配置,使用以下命令快速地生成.config文件:

  1. zcat /proc/config.gz > .config

之後根據.config配置,對源碼進行編譯:

  1. make -j4

以上使用-j選項,指定並行編譯工作任務數目,在多核環境下,減少了編譯時間。

編譯完成後生成內核壓縮鏡像:

  1. make bzImage

生成的內核壓縮鏡像文件位於 arch/x86/boot目錄下:

  1. linux-2.6.32.59 # ll arch/x86/boot/bzImage
  2. -rw-r--r-- 1 root root 2814112 07-02 22:27 arch/x86/boot/bzImage

接著安裝內核模塊:

  1. make modules_install

新的模塊會被放置在/lib/modules目錄下:

  1. /lib/modules # ll
  2. 總計 8
  3. drwxr-xr-x 4 root root 4096 03-08 23:53 2.6.32.12-0.7-default
  4. drwxr-xr-x 3 root root 4096 07-02 23:31 2.6.32.59-0.7-default

最後執行make install安裝內核,在/boot目錄下將生成System.map、vmlinuz和initrd文件:

  1. linux-2.6.32.59 # make install
  2. sh /home/lx/kernel/linux-2.6.32.59/arch/x86/boot/install.sh 2.6.32.59-0.7-default arch/x86/boot/bzImage \
  3. System.map "/boot"
  4. Kernel image: /boot/vmlinuz-2.6.32.59-0.7-default
  5. Initrd image: /boot/initrd-2.6.32.59-0.7-default
  6. ……

完成安裝後,在/boot/grub/menu.lst文件中增加了新內核相應的啟動項,我們可以修改該文件,指定系統啟動後使用新編譯的內核。

進程與線程

Linux下,進程與線程的最大不同是進程擁有獨立的內存地址空間,而線程與其他線程共享內存地址空間。除此之外,進程與線程的實現基本相同,都有task_struct結構,都被分配PID。

內核線程沒有獨立的地址空間,它們完成特定工作並接受內核的調度,不同於一般用戶進程,它們不接收kill命令發送的信號:

  1. F S UID PID PPID C PRI NI ADDR SZ WCHAN STIME TTY TIME CMD
  2. 1 S root 2 1 0 -40 - - 0 migrat Jul01 ? 00:00:00 [migration/0]
  3. 1 S root 3 1 0 94 19 - 0 ksofti Jul01 ? 00:00:00 [ksoftirqd/0]
  4. 5 S root 18 1 0 70 -5 - 0 worker Jul01 ? 00:00:00 [events/0]
  5. ……

task_struct

task_struct結構包含進程使用的虛擬內存、打開的文件、進程狀態、進程pid等信息,占用的內存由slab分配,在文件中定義。thread_info結構的第一個字段為task_struct類型的指針,當進程創建時,thread_info存放在進程內核棧的頂部:

current全局變量指向當前運行進程的task_struct結構,由於thread_info存放的位置固定,這樣我們通過以下匯編指令就能很容易地計算出current的值:

  1. movl $-8192, %eax
  2. andl $esp, %eax

進程狀態

進程可處於以下幾種狀態:

  1. RUNNING
  2. INTERRUPTABLE
  3. UNINTERRUPTABLE
  4. STOP
  5. ZOMBIE

這些進程狀態作為宏,在sched.h文件中被定義。

  • RUNNING狀態表示進程是可執行的,或正在執行或在運行隊列中,這些進程占用或等待cpu資源。
  • 進程調用退出函數exit後,進程中止,進入ZOMBIE狀態。在ZOMBIE狀態下進程不會占用cpu,但因其task_struct結構尚未釋放,仍占用一點內存,直到父進程調用wait函數接受子進程遺願,假如父進程先於子進程退出,則由init進程接受子進程遺願。如果一個進程長期處於ZOMBIE狀態,則是父進程中未調用wait,為程序編碼問題。
  • UNINTERRUPTABLE狀態表示進程不可中斷,處於此狀態的進程處於內核態,並且不接收任何信號。

設置進程狀態的函數為set_task_state函數,在文件中定義。

進程間關系

進程間關系與目錄結構一樣,為樹狀結構,目錄結構以/為根,而進程關系以init為根。我們可以使用pstree查看進程間關系:

  1. linux-14:~ # echo $$
  2. 10939
  3. linux-14:~ # pstree -G -p 10939
  4. bash(10939)─┬─pstree(12806)
  5. └─sh(12796)───sleep(12801)

內核代碼中提供了一條雙向閉環鏈表,自init進程始,鏈表連接了所有進程的task_struct結構,可以通過for_each_process宏遍歷系統的所有進程:

  1. #define for_each_process(p) \
  2. for (p = &init_task ; (p = next_task(p)) != &init_task ; )

進程創建

Linux kernel將進程創建的步驟分成兩步:fork和exec。fork生成子進程的pid,將父進程執行上下文、打開的文件描述符等內容復制一份給子進程;exec將子進程自己的執行上下文加載進內存地址空間。有以下fork例子,問執行該程序將輸出多少個1?

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. int main()
  4. {
  5. int i;
  6. for(i=0 ; i < 10; i++)
  7. {
  8. fork();
  9. }
  10. printf("%d\n", 1);
  11. return 0;
  12. }

fork拷貝父進程的內容到子進程,開銷較大,假若調用fork之後馬上調用exec,子進程加載自己的執行文件,則拷貝的動作就是多余的。寫時拷貝(copy-on-write,COW)解決了拷貝帶來無謂開銷的問題,在子進程寫父進程地址空間時,才觸發拷貝的動作。不做多余事情、非到不得已的時候才完成工作,這也是Linux kernel高效的原因之一。

內核中do_fork函數完成fork調用的工作,do_fork調用copy_process。copy_process函數中主要完成以下工作:

  1. 調用dup_task_struct函數申請新進程的task_struct、thread_info結構
  2. 根據clone_flags標誌,調用copy_files、copy_fs、copy_mm等函數完成文件、文件系統、內存等信息的拷貝
  3. 調用alloc_pid申請新進程pid

fork返回兩次,在do_fork函數中實現。

進程中止

最終進程會調用exit函數中止,exit系統調用最終會調用內核中do_exit函數,do_exit在中定義,其完成以下工作:

  1. 調用exit_signals設置進程flags標誌為PF_EXITING
  2. 調用exit_mm、exit_files、exit_fs等函數釋放進程內存、文件、文件系統等結構
  3. 設置進程的exit_code
  4. 調用exit_notify,向當前進程的父進程、子進程發送信號,告知當前進程將要中止,並設置當前進程退出狀態exit_state為EXIT_DEAD或EXIT_ZOMBIE
  5. 調用schedule,切換到另一個進程,從do_exit函數不會返回到調用它的函數

Reference: Chapter 1 to chapter 3, Linux kernel development.3rd.Edition

kernel筆記——內核編譯與進程管理