Linux驅動技術(五) _設備阻塞/非阻塞讀寫

来源:http://www.cnblogs.com/xiaojiang1025/archive/2017/02/13/6377925.html
-Advertisement-
Play Games

等待隊列 是內核中實現進程調度的一個十分重要的數據結構,其任務是維護一個鏈表,鏈表中每一個節點都是一個PCB(進程式控制制塊), 內核會將PCB掛在等待隊列中的所有進程都調度為睡眠狀態,直到某個喚醒的條件發生 。應用層的阻塞IO與非阻塞IO的使用我已經在 "Linux I/O多路復用" 一文中討論過了, ...


等待隊列是內核中實現進程調度的一個十分重要的數據結構,其任務是維護一個鏈表,鏈表中每一個節點都是一個PCB(進程式控制制塊),內核會將PCB掛在等待隊列中的所有進程都調度為睡眠狀態,直到某個喚醒的條件發生。應用層的阻塞IO與非阻塞IO的使用我已經在Linux I/O多路復用一文中討論過了,本文主要討論驅動中怎麼實現對設備IO的阻塞與非阻塞讀寫。顯然,實現這種與阻塞相關的機制要用到等待隊列機制。本文的內核源碼使用的是3.14.0版本

設備阻塞IO的實現

當我們讀寫設備文件的IO時,最終會回調驅動中相應的介面,而這些介面也會出現在讀寫設備進程的進程(內核)空間中,如果條件不滿足,介面函數使進程進入睡眠狀態,即使讀寫設備的用戶進程進入了睡眠,也就是我們常說的發生了阻塞。In a word,讀寫設備文件阻塞的本質是驅動在驅動中實現對設備文件的阻塞,其讀寫的流程可概括如下:

1. 定義-初始化等待隊列頭

//定義等待隊列頭
wait_queue_head_t waitq_h;
//初始化,等待隊列頭
init_waitqueue_head(wait_queue_head_t *q);
 //或
//定義並初始化等待隊列頭
DECLARE_WAIT_QUEUE_HEAD(waitq_name);

上面的幾條選擇中,最後一種會直接定義並初始化一個等待頭,但是如果在模塊內使用全局變數傳參,用著並不方便,具體用哪種看需求。
我們可以追一下源碼,看一下上面這幾行都幹了什麼:

//include/linux/wait.h 
 35 struct __wait_queue_head { 
 36         spinlock_t              lock;
 37         struct list_head        task_list;
 38 };
 39 typedef struct __wait_queue_head wait_queue_head_t;

wait_queue_head_t
--36-->這個隊列用的自旋鎖
--27-->將整個隊列"串"在一起的紐帶

然後我們看一下初始化的巨集:

 55 #define __WAIT_QUEUE_HEAD_INITIALIZER(name) {                           \
 56         .lock           = __SPIN_LOCK_UNLOCKED(name.lock),              \
 57         .task_list      = { &(name).task_list, &(name).task_list } }
 58 
 59 #define DECLARE_WAIT_QUEUE_HEAD(name) \
 60         wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)

DECLARE_WAIT_QUEUE_HEAD()
--60-->根據傳入的字元串name,創建一個名為name的等待隊列頭
--57-->初始化上述task_list域,竟然沒有用內核標準的初始化巨集,無語。。。

2. 將本進程添加到等待隊列

為等待隊列添加事件,即進程進入睡眠狀態直到condition為真才返回。**_interruptible的版本版本表示睡眠可中斷,_timeout**版本表示超時版本,超時就會返回,這種命名規範在內核API中隨處可見。

void wait_event(wait_queue_head_t *waitq_h,int condition);
void wait_event_interruptible(wait_queue_head_t *waitq_h,int condition);
void wait_event_timeout(wait_queue_head_t *waitq_h,int condition);
void wait_event_interruptible_timeout(wait_queue_head_t *waitq_h,int condition);

這可是等待隊列的核心,我們來看一下

wait_event
   └── wait_event
            └──
_wait_event
            ├── abort_exclusive_wait
            ├── finish_wait
            ├── prepare_to_wait_event
            └── ___wait_is_interruptible

244 #define wait_event(wq, condition)                                       \
245 do {                                                                    \
246         if (condition)                                                  \
247                 break;                                                  \
248         __wait_event(wq, condition);                                    \ 
249 } while (0)

wait_event
--246-->如果condition為真,立即返回
--248-->否則調用__wait_event

194 #define ___wait_event(wq, condition, state, exclusive, ret, cmd)        \       
195 ({                                                                      \
206         for (;;) {                                                      \
207                 long __int = prepare_to_wait_event(&wq, &__wait, state);\
208                                                                         \  
209                 if (condition)                                          \       
210                         break;                                          \
212                 if (___wait_is_interruptible(state) && __int) {         \
213                         __ret = __int;                                  \
214                         if (exclusive) {                                \
215                                 abort_exclusive_wait(&wq, &__wait,      \
216                                                      state, NULL);      \
217                                 goto __out;                             \
218                         }                                               \
219                         break;                                          \
220                 }                                                       \
222                 cmd;                                                    \
223         }                                                               \
224         finish_wait(&wq, &__wait);                                      \
225 __out:  __ret;                                                          \
226 })

