1. 程式人生 > >linux裝置驅動程式中的阻塞、IO多路複用與非同步通知機制

linux裝置驅動程式中的阻塞、IO多路複用與非同步通知機制

一、阻塞與非阻塞

阻塞與非阻塞是裝置訪問的兩種方式。在寫阻塞與非阻塞的驅動程式時,經常用到等待佇列。

阻塞呼叫是指呼叫結果返回之前,當前執行緒會被掛起,函式只有在得到結果之後才會返回。

非阻塞指不能立刻得到結果之前,該函式不會阻塞當前程序,而會立刻返回。

函式是否處於阻塞模式和驅動對應函式中的實現機制是直接相關的,但並不是一一對應的,例如我們在應用層設定為阻塞模式,如果驅動中沒有實現阻塞,函式仍然沒有阻塞功能。

二、等待佇列

linux裝置驅動程式中,阻塞程序可以使用等待佇列來實現。

在核心中,等待佇列是有很多用處的,尤其是在中斷處理,程序同步,定時等場合,可以使用等待佇列實現阻塞程序的喚醒。它以佇列為基礎資料結構,與程序排程機制緊密結合,能夠用於實現核心中的非同步事件通知機制,同步對系統資源的訪問。

等待佇列的使用

1)定義和初始化等待佇列:

wait_queue_head_t wait;//定義等待佇列
init_waitqueue_head(&wait);//初始化等待佇列

定義並初始化等待佇列:

#define DECLARE_WAIT_QUEUE_HEAD(name) wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)

2)新增或移除等待佇列:

void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);//將等待佇列元素wait新增到等待佇列頭q所指向的等待佇列連結串列中。
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

3)等待事件:

wait_event(wq, condition);//在等待佇列中睡眠直到condition為真。
wait_event_timeout(wq, condition, timeout);
wait_event_interruptible(wq, condition) ;
wait_event_interruptible_timeout(wq, condition, timeout) ;
/*
*queue:作為等待佇列頭的等待佇列被喚醒
*    conditon:必須滿足,否則阻塞
*    timeout和conditon相比,有更高優先順序
*/

4)睡眠:

sleep_on(wait_queue_head_t *q);
interruptible_sleep_on(wait_queue_head_t *q);
/*
sleep_on作用是把目前程序的狀態置成TASK_UNINTERRUPTIBLE,直到資源可用,q引導的等待佇列被喚醒。
interruptible_sleep_on作用是一樣的, 只不過它把程序狀態置為TASK_INTERRUPTIBLE
*/

5)喚醒等待佇列:

//可喚醒處於TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE狀態的程序;
#define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)

//只能喚醒處於TASK_INTERRUPTIBLE狀態的程序
#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)

三、作業系統中睡眠、阻塞、掛起的區別形象解釋

掛起執行緒的意思是線上程中主動使用排程函式,將自己掛起,何時在執行需要等待系統排程實現,無法預知,可能在執行完排程函式後,核心馬上又排程回來,繼續執行。

使執行緒睡眠的意思是線上程中使用能夠睡眠的語句,然後讓執行緒進入睡眠狀態,讓出cpu,直到滿足要求的事情發生了再喚醒執行緒繼續執行,比如睡眠定時時間到達,或者睡眠被打斷,或者睡眠等待的資源得到時。

執行緒阻塞有點是不一定會發生的意思,比如在執行函式的時候執行到摸一個位置需要一個資源,如果這個資源可用就繼續執行,如果這個資源不可用程式會進行睡眠並等待資源可用時在喚醒。這與執行緒主動睡眠的區別就是是否阻塞睡眠需要根據一定的條件進行。

四、阻塞與非阻塞操作

阻塞操作是指在執行裝置操作時若不能獲得資源則掛起程序,直到滿足可操作的條件後在進行操作。

非阻塞操作的程序在不能進行裝置操作時並不掛起,它或者被放棄,或者不停的查詢,直到可以進行操作為止。

在簡單字元裝置驅動, 我們看到如何實現readwrite方法。但是我們僅僅實現了阻塞的方式,也就是我們的應用程式不論如何設定,我們的驅動只支援阻塞方式。

在驅動中如何知道應用程式的設定呢?驅動中使用的是read或者write函式的引數struct file中的f_flags標誌判斷應用程式是否設定了非阻塞。

判斷程式碼片段如下:

