Linux中的阻塞機制

来源:https://www.cnblogs.com/CDHQ1666760231/archive/2019/02/12/10364850.html
-Advertisement-
Play Games

我們知道在字元設備驅動中,應用層調用read、write等系統調用終會調到驅動中對應的介面。 可以當應用層調用read要去讀硬體的數據時,硬體的數據未準備好,那我們該怎麼做? 一種辦法是直接返回並報錯,但是這樣應用層要獲得數據需要不斷的調用read去訪問硬體,進程的上下文在用戶空間和內核空間不停的切 ...


我們知道在字元設備驅動中,應用層調用read、write等系統調用終會調到驅動中對應的介面。 可以當應用層調用read要去讀硬體的數據時,硬體的數據未準備好,那我們該怎麼做?

一種辦法是直接返回並報錯,但是這樣應用層要獲得數據需要不斷的調用read去訪問硬體,進程的上下文在用戶空間和內核空間不停的切換,耗費了CPU的資源,降低了系統效率。那麼有沒有更好的辦法呢?  答案是有的,在這種情況下我們就可以利用Linux的阻塞機制,實現阻塞訪問。

一、阻塞和非阻塞

阻塞操作是指在執行設備操作時若不能獲得資源則掛起進程,直到滿足可操作的條件後再進行操作。被掛起的進程進入休眠狀態,被從調度器的運行隊列移走,直到等待的條件被滿足。而非阻塞操作的進程在不能進行設備操作時並不掛起,它或者放棄,或者不停地查詢,直至可以進行操作為止。

二、等待隊列

在 Linux 驅動程式中,可以使用等待隊列(wait queue)來實現阻塞進程的喚醒。wait queue 很早就作為一個基本的功能單位出現在 Linux 內核里了,它以隊列為基礎數據結構,與進程調度機制緊密結合,能夠用於實現內核中的非同步事件通知機制。等待隊列可以用來同步對系統資源的訪問,信號量在內核中也依賴等待隊列來實現。

希望等待特定事件的進程把自己放進合適的等待隊列,並放棄控制權。因此,等待隊列是一組睡眠的進程,當某一條件變為真時,由內核喚醒他們。

等待隊列是一個具有頭節點的雙向迴圈鏈表,把所有睡眠的進程連接起來,每個節點元素都有進程相關的信息

  等待隊列示意圖1   等待隊列示意圖2

1,等待隊列頭

每個等待隊列都有一個等待隊列頭,驅動註意操作等待隊列頭來實現阻塞的功能,二等待隊列項的內容不需要關心,因為等待隊列是由中斷處理程式和主要內核函數修改的,其雙向鏈表必須進行保護,防止多進程同時進行訪問修改,造成不可預知的後果,所以定義了lock來鎖住鏈表操作的區域

等待隊列頭結構體的定義:內核使用等待隊列頭來掛起一個進程,也使用等待隊列頭來喚醒進程

struct __wait_queue_head {

  spinlock_t  lock;          //自旋鎖變數,用於在對等待隊列頭          

  struct list_head task_list;  // 指向等待隊列的list_head

}; 

typedef struct __wait_queue_head wait_queue_head_t;

操作函數

#include <linux/sched.h>

#include <linux/wait.h>

1).定義“等待隊列頭”

wait _ queue _ head _ t my _ queue;

2) .初始化“等待隊列頭”。

void init_waitqueue_head(wait_queue_head_t *);

而下麵的 DECLARE_WAIT_QUEUE_HEAD()巨集可以作為定義並初始化等待隊列頭的“快捷方式”。

DECLARE_WAIT_QUEUE_HEAD(name);

3).條件等待/休眠函數 一邊休眠等待條件

//當cond條件是false(0)則休眠(不可中斷版,不推薦使用)

void wait_event(wait_queue_head_t wq, int cond); 

上面程式的執行過程:

1.用當前的進程描述塊(PCB)初始化一個wait_queue描述的等待任務。

