Linux驅動程序之阻塞和非阻塞IO

所謂阻塞方式block,顧名思義,就是進程或是線程執行到這些函數時必須等待某個事件的發生,如果事件沒有發生,進程或線程就被阻塞,函數不能立即返回。也就是說在執行設備操作時,若不能獲得資源,則掛起進程直到滿足可操作的條件後再進行操作。被掛起的進程進入睡眠狀態,被從調度器的運行隊列移走,直到等待的條件被滿足
所謂非阻塞方式non-block,就是進程或線程執行此函數時不必非要等待事件的發生,一旦執行肯定返回,以返回值的不同來反映函數的執行情況,如果事件發生則與阻塞方式相同,若事件沒有發生則返回一個代碼來告知事件未發生,而進程或線程繼續執行,所以效率較高。可是使用Select就可以完成非阻塞方式工作的程序,它能夠監視我們需要監視的文件描述符的變化情況——讀寫或是異常

以串口的應用程序爲例:
阻塞地都取串口一個字符

char buf; 
fd = open("/dev/ttys",O_RDWR);
.. .. 
res = read(fd,&buf,1); //當串口上有輸入時才返回 
if(res == 1) 
{ 
     printf("%c\n",buf); 
}

非阻塞地都取串口一個字符

char buf;
fd = open("/dev/ttys",O_RDWR | O_NONBLOCK);
.. .. 
while( read(fd,&buf,1) !=1); //當串口上無輸入也返回,所
continue;                                                //以要循環嘗試讀取串口
printf("%c\n",buf);

也就是非阻塞一定會返回一個數值 就算你read沒有讀取到數據 也會返回一個-EAGAIN 所以非阻塞時要循環讀取串口 而阻塞並不會 因爲阻塞一旦返回數值 說明讀取串口數據成功 沒讀取到不返回

驅動程序往往要提供這樣的能力:當應用程序進行read(),write()等系統調用時,若設備的資源不能獲取,而用戶又希望以阻塞的方式訪問設備,驅動程序應在設備驅動的xxx_read(),xxx_write()等操作將進程阻塞直到資源可以獲取,此後應用程序的read(),write()等調用才返回,整個過程仍然進行了正確的設備訪問,用戶並沒有感知到;若以非阻塞的方式訪問設備文件,則當設備資源不可獲取時,設備驅動的xxx_read(),xxx_write()等操作立即返回,應用程序收到-EAGAIN返回值