if (file->f_flags & O_NONBLOCK)       /* 非 阻塞操作 */  
{  
    if (!ev_press)                 /* ev_press 為 1 表示有按鍵按下,為 0 if 成立 ,沒有按鍵按下, */  
        return -EAGAIN;        /* 返回 -EAGAIN 讓再次來執行 */  
}  
else                                   /* 阻塞操作 */  
{  
   /* 如果沒有按鍵動作, 休眠 */  
    wait_event_interruptible(button_waitq, ev_press);  
} 

五、IO多路複用poll

函式原型如下

static unsigned int poll(struct file *file, struct socket *sock, poll_table *wait)

第一個引數是file結構體指標,第三個引數是輪詢表指標

這個函式應該進行兩項工作

1)對可能引起裝置檔案狀態變化的等待佇列呼叫poll_wait()函式,將對應的等待佇列頭新增到poll_table

2)返回表示是否能對裝置進行無阻塞讀,寫訪問的掩碼

poll_wait()函式原型如下

static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)

從中可以看出是將等待佇列頭wait_address新增到p所指向的結構體中(poll_table)

驅動函式中的poll()函式典型模板如下

static unsigned int xxx_poll(struct file *filp,struct socket *sock, poll_table *wait)
{
    unsigned int mask = 0;
    struct xxx_dev *dev = filp->private_data;//獲得裝置結構體指標
    ...
    poll_wait(filp,&dev->r_wait,wait);//加讀等待佇列頭到poll_table
    poll_wait(filp,&dev->w_wait,wait);//加寫等待佇列頭到poll_table
    ...
    if(...)//可讀
        mask |= POLLIN | POLLRDNORM;
    if(...)//可寫
        mask |= POLLOUT | POLLRDNORM;
    ...
    return mask;
}

六、應用程式實現IO複用

驅動程式中的poll函式,在應用程式中對應著selectpollepoll函式。

Select使用方法

1)將要監控的檔案新增到檔案描述符集

2)呼叫select開始監控

3)判斷檔案是否發生變化

系統提供了4個巨集對描述符集進行操作:

#include<sys/select.h>
Void FD_SET(int fd, fd_set *fdset)
Void FD_CLR(int fd, fd_set *fdset)
Void FD_ZERO(fd_set *fdset)
Void FD_ISSET(int fd, fd_set *fdset)

巨集FD_SET將檔案描述符fd新增到檔案描述符fdset

巨集FD_CLR從檔案描述符集fdset中清除檔案描述符fd

巨集FD_ZERO清空檔案描述符集fdset

在呼叫select後使用FD_ISSET來檢測檔案描述符集fdset中的檔案fd發生了變化

使用例子(對兩個檔案進行讀監控)

FD_ZERO(&fds);//清空集合
FD_SET(fd1,&fds);//設定描述符
FD_SET(fd2,&fds);//設定描述符
 
Maxfdp = fd1+1;//描述符最大值加1,假設fd1>fd2
 
Switch(select(maxfdp,&fds,NULL,NULL,&timeout))//讀監控
 
Case -1: exit(-1);break;//select錯誤,退出程式
Case 0:break;
Default:
If(FD_ISSET(fd1,&fds)) //測試fd1是否可讀

select的幾大缺點:

1)每次呼叫select,都需要把fd集合從使用者態拷貝到核心態,這個開銷在fd很多時會很大

2)同時每次呼叫select都需要在核心遍歷傳遞進來的所有fd,這個開銷在fd很多時也很大

3select支援的檔案描述符數量太小了,預設是1024

poll使用方法

poll的實現和select非常相似,只是描述fd集合的方式不同,poll使用pollfd結構而不是selectfd_set結構,其他的都差不多。

epoll使用方法

epoll既然是對selectpoll的改進,就應該能避免上述的三個缺點。那epoll都是怎麼解決的呢?在此之前,我們先看一下epollselectpoll的呼叫介面上的不同,selectpoll都只提供了一個函式——select或者poll函式。而epoll提供了三個函式,epoll_create,epoll_ctlepoll_waitepoll_create是建立一個epoll控制代碼;epoll_ctl是註冊要監聽的事件型別;epoll_wait則是等待事件的產生。

  對於第一個缺點,epoll的解決方案在epoll_ctl函式中。每次註冊新的事件到epoll控制代碼中時(在epoll_ctl中指定EPOLL_CTL_ADD),會把所有的fd拷貝進核心,而不是在epoll_wait的時候重複拷貝。epoll保證了每個fd在整個過程中只會拷貝一次。

  對於第二個缺點,epoll的解決方案不像selectpoll一樣每次都把current輪流加入fd對應的裝置等待佇列中,而只在epoll_ctl時把current掛一遍(這一遍必不可少)併為每個fd指定一個回撥函式,當裝置就緒,喚醒等待佇列上的等待者時,就會呼叫這個回撥函式,而這個回撥函式會把就緒的fd加入一個就緒連結串列)。epoll_wait的工作實際上就是在這個就緒連結串列中檢視有沒有就緒的fd(利用schedule_timeout()實現睡一會,判斷一會的效果,和select實現中的第7步是類似的)。

