歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux綜合 >> Linux資訊 >> 更多Linux

Linux設備驅動編程之阻塞與非阻塞

  阻塞操作是指,在執行設備操作時,若不能獲得資源,則進程掛起直到滿足可操作的條件再進行操作。非阻塞操作的進程在不能進行設備操作時,並不掛起。被掛起的進程進入sleep狀態,被從調度器的運行隊列移走,直到等待的條件被滿足。  在Linux驅動程序中,我們可以使用等待隊列(wait queue)來實現阻塞操作。wait queue很早就作為一個基本的功能單位出現在Linux內核裡了,它以隊列為基礎數據結構,與進程調度機制緊密結合,能夠用於實現核心的異步事件通知機制。等待隊列可以用來同步對系統資源的訪問,上節中所講述Linux信號量在內核中也是由等待隊列來實現的。  下面我們重新定義設備"globalvar",它可以被多個進程打開,但是每次只有當一個進程寫入了一個數據之後本進程或其它進程才可以讀取該數據,否則一直阻塞。 #include <linux/module.h>#include <linux/init.h>#include <linux/fs.h>#include <asm/uAccess.h>#include <linux/wait.h>#include <asm/semaphore.h> MODULE_LICENSE("GPL");#define MAJOR_NUM 254static ssize_t globalvar_read(strUCt file *, char *, size_t, loff_t*);static ssize_t globalvar_write(struct file *, const char *, size_t, loff_t*);struct file_operations globalvar_fops ={ read: globalvar_read, write: globalvar_write,};static int global_var = 0;static struct semaphore sem;static wait_queue_head_t outq;static int flag = 0;static int __init globalvar_init(void){ int ret; ret = register_chrdev(MAJOR_NUM, "globalvar", &globalvar_fops); if (ret) {  printk("globalvar register failure"); } else {  printk("globalvar register success");  init_MUTEX(&sem);  init_waitqueue_head(&outq); } return ret;}static void __exit globalvar_exit(void){ int ret; ret = unregister_chrdev(MAJOR_NUM, "globalvar"); if (ret) {  printk("globalvar unregister failure"); } else {  printk("globalvar unregister success"); }}static ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t *off){ //等待數據可獲得 if (wait_event_interruptible(outq, flag != 0)) {  return - ERESTARTSYS; } if (down_interruptible(&sem)) {  return - ERESTARTSYS; } flag = 0; if (copy_to_user(buf, &global_var, sizeof(int))) {  up(&sem);  return - EFAULT; } up(&sem); return sizeof(int);}static ssize_t globalvar_write(struct file *filp, const char *buf, size_t len,loff_t *off){ if (down_interruptible(&sem)) {  return - ERESTARTSYS; } if (copy_from_user(&global_var, buf, sizeof(int))) {  up(&sem);  return - EFAULT; } up(&sem); flag = 1; //通知數據可獲得 wake_up_interruptible(&outq); return sizeof(int);}module_init(globalvar_init);module_exit(globalvar_exit);

 

  編寫兩個用戶態的程序來測試,第一個用於阻塞地讀/dev/globalvar,另一個用於寫/dev/globalvar。只有當後一個對/dev/globalvar進行了輸入之後,前者的read才能返回。  讀的程序為:

#include <sys/types.h>#include <sys/stat.h>#include <stdio.h>#include <fcntl.h>main(){ int fd, num; fd = open("/dev/globalvar", O_RDWR, S_IRUSR S_IWUSR); if (fd != - 1) {  while (1)  {   read(fd, &num, sizeof(int)); //程序將阻塞在此語句,除非有針對globalvar的輸入   printf("The globalvar is %d\n", num);   //如果輸入是0,則退出   if (num == 0)   {    close(fd);    break;   }  } } else {  printf("device open failure\n"); }}

  寫的程序為:

#include <sys/types.h>#include <sys/stat.h>#include <stdio.h>#include <fcntl.h>main(){ int fd, num; fd = open("/dev/globalvar", O_RDWR, S_IRUSR S_IWUSR); if (fd != - 1) {  while (1)  {   printf("Please input the globalvar:\n");   scanf("%d", &num);   write(fd, &num, sizeof(int));   //如果輸入0,退出   if (num == 0)   {    close(fd);    break;   }  } } else {  printf("device open failure\n"); }}

  打開兩個終端,分別運行上述兩個應用程序,發現當在第二個終端中沒有輸入數據時,第一個終端沒有輸出(阻塞),每當我們在第二個終端中給globalvar輸入一個值,第一個終端就會輸出這個值,如下圖:

第一個終端就會輸出這個值

 

  關於上述例程,我們補充說一點,如果將驅動程序中的read函數改為:

static ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t *off){ //獲取信號量:可能阻塞 if (down_interruptible(&sem)) {  return - ERESTARTSYS; } //等待數據可獲得:可能阻塞 if (wait_event_interruptible(outq, flag != 0)) {  return - ERESTARTSYS; } flag = 0; //臨界資源訪問 if (copy_to_user(buf, &global_var, sizeof(int))) {  up(&sem);  return - EFAULT; } //釋放信號量 up(&sem); return sizeof(int);}

  即交換wait_event_interruptible(outq, flag != 0)和down_interruptible(&sem)的順序,這個驅動程序將變得不可運行。實際上,當兩個可能要阻塞的事件同時出現時,即兩個wait_event或down擺在一起的時候,將變得非常危險,死鎖的可能性很大,這個時候我們要特別留意它們的出現順序。當然,我們應該盡可能地避免這種情況的發生!

  +還有一個與設備阻塞與非阻塞訪問息息相關的論題,即select和poll,select和poll的本質一樣,前者在BSD Unix中引入,後者在System V中引入。poll和select用於查詢設備的狀態,以便用戶程序獲知是否能對設備進行非阻塞的訪問,它們都需要設備驅動程序中的poll函數支持。  驅動程序中poll函數中最主要用到的一個API是poll_wait,其原型如下: void poll_wait(struct file *filp, wait_queue_heat_t *queue, poll_table * wait);  poll_wait函數所做的工作是把當前進程添加到wait參數指定的等待列表(poll_table)中。下面我們給globalvar的驅動添加一個poll函數: static unsigned int globalvar_poll(struct file *filp, poll_table *wait){ unsigned int mask = 0; poll_wait(filp, &outq, wait); //數據是否可獲得?  if (flag != 0) {  mask = POLLIN POLLRDNORM; //標示數據可獲得 } return mask;}  需要說明的是,poll_wait函數並不阻塞,程序中poll_wait(filp, &outq, wait)這句話的意思並不是說一直等待outq信號量可獲得,真正的阻塞動作是上層的select/poll函數中完成的。select/poll會在一個循環中對每個需要監聽的設備調用它們自己的poll支持函數以使得當前進程被加入各個設備的等待列表。若當前沒有任何被監聽的設備就緒,則內核進行調度(調用schedule)讓出cpu進入阻塞狀態,schedule返回時將再次循環檢測是否有操作可以進行,如此反復;否則,若有任意一個設備就緒,select/poll都立即返回。  我們編寫一個用戶態應用程序來測試改寫後的驅動。程序中要用到BSD Unix中引入的select函數,其原型為: int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);  其中readfds、writefds、exceptfds分別是被select()監視的讀、寫和異常處理的文件描述符集合,numfds的值是需要檢查的號碼最高的文件描述符加1。timeout參數是一個指向struct timeval類型的指針,它可以使select()在等待timeout時間後若沒有文件描述符准備好則返回。struct timeval數據結構為:

struct timeval {   int tv_sec; /* seconds */   int tv_usec; /* microseconds */ };

  除此之外,我們還將使用下列API:  FD_ZERO(fd_set *set)――清除一個文件描述符集;   FD_SET(int fd,fd_set *set)――將一個文件描述符加入文件描述符集中;   FD_CLR(int fd,fd_set *set)――將一個文件描述符從文件描述符集中清除;   FD_ISSET(int fd,fd_set *set)――判斷文件描述符是否被置位。

  下面的用戶態測試程序等待/dev/globalvar可讀,但是設置了5秒的等待超時,若超過5秒仍然沒有數據可讀,則輸出"No data within 5 seconds":

#include <sys/types.h>#include <sys/stat.h>#include <stdio.h>#include <fcntl.h>#include <sys/time.h>#include <sys/types.h>#include <unistd.h>main(){ int fd, num; fd_set rfds; struct timeval tv; fd = open("/dev/globalvar", O_RDWR, S_IRUSR S_IWUSR); if (fd != - 1) {  while (1)  {   //查看globalvar是否有輸入   FD_ZERO(&rfds);   FD_SET(fd, &rfds);   //設置超時時間為5s   tv.tv_sec = 5;   tv.tv_usec = 0;   select(fd + 1, &rfds, NULL, NULL, &tv);   //數據是否可獲得?   if (FD_ISSET(fd, &rfds))   {    read(fd, &num, sizeof(int));    printf("The globalvar is %d\n", num);    //輸入為0,退出    if (num == 0)    {     close(fd);     break;    }   }   else    printf("No data within 5 seconds.\n");  } } else {  printf("device open failure\n"); }}

  開兩個終端,分別運行程序:一個對globalvar進行寫,一個用上述程序對globalvar進行讀。當我們在寫終端給globalvar輸入一個值後,讀終端立即就能輸出該值,當我們連續5秒沒有輸入時,"No data within 5 seconds"在讀終端被輸出,如下圖:

"No data within 5 seconds"在讀終端被輸出




Copyright © Linux教程網 All Rights Reserved