2.在等待隊列鎖資源的保護下,將等待任務加入等待隊列。

3.判斷等待條件是否滿足,如果滿足,那麼將等待任務從隊列中移出,退出函數。

4.如果條件不滿足,那麼任務調度,將CPU資源交與其它任務。

5.當睡眠任務被喚醒之後,需要重覆(2)、(3)步驟,如果確認條件滿足,退出等待事件函數。

使用舉例: flag可以是一個條件表達式

static wait_queue_head_t wq;

init_waitqueue_head(&wq);//初始化等待隊列頭

//if(!flag){ 

while(!flag){ //條件不滿足

if(wait_event_interruptible(wq,flag)) //如果是被其他信號喚醒則返回錯誤

return -ERESTARTSYS;

}

使用 while 而不使用if的原因是:wait_event_interruptible

可以被中斷及信號打斷,使用while(1),可以避免被打斷的情況。

註:其實可以不用加while,查看內核源碼的用法,如果被中斷或者信號打斷,直接返回錯誤。

flag = 1; //先設置條件,再喚醒

wake_up(&wq); //條件滿足時喚醒等待隊列頭上所有的進程

//當cond條件是false(0)則休眠(超時版,timeout是超時值,單位是計數值)

//超時返回值為0 ,被喚醒大於0 需判斷返回值

int wait_event_timeout(wait_queue_head_t wq, int condition, unsigend long timeout);

//當cond條件是false則休眠(可中斷版)

//返回值:打斷:負數,絕對值是錯誤碼,成功0 返回值需要做判斷

int wait_event_interruptible(wait_queue_head_t wq, int condition);

//當cond條件是false則休眠(可超時中斷版)

//打斷:負數,絕對值是錯誤碼; 超時:0; 條件滿足:>0

int wait_event_interruptible_timeout(wait_queue_head_t wq, int condition, unsigend long timeout);

4).喚醒函數 另一邊條件成熟時喚醒

void wake_up(wait_queue_head_t *) //能喚醒所以狀態的進程

void wake_up_interruptible(wait_queue_head_t *) //只適用於interruptible,配對使用

註意:喚醒函數當條件滿足時,一定要先設置條件condition,再喚醒調用喚醒函數。因為等待睡眠函數返回後會首先檢查condition是否滿足,若不滿足會繼續睡

如: counter = count;

wake_up_interruptible(&wq);

2,等待隊列項

定義等待對列:

struct __wait_queue {

     unsigned int flags;  //prepare_to_wait()里有對flags的操作,查看以得出其含義

          #define WQ_FLAG_EXCLUSIVE        0x01 //一個常數,在prepare_to_wait()用於修改flags的值

 

          void * private           //通常指向當前任務控制塊

          wait_queue_func_t func;     //喚醒阻塞任務的函數 ,決定了喚醒的方式

   struct list_head task_list;    // 阻塞任務鏈表

};

typedef struct __wait_queue          wait_queue_t;

1) 定義一個等待隊列

 wait_queue_t wait;

 

2) 初始化等待隊列

內核中定義的介面如下:

static inline void init_waitqueue_entry(wait_queue_t *q, struct task_struct *p)

{

  q->flags = 0;

  q->private = p;          //私有數據指針,一般指向當前任務控制塊

  q->func = default_wake_function;  //使用預設的喚醒函數

}

 

使用範例:

init_waitqueue_entry(&wait, current); 

 

3) 添加/ 等待隊列。

void fastcall add _ wait _ queue(wait _ queue _ head _ t *q, wait _ queue _ t *wait);

add_wait_queue()用於將等待隊列 wait 添加到等待隊列頭 q 指向的等待隊列鏈表

 

4)移除等待隊列。

void fastcall remove _ wait _ queue(wait _ queue _ head _ t *q, wait _ queue _ t *wait);

