Linux下2號進程的kthreadd--Linux進程的管理與調度(七)

来源:https://www.cnblogs.com/linhaostudy/archive/2018/09/13/9641651.html
-Advertisement-
Play Games

2號進程 內核初始化rest_init函數中,由進程 0 (swapper 進程)創建了兩個process init 進程 (pid = 1, ppid = 0) kthreadd (pid = 2, ppid = 0) 所有其它的內核線程的ppid 都是 2,也就是說它們都是由kthreadd t ...


2號進程

內核初始化rest_init函數中,由進程 0 (swapper 進程)創建了兩個process

  • init 進程 (pid = 1, ppid = 0)
  • kthreadd (pid = 2, ppid = 0)

所有其它的內核線程的ppid 都是 2,也就是說它們都是由kthreadd thread創建的

所有的內核線程在大部分時間里都處於阻塞狀態(TASK_INTERRUPTIBLE)只有在系統滿足進程需要的某種資源的情況下才會運行

它的任務就是管理和調度其他內核線程kernel_thread, 會迴圈執行一個kthreadd的函數,該函數的作用就是運行kthread_create_list全局鏈表中維護的kthread, 當我們調用kernel_thread創建的內核線程會被加入到此鏈表中,因此所有的內核線程都是直接或者間接的以kthreadd為父進程

2號進程的創建

在rest_init函數中創建2號進程的代碼如下

pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
rcu_read_lock();
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
rcu_read_unlock();
complete(&kthreadd_done);

2號進程的事件迴圈

int kthreadd(void *unused)
{
    struct task_struct *tsk = current;

    /* Setup a clean context for our children to inherit. */
    set_task_comm(tsk, "kthreadd");
    ignore_signals(tsk);
    set_cpus_allowed_ptr(tsk, cpu_all_mask);            //  允許kthreadd在任意CPU上運行
    set_mems_allowed(node_states[N_MEMORY]);

    current->flags |= PF_NOFREEZE;

    for (;;) {
            /*  首先將線程狀態設置為 TASK_INTERRUPTIBLE, 如果當前
            沒有要創建的線程則主動放棄 CPU 完成調度.此進程變為阻塞態*/
            set_current_state(TASK_INTERRUPTIBLE);
            if (list_empty(&kthread_create_list))  //  沒有需要創建的內核線程
                    schedule();                                 //   什麼也不做, 執行一次調度, 讓出CPU

             /*  運行到此表示 kthreadd 線程被喚醒(就是我們當前)
            設置進程運行狀態為 TASK_RUNNING */
            __set_current_state(TASK_RUNNING);

            spin_lock(&kthread_create_lock);                    //  加鎖,
            while (!list_empty(&kthread_create_list)) {
                    struct kthread_create_info *create;

                    /*  從鏈表中取得 kthread_create_info 結構的地址,在上文中已經完成插入操作(將
                    kthread_create_info 結構中的 list 成員加到鏈表中,此時根據成員 list 的偏移
                    獲得 create)  */
                    create = list_entry(kthread_create_list.next,
                                        struct kthread_create_info, list);

                    /* 完成穿件後將其從鏈表中刪除 */
                    list_del_init(&create->list);

                    /* 完成真正線程的創建 */
                    spin_unlock(&kthread_create_lock);  

                    create_kthread(create);

                    spin_lock(&kthread_create_lock);
            }
            spin_unlock(&kthread_create_lock);
    }

    return 0;
}

kthreadd的核心是一for和while迴圈體。

在for迴圈中,如果發現kthread_create_list是一空鏈表,則調用schedule調度函數,因為此前已經將該進程的狀態設置為TASK_INTERRUPTIBLE,所以schedule的調用將會使當前進程進入睡眠。

如果kthread_create_list不為空,則進入while迴圈,在該迴圈體中會遍歷該kthread_create_list列表,對於該列表上的每一個entry,都會得到對應的類型為struct kthread_create_info的節點的指針create.

然後函數在kthread_create_list中刪除create對應的列表entry,接下來以create指針為參數調用create_kthread(create).

create_kthread的過程如下

create_kthread完成內核線程創建

static void create_kthread(struct kthread_create_info *create)
{
    int pid;

#ifdef CONFIG_NUMA
    current->pref_node_fork = create->node;
#endif
    /* We want our own signal handler (we take no signals by default). 
    其實就是調用首先構造一個假的上下文執行環境,最後調用 do_fork()
    返回進程 id, 創建後的線程執行 kthread 函數
    */
    pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);
    if (pid < 0) {
            /* If user was SIGKILLed, I release the structure. */
            struct completion *done = xchg(&create->done, NULL);

            if (!done) {
                    kfree(create);
                    return;
            }
            create->result = ERR_PTR(pid);
            complete(done);
    }
}

在create_kthread()函數中,會調用kernel_thread來生成一個新的進程,該進程的內核函數為kthread,調用參數為

pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);

我們可以看到,創建的內核線程執行的事件kthread

此時回到 kthreadd thread,它在完成了進程的創建後繼續迴圈,檢查 kthread_create_list 鏈表,如果為空,則 kthreadd 內核線程昏睡過去

