《linux設備驅動開發詳解》筆記——8阻塞與非阻塞IO


 

8.1 阻塞與非阻塞IO

8.1.0 概述

  • 阻塞:訪問設備時,若不能獲取資源,則進程掛起,進入睡眠狀態;也就是進入等待隊列

 

 

  • 非阻塞:不能獲取資源時,不睡眠,要么退出、要么一直查詢;直接退出且無資源時,返回-EAGAIN

  • 阻塞進程的喚醒:必須有地方能夠喚醒處於睡眠狀態的阻塞進程,否則就真睡不醒了。一般是在中斷中。
  • 阻塞與非阻塞可以在open時設置,也可以通過fcntl和ioctl重新設置

8.1.1 等待隊列

  linux驅動中,可以用等待隊列wait queue實現阻塞。等待隊列與linux進程調度緊密結合,理解等待隊列,對理解linux調度很有幫助。

  使用等待隊列,大體由一下三個方面構成:

  • 初始化init,組織等待隊列
  • 睡,schedule,加入等待隊列以后,可以主動觸發內核調度其他進程,從而進入睡眠
  • 醒,wake up,喚醒等待隊列里的進程(第一步本進程已經加入了該等待隊列)
#include <linux/wait.h>

/**** 定義和初始化等待隊列 *****/
// head相關
wait_queue_head_t my_queue;        // 1.定義“等待隊列head”
init_waitqueue_head( &my_queue );     // 2.初始化該“等待隊列head”
DECLARE_WAIT_QUEUE_HEAD(&my_queue );   // 3.相當於1+2的效果,定義+初始化

// 定義等待隊列元素
DECLARE_WAITQUEUE( wait,tsk);       // 定義1個等待隊列元素,與進程tsk掛鈎;tsk是進程,一般用current,表示當前進程,對應task_struct;

// 添加刪除等待隊列元素,即把1個具體的等待隊列添加都head所在的鏈表里
void add_wait_queue( wait_queue_head_t *q, wait_queue_t *wait );
void remove_wait_queue( wait_queue_head_t *q, wait_queue_t *wait );

/**** 等待事件,不一定需要調用,linux內核有些驅動也調用了 ****/
wait_event( my_queue, condition );            // 等待my_queue的隊列被喚醒,同時condition必須滿足,否則繼續阻塞
wait_event_interruptible( my_queue,condition );     // 可被信號打斷
wait_event_timeout( my_queue, condition, timeout );  // timeout到時后,不論condition是否滿足,均返回
wait_evetn_timeout_interruptible( my_queue, condition, timeout );

/**** 喚醒 ****/
void wake_up( wait_queue_head_t * queue );          // 能喚醒TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE兩種狀態的進程
void wake_up_interruptible( wait_queue_head_t * queue );  // 只能喚醒處於TASK_INTERRUPTIBLE狀態的進程

【注】
wait和wake_up,是否帶_interruptible要成對兒使用。

/* 在等待隊列上睡眠,老接口,不建議用,會產生race */
sleep_on( wait_queue_head_t *q );            // 干的事挺多,進程狀態設置為TASK_UNINTERRUPTIBLE——>定義wait並加到head——>調度出去
interruptible_sleep_on( wait_queue_heat *q );

   如下圖,等待隊列采用鏈表形式,先要用 DECLARE_WAIT_QUEUE_HEAD()定義並初始化一個wait_queue_head,然后再定義1個隊列元素wait_queue,再通過add/remove函數把這個元素添加到隊列或從隊列刪除。

 

重要的等待隊列模板(假設head_t已經定義好了):

#include <linux/wait.h>
#include <linux/sched.h>  // for __set_current_state()/schedule()...

static ssize_t xxx_write( struct file *file, const char *buffer, size_t count, loff_t * ppos )
{
  ......
  DECLEAR_WAITQUEUE( wait, current );  // 定義等待隊列元素,current代表當前進程的task_struct
  add_wait_queue( &xxx_wait,wait );   // 將等待隊列元素wait加入到頭部為xxx_wait的等待隊列里

  do{
    avail = device_writable(...);    
    if( avail < 0 ){             // 資源不可用
      if( file->f_flags & O_NONBLOCK ){  // 非阻塞立即返回
        ret = -EAGAIN;
        goto out;
      }
  
      __set_current_state( TASK_INTERRUPTIBLE );    // 只是設置狀態,進程還在運行
      schedule();                      // 調度其他進程執行,估計內核正常的調度也是調用這個函數
      
      if( signal_pending( current ) ){          // interruptible是淺睡眠,可被信號打斷,醒來時要判斷是否是被信號打斷的
        ret = -ERESTERTSYS;
        goto out;
      }
    }
  }while(avail<0)

  /* 寫設備 */
  device_write(...);

out:
  remove_wait_queue( &xxx_wait,wait );
  set_current_state( TASK_RUNNING );
  return ret;
}