___wait_event
--206-->死迴圈的輪詢
--209-->如果條件為真,跳出迴圈,執行finish_wait();進程被喚醒
--212-->如果進程睡眠的方式是interruptible的,那麼當中斷來的時候也會abort_exclusive_wait被喚醒
--222-->如果上面兩條都不滿足,就會回調傳入的schedule(),即繼續睡眠

模板

struct wait_queue_head_t xj_waitq_h;
static ssize_t demo_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
    if(!condition)
        wait_event_interruptible(&xj_waitq_h,condition);
}
static file_operations fops = {
    .read = demo_read,
};
static __init demo_init(void)
{
    init_waitqueue_head(&xj_waitq_h);
}

IO多路復用的實現

對於普通的非阻塞IO,我們只需要在驅動中註冊的read/write介面時不使用阻塞機制即可,這裡我要討論的是IO多路復用,即當驅動中的read/write並沒有實現阻塞機制的時候,我們如何利用內核機制來在驅動中實現對IO多路復用的支持。下麵這個就是我們要用的API

int poll(struct file *filep, poll_table *wait);
void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)  

當應用層調用select/poll/epoll機制的時候,內核其實會遍歷回調相關文件的驅動中的poll介面,通過每一個驅動的poll介面的返回值,來判斷該文件IO是否有相應的事件發生,我們知道,這三種IO多路復用的機制的核心區別在於內核中管理監視文件的方式,分別是數組鏈表,但對於每一個驅動,回調的介面都是poll。

模板

struct wait_queue_head_t waitq_h;
static unsigned int demo_poll(struct file *filp, struct poll_table_struct *pts)
{
    unsigned int mask = 0;
    poll_wait(filp, &wwaitq_h, pts);
    if(counter){
        mask = (POLLIN | POLLRDNORM);
    }
    return mask;
}

static struct file_operations fops = {
    .owner  = THIS_MODULE,
    .poll   = demo_poll,
};
static __init demo_init(void)
{
    init_waitqueue_head(&xj_waitq_h);
}

其他API

剛纔我們討論瞭如何使用等待隊列實現阻塞IO,非阻塞IO,其實關於等待隊列,內核還提供了很多其他API用以完成相關的操作,這裡我們來認識一下

//在等待隊列上睡眠
sleep_on(wait_queue_head_t *wqueue_h);
sleep_on_interruptible(wait_queue_head_t *wqueue_h);

//喚醒等待的進程
void wake_up(wait_queue_t *wqueue);
void wake_up_interruptible(wait_queue_t *wqueue);




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

-Advertisement-
Play Games
更多相關文章
  • 今天進行了InfluxDB和MySQL的對比測試,這裡記錄下結果,也方便我以後查閱。 操作系統: CentOS6.5_x64InfluxDB版本 : v1.1.0MySQL版本:v5.1.73CPU : Intel(R) Core(TM) i5-2320 CPU @ 3.00GHz記憶體 :12G硬碟 ...
  • 本文分三部步講解: 資料庫安裝,資料庫創建,創建表空間與用戶 資料庫安裝 首先到官方網站根據機器要求下載必要安裝包: http://www.oracle.com/technetwork/database/enterprise-edition/downloads/index.html win32_11 ...
  • 為VMware虛擬機內安裝的Ubuntu 16.04設置靜態IP地址NAT方式 為VMware虛擬機內安裝的Ubuntu 16.04設置靜態IP地址NAT方式 1.安裝環境 VMware 12 Ubuntu 16.04 x86_64 2.在VMware中,配置網路環境 VMware在預設安裝完成之後 ...
  • 在硬體上,中斷源可以通過中斷控制器向CPU提交中斷,進而引發中斷處理程式的執行,不過這種硬體中斷體系每一種CPU都不一樣,而Linux作為操作系統,需要同時支持這些中斷體系,如此一來,Linux中就提出了 軟中斷 的概念,也有人叫 內核中斷 ,其本質就是使用統一的方式對不同硬體中斷體系中的中斷號進行 ...
  • 1:統計10.86.0.0/16網段的內網流量情況 將下麵腳本保存成文件traffic-lan.sh(運行後需要等待10秒抓包) 2:統計到外網IP的流量情況 將下麵腳本保存成文件traffic-wan.sh(運行後需要等待10秒抓包) 3:可以統計UDP的改進版本 4:後來發現iftop也可以完成 ...
  • 我的上一篇文章《Linux編程之PING的實現》里使用ICMP協議實現了PING的程式,ICMP除了實現這麼一個PING程式,還有哪些不為人知或者好玩的用途?這裡我將介紹ICMP另一個很有名的黑科技:ICMP洪水攻擊。 ICMP洪水攻擊屬於大名鼎鼎的DOS(Denial of Service)攻擊的 ...
  • 格式:cut [參數] 文本 參數:-d [分隔符] 指定分割符,預設為TAB -f 指定顯示的列數 -c 單位改為字元 示例:查看系統中所有的用戶名 ...
  • 我校的課程真是跟不上時代發展,甚至還在教授8051/8052單片機的內容,於是不甘寂寞的我就自己踏入了STM32單片機的坑…… 首先,我現在大二,剛學完模擬電子技術,還沒有學習數字電路技術,於是自學單片機開發會有一定困難,而我校要到大三才能開放單片機課程,這就很有趣了,我不得不去啃一些完全沒見過的玩 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...