那麼我們現在回想我們的操作
我們在內核中通過kernel_create或者其他方式創建一個內核線程, 然後kthreadd內核線程被喚醒, 來執行內核線程創建的真正工作,於是這裡有三個線程

  1. kthreadd已經光榮完成使命(接手執行真正的創建工作),睡眠
  2. 喚醒kthreadd的線程由於新創建的線程還沒有創建完畢而繼續睡眠 (在 kthread_create函數中)
  3. 新創建的線程已經正在運行kthread,但是由於還有其它工作沒有做所以還沒有最終創建完成.

新創建的內核線程kthread函數

static int kthread(void *_create)
{
    /* Copy data: it's on kthread's stack 
     create 指向 kthread_create_info 中的 kthread_create_info */
    struct kthread_create_info *create = _create;

     /*  新的線程創建完畢後執行的函數 */
    int (*threadfn)(void *data) = create->threadfn;
    /*  新的線程執行的參數  */
    void *data = create->data;
    struct completion *done;
    struct kthread self;
    int ret;

    self.flags = 0;
    self.data = data;
    init_completion(&self.exited);
    init_completion(&self.parked);
    current->vfork_done = &self.exited;

    /* If user was SIGKILLed, I release the structure. */
    done = xchg(&create->done, NULL);
    if (!done) {
            kfree(create);
            do_exit(-EINTR);
    }
    /* OK, tell user we're spawned, wait for stop or wakeup
     設置運行狀態為 TASK_UNINTERRUPTIBLE  */
    __set_current_state(TASK_UNINTERRUPTIBLE);

     /*  current 表示當前新創建的 thread 的 task_struct 結構  */
    create->result = current;
    complete(done);
    /*  至此線程創建完畢 ,  執行任務切換,讓出 CPU  */
    schedule();

    ret = -EINTR;

    if (!test_bit(KTHREAD_SHOULD_STOP, &self.flags)) {
            __kthread_parkme(&self);
            ret = threadfn(data);
    }
    /* we can't just return, we must preserve "self" on stack */
    do_exit(ret);
}

線程創建完畢:

創建新 thread 的進程恢復運行 kthread_create() 並且返回新創建線程的任務描述符
新創建的線程由於執行了 schedule() 調度,此時並沒有執行.

直到我們使用wake_up_process(p);喚醒新創建的線程

線程被喚醒後, 會接著執行threadfn(data)

ret = -EINTR;

    if (!test_bit(KTHREAD_SHOULD_STOP, &self.flags)) {
            __kthread_parkme(&self);
            ret = threadfn(data);
    }
    /* we can't just return, we must preserve "self" on stack */
    do_exit(ret)

總結

kthreadd進程由idle通過kernel_thread創建,並始終運行在內核空間, 負責所有內核線程的調度和管理,它的任務就是管理和調度其他內核線程kernel_thread, 會迴圈執行一個kthreadd的函數,該函數的作用就是運行kthread_create_list全局鏈表中維護的kthread, 當我們調用kernel_thread創建的內核線程會被加入到此鏈表中,因此所有的內核線程都是直接或者間接的以kthreadd為父進程

我們在內核中通過kernel_create或者其他方式創建一個內核線程, 然後kthreadd內核線程被喚醒, 來執行內核線程創建的真正工作,新的線程將執行kthread函數, 完成創建工作,創建完畢後讓出CPU,因此新的內核線程不會立刻運行.需要手工 wake up, 被喚醒後將執行自己的真正工作函數

  • 任何一個內核線程入口都是 kthread()
  • 通過 kthread_create() 創建的內核線程不會立刻運行.需要手工 wake up.
  • 通過 kthread_create() 創建的內核線程有可能不會執行相應線程函數threadfn而直接退出

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 進程的概念和與程式的區別 1、進程的定義 進程是允許某個併發執行的程式在某個數據集合上的運行過程。 進程是由正文段、用戶數據段及進程式控制制塊共同組成的執行環境。正文段存放被執行的機器指令,用戶數據段存放進程在執行時直接進行操作的用戶數據。進程式控制制塊存放程式的運行環境,操作系統通過這些數據描述和管理進程 ...
  • 下載 clamwin 到 windows 並安裝http://www.clamwin.com/為了方便使用clamwin,寫一個bat,實現拖拽到bat 自動查毒@echo offmode con cols=100 lines=2REM 拖拽查毒REM 文件全名為: %~nx1, 文件名為: %~n... ...
  • ...
  • 在ARM開發中,經常使用的開發環境就是Keil uVision集成開發環境+JLink模擬器,本文就是就是介紹、總結使用該開發環境中遇到的問題,併在問題後方附上親測可行的解決方法。 ...
  • 經常會通過ssh登錄遠程伺服器,一種是通過密碼方式登錄,一種是通過公鑰登錄。 如何設置通過公鑰登錄伺服器 1. 先生成公鑰和私鑰 此時,會在存放ssh秘鑰的地方生成兩個文件(不同系統,存放秘鑰的地方不同),“.pub”結尾的是公鑰,另一個是私鑰 2. 第二步,將公鑰部署到伺服器 公鑰需要寫入到服務的 ...
  • 一、安裝 參考:https://dev.mysql.com/doc/refman/5.7/en/linux-installation-yum-repo.html 二、配置 三、導庫 ...
  • 執行命令 本地上傳文件到伺服器 ...
  • 第1章 Ansible概述 Ansible是一個配置管理系統configuration management system python 語言是運維人員必須會的語言 ansible 是一個基於python 開發的自動化運維工具 其功能實現基於ssh遠程連接服務 ansible 可以實現批量系統配置, ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...