對於第三個缺點,epoll沒有這個限制,它所支援的FD上限是最大可以開啟檔案的數目,這個數字一般遠大於2048,舉個例子,1GB記憶體的機器上大約是10萬左右,具體數目可以cat /proc/sys/fs/file-max察看,一般來說這個數目和系統記憶體關係很大。

總結:

1selectpoll實現需要自己不斷輪詢所有fd集合,直到裝置就緒,期間可能要睡眠和喚醒多次交替。而epoll其實也需要呼叫epoll_wait不斷輪詢就緒連結串列,期間也可能多次睡眠和喚醒交替,但是它是裝置就緒時,呼叫回撥函式,把就緒fd放入就緒連結串列中,並喚醒在epoll_wait中進入睡眠的程序。雖然都要睡眠和交替,但是selectpoll在“醒著”的時候要遍歷整個fd集合,而epoll在“醒著”的時候只要判斷一下就緒連結串列是否為空就行了,這節省了大量的CPU時間。這就是回撥機制帶來的效能提升。

(2)selectpoll每次呼叫都要把fd集合從使用者態往核心態拷貝一次,並且要把current往裝置等待佇列中掛一次,而epoll只要一次拷貝,而且把current往等待佇列上掛也只掛一次(在epoll_wait的開始,注意這裡的等待佇列並不是裝置等待佇列,只是一個epoll內部定義的等待佇列)。這也能節省不少的開銷。

參考資料:

七、驅動中的非同步通知機制

非同步通知是,一旦裝置就緒,則主動向應用程式傳送訊號,應用程式根本就不需要查詢裝置狀態,類似於中斷的概念,一個程序收到一個訊號與處理器收到一箇中斷請求可以說是一樣的。訊號是非同步的,一個程序不必通過任何操作來等待訊號的到達。

linux中,非同步通知是使用訊號來實現的,而在linux,大概有30種訊號,比如大家熟悉的ctrl+cSIGINT訊號,程序能夠忽略或者捕獲除過SIGSTOPSIGKILL的全部訊號,當訊號背捕獲以後,有相應的函式來處理它。

在驅動程式可以將特定訊號傳送到特定應用程序中。因此,應該在合適的時候讓裝置驅動傳送訊號,驅動中應實現fasync()函式。並在裝置資源可獲得時,呼叫kill_fasync()函式激發相應的訊號。

驅動中實現非同步通知機制很簡單。首先說fasync()函式的實現。

static int xxx_fasync(int fd,struct file *filp,int mode)  
{  
    struct xxx_dev *dev = filp->private_data;  
    return fasync_helper(fd,filp,mode,&dev->async_queue);  
}  

這個就是標準模板。

然後在需要傳送訊號的地方呼叫 kill_fasync()函式,釋放訊號。

釋放訊號的函式:

void kill_fasync(struct fasync_struct **fp, int sig, int band)  

下面我們來看下支援非同步通知的模板。

裝置結構體:

struct xxx_dev{  
        struct cdev cdev;  
    ......  
    struct fasync_struct *async_queue;  
};  
fasync()函式:
static int xxx_fasync(int fd,struct file *filp,int mode)  
{  
    struct xxx_dev *dev = filp->private_data;  
    return fasync_helper(fd,filp,mode,&dev->async_queue);  
}  

在裝置資源可以獲得時,應該呼叫kill_fasync()釋放SIGIO訊號,可讀時第三個引數是POLL_IN,可寫時為POLL_OUT.

static ssize_t xxx_write(struct file *filp,const char __user *buf,size_t count,loff_t *ppos)  
{  
    struct xxx_dev *dev = filp->private_data;  
    ......  
    if(dev->async_queue)  
        kill_fasync(&dev->async_queue,SIGIO,POLL_IN);  
        ......  
}  

release函式中,應呼叫fasync()函式將檔案從非同步通知的列表中刪除。

int xxx_release(struct inode *inode,struct file *filp)  
{  
    xxx_fasync(-1,filp,0);  
    return 0;  
}  