#include <asm-generic/current.h>    // current代表當前進程的task_struct

#define get_current() (current_thread_info()->task)
#define current get_current()

 

#include <linux/sched.h>

/*
* set_current_state() includes a barrier so that the write of current->state
* is correctly serialised wrt the caller's subsequent test of whether to
* actually sleep:
*
* set_current_state(TASK_UNINTERRUPTIBLE);
* if (do_i_need_to_sleep())
* schedule();
*
* If the caller does not need such serialisation then use __set_current_state()
*/
#define __set_current_state(state_value) \
do { current->state = (state_value); } while (0)
#define set_current_state(state_value) \
set_mb(current->state, (state_value))

8.1.2 支持阻塞等待的驅動

 把globalmem做成FIFO形式,滿了再寫就阻塞,空了再讀就阻塞,讀喚醒寫,寫喚醒讀。

/*
    注意:1.用等待隊列實現讀寫阻塞,把globalmem當成FIFO處理
 */


#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/wait.h>
#include <linux/mutex.h>
#include <linux/sched.h>

#define DEV_NAME    "globalmem"

#define GLOBALMEN_LEN    1024

struct globalmem_dev_t
{
    struct cdev cdev;
    struct class * class;
    dev_t  dev_no;
    wait_queue_head_t r_wait_head;
    wait_queue_head_t w_wait_head;
    struct mutex mutex;
    unsigned int curr_len;
    char buf[GLOBALMEN_LEN];
}globalmem_dev;


int globalmem_open(struct inode * inode, struct file * filp)
{
    filp->private_data = &globalmem_dev;
#if 0    // 不能放在這,因為每次寫,例如echo命令,就會open一次,會重新初始化r_wait_head,導致read等待隊列重新初始化,引起等待隊列異常。 唉,基本常識都忘記了
    init_waitqueue_head(&globalmem_dev.r_wait_head);
    init_waitqueue_head(&globalmem_dev.w_wait_head);
    mutex_init(&globalmem_dev.mutex);
#endif
    printk("\r\nglobalmem open.");
    printk("\r\nglobalmem open.");
    return 0;
}

ssize_t globalmem_read(struct file *filp, char __user * buf, size_t len, loff_t * pos)
{
    struct globalmem_dev_t * globalmem_devp;
    size_t len_rd;
    int ret;

    globalmem_devp = filp->private_data;

    DECLARE_WAITQUEUE(wait,current);

    mutex_lock(&globalmem_devp->mutex);
    add_wait_queue(&globalmem_devp->r_wait_head,&wait);
    while(globalmem_devp->curr_len==0)
    {
        // non-block 
        if( filp->f_flags&O_NONBLOCK )
        {
            ret = -EAGAIN;
            goto out;
        }

        printk("\r\nglobelmem read before schedule.");
        __set_current_state( TASK_INTERRUPTIBLE );
        mutex_unlock(&globalmem_devp->mutex);    // 睡前一定要解鎖,否則可能引起死鎖
        schedule();        // schudule    
        printk("\r\nglobelmem read after schedule.");
        if( signal_pending( current ) ){
            ret = -ERESTARTSYS;
            goto out2;        
        }
        printk("\r\nglobelmem after signal_pending.");
        mutex_lock(&globalmem_devp->mutex);    
    }

    if( len>globalmem_devp->curr_len )
        len_rd = globalmem_devp->curr_len;
    else
        len_rd = len;    

    if( copy_to_user(buf,globalmem_devp->buf,len_rd) ){
        ret=-EFAULT;
        goto out;
    }
    else{
        memcpy(globalmem_devp->buf,&globalmem_devp->buf[len_rd],globalmem_devp->curr_len-len_rd);
        globalmem_devp->curr_len-=len_rd; 
        printk(KERN_INFO"read %d bytes,current_len %d bytes.",len_rd,globalmem_devp->curr_len);
        wake_up_interruptible(&globalmem_devp->w_wait_head);    // 喚醒等待隊列里的寫
        ret = len_rd;
    }

out:
    mutex_unlock(&globalmem_devp->mutex);
out2:
    remove_wait_queue(&globalmem_devp->r_wait_head,&wait);
    set_current_state(TASK_RUNNING);
    return ret;
}