remove_wait_queue()用於將等待隊列 wait 從附屬的等待隊列頭 q 指向的等待隊列鏈表中移除。

 

5)判斷等待隊列是否為空。

static inline int waitqueue_active(wait_queue_head_t *q)

{

  return ! list_empty(&q->task_list);

}

  判斷等待對列頭是否為空,當一個進程訪問設備而得不到資源時就會被放入等待隊列頭指向的等待隊列中。

三、函數 sleep_on的實現

Sleep()函數相信大家早已耳熟能詳了,可是內部究竟是怎麼實現的呢?讓我們一起來揭開它的面紗

void sleep_on(wait_queue_head_t *wq)

{

wait_queue_t wait; //定義等待隊列

init_waitqueue_entry(&wait, current); //初始化等待隊列

current->state = TASK_UNINTERRUPTIBALE; //設置進程狀態

add_wait_queue(wq,&wait); //加入等待隊列

schedule(); //調度,當前進程進入睡眠

remove_wait_queue(wq,&wait); //醒後從等待隊列中移除

}

可以發現,程式之所以能睡眠,是因為他改變了自己的狀態,並執行調度,放棄了占用CPU。但是我們要喚醒進程,必須要找到它,怎麼找到它呢,關鍵就在於進程在睡眠前我們把它加入了等待對應,只要找到等待隊列我們就能找到掛起的進程並喚醒它。


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

-Advertisement-
Play Games
更多相關文章
  • 效果:兩個DataGrid的滾動條實現同步滾動。 代碼參考了博客園chuncn的文章《.net中同步多個ScrollViewer滾動的四種方法》,原文是針對ListBox的。現改為針對DataGrid,略微作了修改,代碼以水平滾動條為例。 原文地址:https://www.cnblogs.com/c ...
  • EF是 EntityFramework 的簡稱。在程式和資料庫交互的過程中,起到了極大的方便。 首先在需要使用EF的項目單擊,然後 Ctrl+Shift+a 找到ADO.NET實體數據模型。 首先在需要使用EF的項目單擊,然後 Ctrl+Shift+a 找到ADO.NET實體數據模型。 單擊添加後, ...
  • 在使用notepad++工具的時候,很多情況下我們會遇到批量替換空行的操作,之前的操作方法是快捷鍵Crtl+h調出視窗選擇替換欄,在查找目標欄中輸入\r\n\r\n,替換為 欄中輸入\r\n並選擇全部替換,可實現批量刪除空行的操作。隨著Visual Studio Code的普及,之前notepad+... ...
  • 請給出如下格式的date命令 例:19-01-18.再給出實現按周輸出 比如:周六輸出為6,請分別給出命令。 解答: 方法1: [root@zhaokang ~]# date2019年 01月 17日 星期四 07:41:14 CST[root@zhaokang ~]# LANG=en[root@z ...
  • 博客原文地址: "CentOS 7 個性化配置指南 Wind Spirit" 0x00 前言 該教程主要安裝瞭如下軟體包 iptables MySQL PHP PHP 相關模塊 Nginx 主要配置實現了以下功能 修改軟體源 修改 PS1,更改配色 SSH 每隔 30 秒發送一個心跳包 修改 ipt ...
  • pwd:顯示當前路徑 ls:顯示當前文件夾下內容 cd:前往某個路徑 mkdir:新建文件夾 ...
  • 1.通過電腦進行共用印表機(win7\8\10系統使用類似) (1)安裝匹配驅動,開啟Windows Firewall服務(services.msc) (2)調試 (3)client_1添加共用印表機 同時按下 windows+r ,鍵入共用主機地址(ip),如果提示輸入用戶名/密碼,正常輸入即可, ...
  • 家目錄下,通用文件夾名稱中英文互轉: (1)中文 英文 export LANG=en_US.UTF 8 xdg user dirs update force (2)英文 中文 export LANG=zh_CN.UTF 8 xdg user dirs update force 台式機前置耳機面板無聲 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...