八、應用程式中給驅動註冊訊號的機制

在應用程式中需要3步將訊號與驅動繫結。

         1、註冊 SIGIO訊號

            signal(SIGIO, handler);

         2、設定程序為檔案的屬主

            fcntl(fd, F_SETOWN, getpid());

         3、設定非同步屬性

            int flags;

            flags = fcntl(fd, F_GETFL);

            flags |= FASYNC;

            fcntl(fd, F_SETFL, flags);

然後當驅動傳送訊號的時候就會自動呼叫應用程式的訊號處理函式。

應用程式模板:

void input_handler(int num)  
{  
   ……
}  
main()  
{  
    int oflags;  
      
    signal(SIGIO,input_handler);  
    fcntl(STDIN_FILENO,F_SETOWN,getpid());  
    oflags=fcntl(STDIN_FILENO,F_GETFL);  
    fcntl(STDIN_FILENO,F_SETFL,oflags|FASYNC);  
  
    while(1);  
}  

九、總結

阻塞與非阻塞操作

1)定義並初始化等待對列頭;

2)定義並初始化等待佇列;

3)把等待佇列新增到等待佇列頭

4)設定程序狀態(TASK_INTERRUPTIBLE(可以被訊號打斷)和TASK_UNINTERRUPTIBLE(不能被訊號打斷))

5)呼叫其它程序

poll機制

1)把等待佇列頭加到poll_table

(2)返回表示是否能對裝置進行無阻塞讀,寫訪問的掩碼

非同步通知機制

(1)當發出 F_SETOWN,什麼都沒發生,除了一個值被賦值給filp->f_owner.

(2)F_SETFL被執行來開啟FASYNC,驅動的fasync方法被呼叫.這個方法被呼叫無論何時FASYNC的值在filp->f_flags中被改變來通知驅動這個變化,因此它可正確地響應.這個標誌在檔案被開啟時預設地被清除.我們將看這個驅動方法的標準實現,在本節.

(3)當資料到達,所有的註冊非同步通知的程序必須被髮出一個SIGIO訊號.


相關推薦

linux裝置驅動程式阻塞IO非同步通知機制

一、阻塞與非阻塞 阻塞與非阻塞是裝置訪問的兩種方式。在寫阻塞與非阻塞的驅動程式時,經常用到等待佇列。 阻塞呼叫是指呼叫結果返回之前,當前執行緒會被掛起,函式只有在得到結果之後才會返回。 非阻塞指不能立刻得到結果之前,該函式不會阻塞當前程序,而會立刻返回。 函式是否處於阻

Ioctl命令組成阻塞IO實現IO >>Linux裝置驅動程式

我又來了,一天學習一點點,一天成長一點點就會很開心的說! 今天的狀態很好,況且還有好喝的咖啡陪伴著我,元氣滿滿喲!^. ^? 文章目錄 [0x100]內容概述 [0x200] 編寫ioctl的命令編號 [0x210]命令編號組成

Linux IO模式(BIONIOIO非同步IO)及 selectpollepoll詳解

同步IO和非同步IO,阻塞IO和非阻塞IO分別是什麼,到底有什麼區別?不同的人在不同的上下文下給出的答案是不同的。所以先限定一下本文的上下文。 本文討論的背景是Linux環境下的network IO。 一 概念說明 在進行解釋之前,首先要說明幾個概念: -

day035協程IO

  本節內容: 1、協程(重點:gevent) 2、IO多路複用 一、協程 1、引子 本節的主題是基於單執行緒來實現併發,即只用一個主執行緒(很明顯可利用的cpu只有一個)情況下實現併發, 為此我們需要先回顧下併發的本質:切換+儲存狀態   cpu正在執行一個任務,會在兩種

IO模型IOselect poll epoll

基礎知識     同步、非同步     程序的阻塞     非同步 IO 基礎知識 高效能架構設計主要有兩方面: 1.提高單伺服器的效能。 2.伺服器採用叢集。 提升單伺服器的效能的關鍵之一就是服務端採用併發模型

Event Loop函數式編程IO事件驅動響應式

gen 輪詢 .html mar martin 網絡編程 tin reac 都是 IO多路復用、事件驅動、響應式概念類似或者一樣 就是很多網絡連接(多路),共(復)用少數幾個(甚至是一個)線程。 連接很多的時候,不能每個連接一個線程,會耗盡系統內存的。線程也不能阻塞在任何

IO(二) -- selectpollepoll實現TCP反射程式