ssize_t globalmem_write (struct file *filp, const char __user *buf, size_t len, loff_t *pos)
{
    struct globalmem_dev_t * globalmem_devp;
    size_t len_wr;
    int ret;

    printk("\r\nEnter glocalmem_write.");

    globalmem_devp = filp->private_data;
    DECLARE_WAITQUEUE(wait,current);

    mutex_lock(&globalmem_devp->mutex);
    add_wait_queue(&globalmem_devp->w_wait_head,&wait);
    while(globalmem_devp->curr_len==GLOBALMEN_LEN)
    {
        // non-block 
        if( filp->f_flags&O_NONBLOCK )
        {
            ret = -EAGAIN;
            goto out;
        }

        __set_current_state( TASK_INTERRUPTIBLE );
        mutex_unlock(&globalmem_devp->mutex);    // 睡前一定要解鎖,否則可能引起死鎖
        schedule();        // schudule    

        if( signal_pending( current ) ){
            ret = -ERESTARTSYS;
            goto out2;        
        }
        
        mutex_lock(&globalmem_devp->mutex);    
    }

    if( len>(GLOBALMEN_LEN-globalmem_devp->curr_len) )
        len_wr = GLOBALMEN_LEN - globalmem_devp->curr_len;
    else
        len_wr = len;    

    if( copy_from_user(globalmem_devp->buf+globalmem_devp->curr_len,buf,len_wr) ){
        ret=-EFAULT;
        goto out;
    }
    else{
        globalmem_devp->curr_len+=len_wr; 
        printk(KERN_INFO"write %d bytes,current_len %d bytes.",len_wr,globalmem_devp->curr_len);
        wake_up_interruptible(&globalmem_devp->r_wait_head);    // 喚醒等待隊列里的寫
        ret = len_wr;
    }
    

out:
    mutex_unlock(&globalmem_devp->mutex);
out2:
    remove_wait_queue(&globalmem_devp->w_wait_head,&wait);
    set_current_state(TASK_RUNNING);
    return ret;    
}


loff_t globalmem_llseek(struct file *filp, loff_t offset, int whence )
{
    loff_t ret;    // 注意要有返回值

    switch(whence){
    case SEEK_SET:
        if( offset < 0 )
            return -EINVAL;
        if( offset > GLOBALMEN_LEN )
            return -EINVAL;
        filp->f_pos = offset;
        ret = filp->f_pos;    
        break;
    case SEEK_CUR:
        if((filp->f_pos+offset)< 0 )
            return -EINVAL;
        if((filp->f_pos+offset)> GLOBALMEN_LEN )
            return -EINVAL;
        filp->f_pos += offset;
        ret = filp->f_pos;
        break;
    case SEEK_END:
        if((filp->f_pos+offset)< 0 )
            return -EINVAL;
        if((filp->f_pos+offset) > GLOBALMEN_LEN )
            return -EINVAL;
        filp->f_pos += (offset+GLOBALMEN_LEN);
        ret = filp->f_pos;
        break;
    default:
        return -EINVAL;
        break;
    }
    
    return ret;
}

int globalmem_release(struct inode * inode, struct file * filp)
{
    return 0;
}

struct file_operations globalmem_fops = {
    .owner = THIS_MODULE,
    .open = globalmem_open,
    .read = globalmem_read,
    .write = globalmem_write,
    .llseek = globalmem_llseek,
    .release = globalmem_release,
};

static int __init globalmem_init( void )
{
    int ret;

    printk("enter globalmem_init()\r\n");
    
    cdev_init(&globalmem_dev.cdev,&globalmem_fops);
    globalmem_dev.cdev.owner=THIS_MODULE;

    if( (ret=alloc_chrdev_region(&globalmem_dev.dev_no,0,1,DEV_NAME))<0 )
    {
        printk("alloc_chrdev_region err.\r\n");    
        return ret;
    }
    ret = cdev_add(&globalmem_dev.cdev,globalmem_dev.dev_no,1);
    if( ret )
    {
        printk("cdev_add err.\r\n");    
        return ret;
    }

    /*
         * $ sudo insmod globalmem.ko    如果使用class_create,insmod時會報錯,dmesg查看內核log信息發現找不到class相關函數
     *   insmod: ERROR: could not insert module globalmem.ko: Unknown symbol in module
     *   $ dmesg   
     *    [ 5495.606920] globalmem: Unknown symbol __class_create (err 0)
     *    [ 5495.606943] globalmem: Unknown symbol class_destroy (err 0)
     *    [ 5495.607027] globalmem: Unknown symbol device_create (err 0)
     */    

    globalmem_dev.class = class_create( THIS_MODULE, DEV_NAME );
    device_create(globalmem_dev.class,NULL,globalmem_dev.dev_no,NULL,DEV_NAME);

    /* init mem and pos */
    memset(globalmem_dev.buf,0,GLOBALMEN_LEN);

    init_waitqueue_head(&globalmem_dev.r_wait_head);
    init_waitqueue_head(&globalmem_dev.w_wait_head);
    mutex_init(&globalmem_dev.mutex);
    

    return 0;
}


