@
目錄- 阻塞/非阻塞簡介
- 阻塞/非阻塞例程
- 等待佇列簡介
- 等待佇列相關函式
- 定義等待佇列
- 初始化等待佇列頭
- 定義并初始化一個等待佇列項
- 將佇列項添加到等待佇列頭
- 將佇列項從等待佇列頭移除
- 等待喚醒
- 等待事件
- 輪詢
- select
- poll
- epoll
- 異步通知概念
- Linux信號
- 異步通知代碼
- 驅動中的信號處理
- fasync_struct結構體
- fasync函式
- kill_fasync函式
- 應用程式對異步通知的處理
- 驅動中的信號處理
阻塞/非阻塞簡介
??阻塞操作是指在執行設備操作時,若不能獲得資源,則掛起行程直到滿足可操作的條件后再進行操作,被掛起的行程進入睡眠狀態,被從調度器的運行佇列移走,直到等待的條件被滿足,而非阻塞操作的行程在不能進行設備操作時,并不掛起,它要么放棄,要么不停地查詢,直至可以進行操作為止,
阻塞/非阻塞例程
??阻塞方式
int fd;
int data = https://www.cnblogs.com/dongxb/p/0;
fd = open("/dev/xxx_dev", O_RDWR); /* 阻塞方式打開 */
ret = read(fd, &data, sizeof(data)); /* 讀取資料 */
??非阻塞方式
int fd;
int data = https://www.cnblogs.com/dongxb/p/0;
fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK); /* 非阻塞方式打開 */
ret = read(fd, &data, sizeof(data)); /* 讀取資料 */
等待佇列簡介
??等待佇列是內核中一個重要的資料結構,阻塞方式訪問設備時,如果設備不可操作,那么行程就會進入休眠狀態,等待佇列就是來完成行程休眠操作的一種資料結構,
等待佇列相關函式
定義等待佇列
wait_queue_head_t my_queue;
??wait_queue_head_t是__wait_queue_head結構體的一個typedef,
初始化等待佇列頭
void init_waitqueue_head(wait_queue_head_t *q)
??引數q就是要初始化的等待佇列頭,也可以使用宏 DECLARE_WAIT_QUEUE_HEAD (name)來一次性完成等待佇列頭的定義的初始化,
定義并初始化一個等待佇列項
DECLARE_WAITQUEUE(name, tsk)
??name就是等待佇列項的名字,tsk表示這個等待佇列項屬于哪個任務行程,一般設定為current,在 Linux內核中 current相當于一個全域變數,表示當前行程,因此宏DECLARE_WAITQUEUE就是給當前正在運行的行程創建并初始化了一個等待佇列項,
將佇列項添加到等待佇列頭
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
??q:等待佇列項要加入的等待佇列頭
??wait:要加入的等待佇列項
??回傳值:無
將佇列項從等待佇列頭移除
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
??q:要洗掉的等待佇列項所處的等待佇列頭
??wait:要洗掉的等待佇列項,
??回傳值:無
等待喚醒
void wake_up(wait_queue_head_t *q)
void wake_up_interruptible(wait_queue_head_t *q)
??q:就是要喚醒的等待佇列頭,這兩個函式會將這個等待佇列頭中的所有行程都喚醒
??wake_up函式可以喚醒處于 TASK_INTERRUPTIBLE和 TASK_UNINTERRUPTIBLE狀態的行程,而wake_ up_ interruptible函式只能喚醒處于 TASK_INTERRUPTIBLE狀態的行程
等待事件
wait_event(wq, condition)
??等待以wq為等待佇列頭的等待佇列被喚醒,前提是 condition條件必須滿足(為真),否則一直阻塞,此函式會將行程設定為TASK _UNINTERRUPTIBLE狀態
wait_event_timeout(wq, condition, timeout)
??功能和 wait_event類似,但是此函式可以添加超時時間,以 jiffies為單位,此函式有回傳值,如果回傳0的話表示超時時間到,而且 condition為假,為1的話表示 condition為真,也就是條件滿足了,
wait_event_interruptible(wq, condition)
??與 wait event函式類似,但是此函式將行程設定為 TASK_INTERRUPTIBLE,就是可以被信號打斷,
wait_event_interruptible_timeout(wq, condition, timeout)
??與 wait event timeout函式類似,此函式也將行程設定為 TASK_INTERRUPTIBLE,可以被信號打斷,
輪詢
??當應用程式以非阻塞的方式訪問設備時,會一遍一遍的去查詢我們的設備是否可以訪問,這個查詢操作就叫做輪詢,內核中提供了poll,epoll,select函式來處理輪詢操作,當應用程式在上層通過poll,epoll,select函式來查詢設備時,驅動程式中的poll,epoll,select函式就要在底層實作查詢,如果可以操作的話,就會從讀取設備的資料或者向設備寫入資料,
select
??函式原型
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
??nfds:要操作的檔案描述符個數,
??readifds、 writefds和 exceptfds:這三個指標指向描述符集合,這三個引數指明了關心哪些描述符、需要滿足哪些條件等等,這三個引數都是fd_set型別的, fd_set型別變數的每一個位都代表了一個檔案描述符, readfds用于監視指定描述符集的讀變化,也就是監視這些檔案是否可以讀取,只要這些集合里面有一個檔案可以讀取,那么 seclect就會回傳一個大于0的值表示檔案可以讀取,如果沒有檔案可以讀取,那么就會根據 timeout引數來判斷是否超時,可以將 reads設定為NULL,表示不關心任何檔案的讀變化, writefds和 reads類似,只是 writers用于監視這些檔案是否可以進行寫操作, exceptfds用于監視這些檔案的例外
??timeout:超時時間,當我們呼叫 select函式等待某些檔案描述符可以設定超時時間,超時時間使用結構體 timeval表示,結構體定義如下所示:
struct timeval {
long tv_sec; /* 秒 */
long tv_usec; /* 微妙 */
};
??當 timeout為NULL的時候就表示無限期的等待回傳值,0,表示的話就表示超時發生,但是沒有任何檔案描述符可以進行操作;-1,發生錯誤;其他值,可以進行操作的檔案描述符個數,
??操作fd_set變數的函式
void FD_ZERO(fd_set *set)
void FD_SET(int fd, fd_set *set)
void FD_CLR(int fd, fd_set *set)
int FD_ISSET(int fd, fd_set *set)
??FD_ZERO用于將 fd set變數的所有位都清零, FD_SET用于將 fd_set變數的某個位置1,也就是向 fd_set添加一個檔案描述符,引數fd就是要加入的檔案描述符, FD_CLR用戶將 fd_set變數的某個位清零,也就是將一個檔案描述符從 fd_set中洗掉,引數fd就是要洗掉的檔案描述符, FD_ISSET用于測驗 fd_set的某個位是否置1,也就是判斷某個檔案是否可以進行操作,引數fd就是要判斷的檔案描述符,
void main(void)
{ int ret, fd; /* 要監視的檔案描述符 */
fd_set readfds; /* 讀操作檔案描述符集 */
struct timeval timeout; /* 超時結構體 */
fd = open("dev_xxx", O_RDWR | O_NONBLOCK); /* 非阻塞式訪問 */
FD_ZERO(&readfds); /* 清除readfds */
FD_SET(fd, &readfds); /* 將fd添加到readfds里面 */
/* 構造超時時間 */
timeout.tv_sec = 0;
timeout.tv_usec = 500000; /* 500ms */
ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
switch (ret) {
case 0: /* 超時 */
printf("timeout!\r\n");
break;
case -1: /* 錯誤 */
printf("error!\r\n");
break;
default: /* 可以讀取資料 */
if(FD_ISSET(fd, &readfds)) /* 判斷是否為fd檔案描述符 */
{
/* 使用read函式讀取資料 */
}
break;
}
}
poll
??在單個執行緒中, select函式能夠監視的檔案描述符數量有最大的限制,一般為1024,可以修改內核將監視的檔案描述符數量改大,但是這樣會降低效率!這個時候就可以使用poll函式, poll函式本質上和 select沒有太大的差別,但是poll函式沒有最大檔案描述符限制,Linx應用程式中poll函式原型如下所示:
int poll(struct pollfd *fds, nfds_t nfds, int timeout)
??函式引數和回傳值含義如下
??fds:要監視的檔案描述符集合以及要監視的事件,為一個陣列,陣列元素都是結構體 polled型別的, pollfd結構體如下所示
struct pollfd
{
int fd; /* 檔案描述符 檔案描述符 檔案描述符 */
short events; /* 請求的事件 請求的事件 請求的事件 */
short revents; /* 回傳的事件 回傳的事件 回傳的事件 */
};
??fd是要監視的檔案描述符,如果f無效的話那么 events監視事件也就無效,并且 revents回傳0, events是要監視的事件,可監視的事件型別如下所示
POLLIN //有資料可以讀取,
POLLPRI //有緊急的資料需要讀取,
POLLOUT //可以寫資料POLLERR指定的檔案描述符發生錯誤POLLHUP指定的檔案描述符掛起POLLNVAL無效的請求POLLRDNORM等同于 POLLIN
??revents:回傳引數,也就是回傳的事件,有Linux內核設定具體的回傳事件,
??nfds:poll函式要監視的檔案描述符數量
??timeout:超時時間,單位為ms
??回傳值:回傳 revents域中不為0的 polled結構體個數,也就是發生事件或錯誤的檔案描述符數量;0,超時;-1,發生錯誤,并且設定errno為錯誤型別
void main(void)
{
int ret;
int fd; /* 要監視的檔案描述符 */
struct pollfd fds;
fd = open(filename, O_RDWR | O_NONBLOCK); /* 非阻塞式訪問 */
/* 構造結構體 */
fds.fd = fd;
fds.events = POLLIN; /* 監視資料是否可以讀取 */
ret = poll(&fds, 1, 500); /* 輪詢檔案是否可操作,超時500ms */
if (ret)
{ /* 資料有效 */
/* 讀取資料 */
} else if (ret == 0)
{
/* 超時 */
} else if (ret < 0)
{
/* 錯誤 */
}
}
epoll
??傳統的 selcet和poll函式都會隨著所監聽的fd數量的增加,出現效率低下的問題,而且poll函式每次必須遍歷所有的描述符來檢查就緒的描述符,這個程序很浪費時間,為此,epoll因運而生,epoll就是為處理大并發而準備的,一般常常在網路編程中使用epoll函式,應用程式需要先使用 epoll_create函式創建一個 epoll句柄, epoll create函式原至如下.
int epoll_create(int size)
??函式引數和回傳值含義如下:
??size;從 Linux2.6.8開始此引數已經沒有意義了,隨便填寫一個大于0的值就可以
??回傳值:epoll句柄,如果為-1的話表示創建失敗,epoll句柄創建成功以后使用,epoll ctl函式向其中添加要監視的檔案描述符以及監視的事ct函式原型如下所示
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
??函式引數和回傳值含義如下
??epfd;要操作的epoll句柄,也就是使用 epoll_create函式創建的epoll句柄,
??p:表示要對epfd( epoll句柄)進行的操作,可以設定為
EPOLL CTL ADD //向印fd添加檔案引數d表示的描述符EPOLL CTL MOD修改引數fd的 event事件,
EPOLL CTL DEL //從f中洗掉過l描述符
??fd:要監視的檔案描述
??event:要監視的事件型別,為 epoll_event結構體型別指標, epoll_event結構體型別如下所
struct epoll_event
{
uint32_t events; /* epoll事件 */
epoll_data_t data; /* 用戶資料 用戶資料 */
};
??結構體 epoll_event的 events成員變數表示要監視的事件,可選的事件如下所示
EPOLLIN //有資料可以讀取EPOLLOUT可以寫資料
EPOLLPRI //有緊急的資料需要讀取EPOLLERI指定的檔案描述符發生錯誤,
EPOLLHUP //指定的檔案描述符掛起POLLET設定epo為邊沿觸發,默認觸發模式為水平觸發王
POLLONESHOT //一次性的監視,當監視完成以后還需要再次監視某個fd,那么就需要將fd重新添加到 epoll 里面
??上面這些事件可以進行“或”操作,也就是說可以設定監視多個事件回傳值:0,成功;-1,失敗,并且設定errno的值為相應的錯誤碼,一切都設定好以后應用程式就可以通過 epoll_wait函式來等待事件的發生,類似 select函式, epoll_wait函式原型如下所示
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
??函式引數和回傳值含義如下
??epfd:要等待的 epoll
??events:指向 epoll_event結構體的陣列,當有事件發生的時候Iimx內核會填寫 events,呼叫者可以根據 events判斷發生了哪些事件,
??prevents:events陣列大小,必須大于0
??timeout:超時時間,單位為ms回傳值:0,超時;-1,錯誤;其他值,準備就緒的檔案描述符數量,
??epoll更多的是用在大規模的并發服務器上,因為在這種場合下 select和poll并不適合,當設計到的檔案描述符(fd比較少的時候就適合用 selcet和pl本章我們就使用 sellect和poll這兩個函式
異步通知概念
??阻塞與非阻塞訪問、poll函式提供了較好的解決設備訪問的機制,但是如果有了異步通知,整套機制則更加完整了,
??異步通知的意思是:一旦設備就緒,則主動通知應用程式,這樣應用程式根本就不需要查詢設備狀態,這一點非常類似于硬體上“中斷”的概念,比較準確的稱謂是“信號驅動的異步I/O”,信號是在軟體層次上對中斷機制的一種模擬,在原理上,一個行程收到一個信號與處理器收到一個中斷請求可以說是一樣的,信號是異步的,一個行程不必通過任何操作來等待信號的到達,事實上,行程也不知道信號到底什么時候到達,
??阻塞I/O意味著一直等待設備可訪問后再訪問,非阻塞I/O中使用poll()意味著查詢設備是否可訪問,而異步通知則意味著設備通知用戶自身可訪問,之后用戶再進行I/O處理,由此可見,這幾種I/O方式可以相互補充,
Linux信號
??異步通知的核心就是信號,在 arch/xtensa/include/uapi/asm/signal.h檔案中定義了Linux所支持的所有信號
#define SIGHUP 1/* 終端掛起或控制行程終止 */
#define SIGINT 2/* 終端中斷(Ctrl+C組合鍵) */
#define SIGQUIT 3 /* 終端退出(Ctrl+\組合鍵) */
#define SIGILL 4/* 非法指令 */
#define SIGTRAP 5/* debug使用,有斷點指令產生 */
#define SIGABRT 6/* 由abort(3)發出的退出指令 */
#define SIGIOT 6 /* IOT指令 */
#define SIGBUS 7 /* 總線錯誤 */
#define SIGFPE 8 /* 浮點運算錯誤 */
#define SIGKILL 9 /* 殺死、終止行程 */
#define SIGUSR1 10 /* 用戶自定義信號1 */
#define SIGSEGV 11 /* 段違例(無效的記憶體段) */
#define SIGUSR2 12 /* 用戶自定義信號2 */
#define SIGPIPE 13 /* 向非讀管道寫入資料 */
#define SIGALRM 14 /* 鬧鐘 */
#define SIGTERM 15 /* 軟體終止 */
#define SIGSTKFLT 16 /* 堆疊例外 */
#define SIGCHLD 17 /* 子行程結束 */
#define SIGCONT 18 /* 行程繼續 */
#define SIGSTOP 19 /* 停止行程的執行,只是暫停 */
#define SIGTSTP 20 /* 停止行程的運行(Ctrl+Z組合鍵) */
#define SIGTTIN 21 /* 后臺行程需要從終端讀取資料 */
#define SIGTTOU 22 /* 后臺行程需要向終端寫資料 */
#define SIGURG 23 /* 有"緊急"資料 */
#define SIGXCPU 24 /* 超過CPU資源限制 */
#define SIGXFSZ 25 /* 檔案大小超額 */
#define SIGVTALRM 26 /* 虛擬時鐘信號 */
#define SIGPROF 27 /* 時鐘信號描述 */
#define SIGWINCH 28 /* 視窗大小改變 */
#define SIGIO 29 /* 可以進行輸入/輸出操作 */
#define SIGPOLL SIGIO
/* #define SIGLOS 29 */
#define SIGPWR 30 /* 斷點重啟 */
#define SIGSYS 31 /* 非法的系統呼叫 */
#define SIGUNUSED 31 /* 未使用信號 */
異步通知代碼
??我們使用中斷的時候需要設定中斷處理函式,同樣的,如果要在應用程式中使用信號,那么就必須設定信號所使用的信號處理函式,在應用程式中使用 signal函式來設定指定信號的處理函式, signal函式原型如下所示
void (*signal(int signum, void (*handler))(int)))(int);
??該函式原型較難理解,它可以分解為:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler));
??第一個引數指定信號的值,第二個引數指定針對前面信號值的處理函式,若為SIG_IGN,表示忽略該信號;若為SIG_DFL,表示采用系統默認方式處理信號;若為用戶自定義的函式,則信號被捕獲到后,該函式將被執行,
??如果signal呼叫成功,它回傳最后一次為信號signum系結的處理函式的handler值,失敗則回傳SIG_ERR,
驅動中的信號處理
fasync_struct結構體
??首先我們需要在驅動程式中定義個 fasync_struct結構體指標變數, fasync_struct結構體內容如下
struct fasync_struct
{ spinlock_t fa_lock;
int magic;
int fa_fd;
struct fasync_struct *fa_next;
struct file *fa_file;
struct rcu_head fa_rcu;
};
??一般將 fasync_struct結構體指標變數定義到設備結構體中,比如在xxx_dev結構體中添加一個 fasync_struct結構體指標變數,結果如下所示
struct xxx_dev
{
struct device *dev;
struct class *cls;
struct cdev cdev;
......
struct fasync_struct *async_queue; /* 異步相關結構體 */
};
fasync函式
??如果要使用異步通知,需要在設備驅動中實作file_ operations操作集中的 fasync函式,此函式格式如下所示:
int (*fasync) (int fd, struct file *filp, int on)
??fasync函式里面一般通過呼叫 fasync_helper函式來初始化前面定義的 fasync_struct結構體指標, fasync_helper函式原型如下
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
??fasync_helper函式的前三個引數就是 fasync函式的那三個引數,第四個引數就是要初始化的 fasync_ struct結構體指標變數,當應用程式通過結構體指標變數,當應用程式通過“ fcntl(fd, F_SETFL, flags | FASYNC)”改變fasync標記的時候,驅動程式 file_operations操作集中的 fasync函式就會執行,
struct xxx_dev
{
......
struct fasync_struct *async_queue; /* 異步相關結構體 */
};
static int xxx_fasync(int fd, struct file *filp, int on)
{
struct xxx_dev *dev = (xxx_dev)filp->private_data;
if (fasync_helper(fd, filp, on, &dev->async_queue) < 0)
return -EIO;
return 0;
}
static struct file_operations xxx_ops =
{
......
.fasync = xxx_fasync,
......
};
??在關閉驅動檔案的時候需要在file_ operations操作集中的 release函式中釋放 fasyn_fasync struct的釋放函式同樣為 fasync_helper, release函式引數參考實體如下
static int xxx_release(struct inode *inode, struct file *filp)
{
return xxx_fasync(-1, filp, 0); /* 洗掉異步通知 */
}
static struct file_operations xxx_ops =
{
......
.release = xxx_release,
};
??第3行通過呼叫示例代碼 xxx_fasync函式來完成 fasync_struct的釋放作業,但是,其最侄訓是通過 fasync_helper函式完成釋放作業,
kill_fasync函式
??當設備可以訪問的時候,驅動程式需要向應用程式發出信號,相當于產生“中斷” kill_fasync函式負責發送指定的信號, kill_fasync函式原型如下所示
void kill_fasync(struct fasync_struct **fp, int sig, int band)
??函式引數和回傳值含義如下:
??fasync struct 要操作的檔案指標
??sig:要發送的信號
?? band:可讀時設定為 POLL IN,可寫時設定為 POLL OUT,
??回傳值:無,
應用程式對異步通知的處理
??應用程式對異步通知的處理包括以下三步
??1、注冊信號處理函式應用程式根據驅動程式所使用的信號來設定信號的處理函式,應用程式使用 signal函式來設定信號的處理函式,前面已經詳細的講過了,這里就不細講了,
??2、將本應用程式的行程號告訴給內核使用fcntl(fd, F_SETOWN, getpid)將本應用程式的行程號告訴給內核
??3、開啟異步通知使用如下兩行程式開啟異步通知:
flags = fcntl(fd, F_GETFL); /* 獲取當前的行程狀態*/
fcntl(fd, F_SETFL, flags | FASYNC); /* 開啟當前行程異步通知功能 */
??重點就是通過 fcntl函式設定行程狀態為 FASYNC,經過這一步,驅動程式中的 fasync函式就會執行,
大家的鼓勵是我繼續創作的動力,如果覺得寫的不錯,歡迎關注,點贊,收藏,轉發,謝謝!
有任何問題,均可通過公告中的二維碼聯系我
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/242654.html
標籤:嵌入式