接著上文IO多路複用(一)-- Select、Poll、Epoll,接下來將演示一個TCP回射程式,原始碼來自於該博文https://www.cnblogs.com/Anker...,在這裡將其進行了整合,突出select、poll和epoll不同方法之間的比較,但

python 學習第二十五天(事件驅動io

事件驅動模型 原文連結:http://www.cnblogs.com/yuanchenqi/articles/5722574.html 上節的問題: 協程:遇到IO操作就切換。 但什麼時候切回去呢?怎麼確定IO操作完了?

Linux下套接字詳解---epoll模式下的IO伺服器

1 epoll模型簡介 epoll可是當前在Linux下開發大規模併發網路程式的熱門人選,epoll 在Linux2.6核心中正式引入,和select相似,其實都I/O多路複用技術而已,並沒有什麼神祕的。 其實在Linux下設計併發網路程式,向來不缺少方法,比如典型的Apache模型(Proce

Linux IO之select

Linux IO多路複用之select 首先,我我們來介紹一下什麼是IO多路複用: IO多路複用是指核心一旦發現程序指定的一個或者多個IO條件準備讀取,它就通知該程序。 IO多路複用適用如下場合: 當客戶處理多個描述符時(一般是互動式輸入和網路套介面),

Linux IO之poll

Linux IO多路複用之poll 前面介紹了select,今天來介紹poll,poll 是一種高階的輪詢的方法,通常用於伺服器端處理多個客戶端的請求的時候。 其作用於 select 很相似,但是較比 select 方法而言,效率更高,並且處理的連線個數不受核心的限制。 若是使用 s

事件驅動----IO

1、事件驅動 一種程式設計正規化,程式的執行流由外部事件來決定。它的一個特點是包含一個事件迴圈,當外部事件發生時使用回撥機制來觸發相應的處理。 2、IO多路複用 (1)使用者空間與核心空間: 供作業系統使用的是核心空間,供使用者使用的是使用者空間。 假設一個作業系統4G記憶體,使

IO(一)-- SelectPollEpoll

在上一篇博文中提到了五種IO模型,關於這五種IO模型可以參考博文IO模型淺析-阻塞、非阻塞、IO複用、訊號驅動、非同步IO、同步IO,本篇主要介紹IO多路複用的使用和程式設計。 IO多路複用的概念 多路複用是一種機制,可以用來監聽多種描述符,如果其中任意一個描述符處

linux平臺IO select介面使用例子

這幾天在學習net-snmp原始碼,裡面封裝了很多select函式呼叫,這裡記錄一下linux上select的用法以及相關介面。 先看介面: //標頭檔案 #include <sys/select.h> #include <sys/time.h>

網路通訊 :IO之selectpollepoll詳解

 目前支援I/O多路複用的系統呼叫有 select,pselect,poll,epoll,I/O多路複用就是通過一種機制,一個程序可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程式進行相應的讀寫操作。但select,pselect,poll,epoll

http協議,阻塞IO,非阻塞IO,IO,位運算,select方法

HTTP請求   Request 請求格式: 請求行       GET          /         HTTP/1.1  請求種類  

阻塞socketio(沒整理好)

非阻塞套接字 多人聊天客戶端 import socket server = socket.socket() server.setblocking(False) #設定非阻塞 server.bind("",6969) server.listen(5) clients = [ ] 存放連線進來的

python實戰之IO(別名:事件驅動,三種模式:(sellect,poll,epoll),Python的selectors模組)

IO多路複用前需瞭解 通常,我們寫伺服器處理模型的程式時,有以下幾種模型: (1)每收到一個請求,建立一個新的程序,來處理該請求; (2)每收到一個請求,建立一個新的執行緒,來處理該請求; (3)每收到一個請求,放入一個事件列表,讓主程序通過非阻塞I/O方式來處理請求 上面的幾種

Python非同步阻塞IOSelect/Poll/Epoll使用

有許多封裝好的非同步非阻塞IO多路複用框架,底層在linux基於最新的epoll實現,為了更好的使用,瞭解其底層原理還是有必要的。 下面記錄下分別基於Select/Poll/Epoll的echo server實現。 Python Select Server,可監控事件數

Linux下套接字詳解(十)---epoll模式下的IO伺服器

1 epoll模型簡介 epoll可是當前在Linux下開發大規模併發網路程式的熱門人選,epoll 在Linux2.6核心中正式引入,和select相似,其實都I/O多路複用技術而已,並沒有什麼神祕的。 其實在Linux下設計併發網路程式,向來不缺少