static void __exit globalmem_exit( void )
{
    printk("enter globalmem_exit()\r\n");
    unregister_chrdev_region(globalmem_dev.dev_no, 1);
    cdev_del(&globalmem_dev.cdev);
    device_destroy(globalmem_dev.class,globalmem_dev.dev_no);
    class_destroy(globalmem_dev.class);
}

module_init(globalmem_init);
module_exit(globalmem_exit);

MODULE_LICENSE("GPL");    // 不加此聲明,會報上述Unknown symbol問題

8.1.3 在用戶空間驗證阻塞驅動

運行結果:打開兩個終端窗口,在一個里面用echo寫入FIFO,兩一個cat實時顯示。

終端1:

:~$ sudo cat /dev/globalmem &
:[1] 8881
~$ 
success
nb

終端2:
sudo echo "success" > /dev/globalmem 
bash: /dev/globalmem: 權限不夠
:~$ sudo su                 // 必須切換到root,否則沒有權限寫
[sudo] 密碼: 
root@***# echo 'success' > /dev/globalmem 
root@***# echo 'nb' > /dev/globalmem 

 

8.2 輪詢操作

  對於無阻塞訪問,應用程序一般要查詢是否可以無阻塞的訪問。一般用select/poll查詢是否可無阻塞訪問,可能在select/poll里阻塞睡眠.

  

 

8.2.1 用戶空間輪詢編程

#include <sys/select.h>