現在我們有了阻塞的方式讀取,那麼阻塞的進程因爲沒有獲得資源會進入休眠狀態,現在就要聊聊有關喚醒的事了。
在Linux設備驅動中,可以使用等待隊列(wait queue)來實現阻塞進程的喚醒.等待隊列能夠用於實現內核中的異步事件通知機制。
Linux提供了有關等待隊列的操作:
1)wait_queue_head_t my_queue; //定義等待隊列頭
2) init_waitqueue_head(&my_queue); //初始化隊列頭
如果覺得上邊兩步來的麻煩,可以直接使用
DECLARE_WAIT_QUEUE_HEAD(name) //定義並初始化
3) DECLARE_WAITQUEUE(name,tsk); //定義等待隊列元素
4) void fastcall add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
void fastcall remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
分別用於將等待隊列wait添加(add)或者移除(remove)到等待隊列頭q指向的等待隊列鏈表中 。
5) wait_event(queue, conditon);
wait_event_interruptible(queue, condition); //可以被信號打斷
wait_event_timeout(queue, condition, timeout);
wait_event_interruptible_timeout(queue, condition, timeout); //不能被信號打斷
queue:作爲等待隊列頭的等待隊列被喚醒
conditon:必須滿足,否則阻塞
timeout和conditon相比,有更高優先級
6) void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);
上述操作會喚醒以queue作爲等待隊列頭的所有等待隊列中所有屬於該等待隊列頭的等待隊列對應的進程。
7) sleep_on(wait_queue_head_t *q);
interruptible_sleep_on(wait_queue_head_t *q);
sleep_on作用是把目前進程的狀態置成TASK_UNINTERRUPTIBLE,並定義一個等待隊列,之後把他附屬到等待隊列頭q,直到資源可用,q引導的等待隊列被喚醒。interruptible_sleep_on作用是一樣的, 只不過它把進程狀態置爲TASK_INTERRUPTIBLE.
這兩個函數的流程是首先,定義並初始化等待隊列,把進程的狀態置成TASK_UNINTERRUPTIBLE或TASK_INTERRUPTIBLE,並將對待隊列添加到等待隊列頭。
然後通過schedule(放棄CPU,調度其他進程執行。最後,當進程被其他地方喚醒,將等待隊列移除等待隊列頭。
在Linux內核中,使用set_current_state()和__add_wait_queue()函數來實現目前進程狀態的改變,直接使用current->state = TASK_UNINTERRUPTIBLE
類似的語句也是可以的。
因此我們有時也可能在許多驅動中看到,它並不調用sleep_on或interruptible_sleep_on(),而是親自進行進程的狀態改變和切換。

話不多說上代碼

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/uaccess.h>

/*for spinlock and semaphore*/
#include <asm/spinlock.h>
#include <linux/spinlock.h>
#include <linux/semaphore.h>

/*for task management*/
#include <linux/wait.h>
#include <linux/sched.h>

/*
 * char device driver with blocked testing
 */

MODULE_LICENSE("GPL");

static int MAJOR_NUM=0;
static struct semaphore sem;
static int global_var = 0;
static int global_var_count = 0;
static spinlock_t spin;
/*waiting queue for kernel*/
static wait_queue_head_t wqueue;
static int flag = 0;

static ssize_t globalvar_read(struct file*,char*,size_t, loff_t*);
static ssize_t globalvar_write(struct file*,const char*,size_t, loff_t*);
static int globalvar_open(struct inode*node, struct file* fp);
static int globalvar_release(struct inode*node, struct file* fp);

/*init the file_operation structure*/
static struct file_operations globalvar_fpos={
    .read = globalvar_read,
    .write = globalvar_write,
    .open = globalvar_open,
    .release = globalvar_release,
};

static int __init globalvar_init(void)
{
    int ret;

    printk("register globalvar:[blocked testing]");
    /*register device drivre*/
    ret = register_chrdev(MAJOR_NUM,"globalvar",&globalvar_fpos);
    if(ret < 0){
        printk("globalvar reg failed!\n");
    }else{
        spin_lock_init(&spin);
    }

    if(MAJOR_NUM == 0){
        MAJOR_NUM = ret;
    }

    sema_init(&sem,1);
    init_waitqueue_head(&wqueue);
    return ret;
}

static void __exit globalvar_exit()
{
    unregister_chrdev(MAJOR_NUM,"globalvar");
}

static ssize_t globalvar_read(struct file* fp, char* buf, size_t len, loff_t* off)
{
    /*wait until condition become true*/
    if( wait_event_interruptible(wqueue,flag!=0) ){
        return -ERESTARTSYS;
    }

    /*get semaphore*/
    if(down_interruptible(&sem)){
        return -ERESTARTSYS;
    }

    /*copy from kernel to user space*/
    if(copy_to_user(buf,&global_var,sizeof(int)) != 0){
        /*release semaphore*/
        up(&sem);
        return -EFAULT;
    }
    /*data unaccessible flag*/
    flag = 0;

    /*release semaphore*/
    up(&sem);
    return sizeof(int);
}

static ssize_t globalvar_write(struct file* fs,const char* buf, size_t len, loff_t* off)
{
    /*get semaphore*/
    if(down_interruptible(&sem)){
        return -ERESTARTSYS;
    }

    printk("down_interruptible ok!\n");
    if(copy_from_user(&global_var,buf,sizeof(int) != 0)){
        /*release semaphore*/
        up(&sem);
        return -EFAULT;
    }

    /*release semaphore*/
    up(&sem);

    /*data ready*/
    flag = 1;
    /*wake up the waiting task*/
    wake_up_interruptible(&wqueue);
    return sizeof(int);
}

/*
 * open device with checking busy.
 * if busy,count++;else return 0
 */
static int globalvar_open(struct inode*node, struct file* fp)
{
    /*get spinlock*/
    //spin_lock(&spin);

    /*reach criticle section*/
    //if(global_var_count){
        //spin_unlock(&spin);
        //printk("[debug]:globalvar open fialed!\n");
        //return -EBUSY;
    //}

    /*release spinlock*/
    global_var_count++;

    //spin_unlock(&spin);
    return 0;
}

static int globalvar_release(struct inode*node, struct file* fp)
{
    //spin_lock(&spin);
    global_var_count--;
    //spin_unlock(&spin);
    return 0;
}

/*module setting*/
module_init(globalvar_init);
module_exit(globalvar_exit);

每寫一個數據 就會喚醒隊列讀取數據
下面是測試用的應用程序,包括了讀寫兩個分支。

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>

/*
 * this is a test for char device "globalvar"
 */

int main(int argc, char** args) {
    int fd, num;

    if (argc >= 2) {
        if (strcmp(args[1], "0") == 0) {
            printf("mode read!\n");
            /*opemn device*/
            fd = open("/dev/globalvar", O_RDWR, S_IRUSR | S_IWUSR);
            if (fd != -1) {
                while (1) {
                    read(fd, &num, sizeof(int));
                    printf("globalvar=%d\n",num);
                    if (num == 0) {
                        close(fd);
                        break;
                    }
                }//while
            } else {
                printf("error:device open error!\n");
            }
        } else if (strcmp(args[1], "1") == 0) {
            printf("mode write!\n");

            /*opemn device*/
            fd = open("/dev/globalvar", O_RDWR, S_IRUSR | S_IWUSR);
            if (fd != -1) {
                while (1) {
                    /*writing test*/
                    printf("print number to write: ");
                    scanf("%d", &num);
                    write(fd, &num, sizeof(int));
                    if (num == 0) {
                        close(fd);
                        break;
                    }
                }
            } else {
                printf("error:device open error!\n");
            }
        }
    }

    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章