/* Check the first NFDS descriptors each in READFDS (if not NULL) for read
readiness, in WRITEFDS (if not NULL) for write readiness, and in EXCEPTFDS
(if not NULL) for exceptional conditions. If TIMEOUT is not NULL, time out
after waiting the interval specified therein. Returns the number of ready
descriptors, or -1 for errors.

This function is a cancellation point and therefore not marked with
__THROW. */
extern int select (int __nfds, fd_set * readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

__nfds:最大fd+1

 

#define FD_SET(fd, fdsetp) __FD_SET (fd, fdsetp)

#define FD_CLR(fd, fdsetp) __FD_CLR (fd, fdsetp)
#define FD_ISSET(fd, fdsetp) __FD_ISSET (fd, fdsetp)
#define FD_ZERO(fdsetp) __FD_ZERO (fdsetp)

/* A time value that is accurate to the nearest
microsecond but also has a range of years. */
struct timeval
{
  __time_t tv_sec; /* Seconds. */
  __suseconds_t tv_usec; /* Microseconds. */
};

 

#include <poll.h>

/* Data structure describing a polling request. */
struct pollfd
{
  int fd; /* File descriptor to poll. */
  short int events; /* Types of events poller cares about. */
  short int revents; /* Types of events that actually occurred. */
};


__BEGIN_DECLS

/* Poll the file descriptors described by the NFDS structures starting at
FDS. If TIMEOUT is nonzero and not -1, allow TIMEOUT milliseconds for
an event to occur; if TIMEOUT is -1, block until an event occurs.
Returns the number of file descriptors with events, zero if timed out,
or -1 for errors.

This function is a cancellation point and therefore not marked with
__THROW. */
extern int poll (struct pollfd *__fds, nfds_t __nfds, int __timeout);

/* These are specified by iBCS2 */
#define POLLIN 0x0001     // 可讀
#define POLLPRI 0x0002     // 可讀高優先級數據
#define POLLOUT 0x0004        // 可寫
#define POLLERR 0x0008     // 已出錯
#define POLLHUP 0x0010     // 已掛斷
#define POLLNVAL 0x0020    // 描述符不引用一打開文件

/* The rest seem to be more-or-less nonstandard. Check them! */
#define POLLRDNORM 0x0040   // 可讀普通數據
#define POLLRDBAND 0x0080   // 可讀非0優先級波段數據
#ifndef POLLWRNORM       // 與POLLOUT相同
#define POLLWRNORM 0x0100
#endif
#ifndef POLLWRBAND       // 可寫非0優先級波段數據
#define POLLWRBAND 0x0200
#endif
#ifndef POLLMSG
#define POLLMSG 0x0400
#endif
#ifndef POLLREMOVE
#define POLLREMOVE 0x1000
#endif
#ifndef POLLRDHUP
#define POLLRDHUP 0x2000
#endif

#define POLLFREE 0x4000 /* currently only for epoll */

#define POLL_BUSY_LOOP 0x8000

struct pollfd {
int fd;
short events;
short revents;
};

 

! 8.2.2 驅動輪詢編程

原型:poll( struct file * filp, poll_table * wait )
要干兩件事:
  • 對可能引起設備文件狀態變化的等待隊列調用poll_wait()函數,將對應的等待隊列頭部添加到poll_table中。
  • 返回表示涉筆是否可無阻塞讀、寫訪問的掩碼

void poll_wait( struct file * filp, wait_queue_head_t * queue, poll_table * wait ); 此函數並不阻塞,只是將當前進程添加到等待列表 poll_table中。實際作用是可以讓queue對應的等待隊列可以喚醒因select()

睡眠的進程。

 模板如下:

驅動poll函數模板:
static unsigned int xxx_poll( struct file * filp, poll_table * wait )
{
  unsigned int mask=0;
  struct xxx_dev *dev=filp->private_data;

  ...
  poll_wait( filp, &dev->r_wait, wait );
  poll_wait( filp, &dev->w_wait, wait );    // 把具體驅動的等待隊列頭部加入到 poll_table中

  if( ... )    // 可讀
    mask = POLLIN | POLLRDNORM;

  if( ... )    // 可寫
    mask = POLLOUT | POLLWRNORM;

  ...

  return mask;
}

 

8.2.3支持輪詢的globalmem驅動

 

unsigned int globalmem_poll( struct file * filp, poll_table * wait )
{
    unsigned int mask=0;
    struct globalmem_dev_t * globalmem_devp;

    globalmem_devp = filp->private_data;

    mutex_lock( &globalmem_devp->mutex );
    poll_wait( filp, &globalmem_devp->r_wait_head, wait );
    poll_wait( filp, &globalmem_devp->w_wait_head, wait );

    if( globalmem_devp->curr_len != 0 )
        mask |= POLLIN | POLLRDNORM;

    if( globalmem_devp->curr_len != GLOBALMEN_LEN )
        mask |= POLLOUT | POLLWRNORM;
    mutex_unlock( &globalmem_devp->mutex );
    return mask;
}

struct file_operations globalmem_fops = {
    .owner = THIS_MODULE,
    .open = globalmem_open,
    .read = globalmem_read,
    .write = globalmem_write,
    .llseek = globalmem_llseek,
    .release = globalmem_release,
    .poll = globalmem_poll,
};

 

8.2.4 應用層驗證

#include <stdio.h>    // printf
#include <stdlib.h>    // exit
#include <unistd.h>
#include <fcntl.h>    // open
#include <sys/select.h>

#define FILE_NAME    "/dev/globalmem"

int main(int args, char *argv[])
{
    int fd;
    fd_set rd_fd_set,wr_fd_set;    

    printf("\r\nstart.");

    // open file
    fd=open(FILE_NAME,O_RDONLY|O_NONBLOCK);
    if( fd<0 )    
    {
        printf("\r\nopen file err");
        exit(-1);
    }
    
        
    while(1)
    {
        FD_ZERO(&rd_fd_set);
        FD_ZERO(&wr_fd_set);
        FD_SET(fd,&rd_fd_set);
        FD_SET(fd,&wr_fd_set);
        
        select(fd+1,&rd_fd_set,&wr_fd_set,NULL,NULL);
        if( FD_ISSET(fd,&rd_fd_set) )
            printf("\r\nPoll monitor:can be read");
        if( FD_ISSET(fd,&wr_fd_set) )
            printf("\r\nPoll monitor:can be write");        
    }
    close(fd);
    exit(0);
}

 

終端1執行應用程序:
1
.初始狀態,只顯示可寫 Poll monitor:can be write ..... 2.執行命令1后,可讀可寫 Poll monitor:can be write Poll monitor:can be read .... 3.執行命令2后,又變回可寫,不可讀了 Poll monitor:can be write .....
終端2讀寫globalmem # echo
"test" > /dev/globalmem   // 命令1 # cat /dev/globalmem        // 命令2 test

8.3 總結


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM