【Linux驅動】阻塞型I/O(一)

校招結束很久了,回顧校招,除了阿里的筆試沒通過以後,其餘的均通過了筆試拿到面試機會(海康威視由於進錯考場考錯了試卷,沒有拿到面試資格,不過後面去霸面,最後順利拿到offer),在拿到幾個還算重量級的offer以後,後面就開始休息了,大家都說今年的就業形勢很艱難,說實話我沒怎麼感覺到,九月底底扎堆的公司太多了,曾記得,一天參加六場筆試+面試,不得不放棄一些面試,有舍有得嘛。
閒來無事,學習下linux內核驅動,順便記錄下來,估計從這個系列開始就是以自我學習的所謂筆記形式更新了,以便日後自己回顧。
由於先前研讀過linux部分內核源碼,尤其是內核網絡協議棧,所以在第一次接觸linux內核驅動的時候,還算蠻好理解的,驅動是跟文件系統息息相關的,上面VFS提供一個抽象層接口,你添加一個內核驅動模塊,你只需要基於上層的VFS接口函數重新實現你的驅動即可,有點類似於C++中的春虛函數語義,然後重新實現一些操作函數就類似於C++中的運算符重載,你應該儘量保證你實現的操作函數是和標準語義一致的。
回想linux內核網絡協議棧也是某個層給定一個操作函數集,然後具體的協議再提供具體的操作函數,比如傳輸層的UDP/TCP等,同樣的接口對應不同的內部實現。
很多是相同的,對於內核驅動,你還要清楚用戶態和內核態,這些隨着你的進一步學習,會有更深的理解。

本篇的例程只是個毛本,直接上代碼(請原諒我懶,沒寫註釋,下篇儘量給出註釋),只作爲本人的記錄,細節神馬的參考《LDD》
後續我們會不斷的完善這個阻塞型I/O內核驅動代碼。

驅動代碼 wqlkp.c

關鍵詞: init_waitqueue_head()、wait_event_interruptible()、wake_up_interruptible()

#include <linux/module.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <asm/uaccess.h>

MODULE_LICENSE("Dual BSD/GPL");

#define DEBUG_SWITCH 1
#if DEBUG_SWITCH
    #define P_DEBUG(fmt, args...)  printk("<1>" "<kernel>[%s]"fmt,__FUNCTION__, ##args)
#else
    #define P_DEBUG(fmt, args...)  printk("<7>" "<kernel>[%s]"fmt,__FUNCTION__, ##args)
#endif

#define DEV_SIZE 100
#define WQ_MAJOR 230

struct wq_dev{
    char kbuf[DEV_SIZE];
    dev_t devno;
    unsigned int major;
    struct cdev wq_cdev;
    unsigned int cur_size;
    wait_queue_head_t r_wait;
    wait_queue_head_t w_wait;
};

//struct wq_dev *wq_devp;

int wq_open(struct inode *inodep, struct file *filp)
{
    struct wq_dev *dev;
    dev = container_of(inodep->i_cdev, struct wq_dev, wq_cdev);
    filp->private_data = dev;
    printk(KERN_ALERT "open is ok!\n");
    return 0;
}

int wq_release(struct inode *inodep, struct file *filp)
{
    printk(KERN_ALERT "release is ok!\n");
    return 0;
}

static ssize_t wq_read(struct file *filp, char __user *buf, size_t count, loff_t *offset)
{
    struct wq_dev *dev = filp->private_data;

    P_DEBUG("read data...\n");
    wait_event_interruptible(dev->r_wait, dev->cur_size > 0);

    if(count > dev->cur_size)
        count = dev->cur_size;

    if(copy_to_user(buf, dev->kbuf, count))
        return -EFAULT;

    dev->cur_size -= count;
    wake_up_interruptible(&dev->w_wait);
    P_DEBUG("%s did read %d bytes\n", current->comm, (unsigned int)count);
    return count;
}

static ssize_t wq_write(struct file *filp, char __user *buf, size_t count, loff_t *offset)
{
    struct wq_dev *dev = filp->private_data;

    wait_event_interruptible(dev->w_wait, dev->cur_size < DEV_SIZE);

    if(count > DEV_SIZE - dev->cur_size)
        count = DEV_SIZE - dev->cur_size;

    if(copy_from_user(dev->kbuf, buf, count))
        return -EFAULT;
    dev->cur_size += count;
    P_DEBUG("write %d bytes , cur_size:[%d]\n", count, dev->cur_size);
    P_DEBUG("kbuf is [%s]\n", dev->kbuf);
    wake_up_interruptible(&dev->r_wait);
    return count;
}

struct file_operations wq_fops = {
    .open = wq_open,
    .release = wq_release,
    .write = wq_write,
    .read = wq_read,
};

struct wq_dev my_dev;

static int __init wq_init(void)
{
    int result = 0;
    my_dev.cur_size = 0;
    my_dev.devno = MKDEV(WQ_MAJOR, 0);
    if(WQ_MAJOR)
        result = register_chrdev_region(my_dev.devno, 1, "wqlkp");
    else
    {
        result = alloc_chrdev_region(&my_dev.devno, 0, 1, "wqlkp");
        my_dev.major = MAJOR(my_dev.devno);
    }
    if(result < 0)
        return result;

    cdev_init(&my_dev.wq_cdev, &wq_fops);
    my_dev.wq_cdev.owner = THIS_MODULE;
    init_waitqueue_head(&my_dev.r_wait);
    init_waitqueue_head(&my_dev.w_wait);

    result = cdev_add(&my_dev.wq_cdev, my_dev.devno, 1);
    if(result < 0)
    {
        P_DEBUG("cdev_add error!\n");
        goto err;
    }
    printk(KERN_ALERT "hello kernel\n");
    return 0;

err:
    unregister_chrdev_region(my_dev.devno,1);
}

static void __exit wq_exit(void)
{
    cdev_del(&my_dev.wq_cdev);
    unregister_chrdev_region(my_dev.devno, 1);
}

module_init(wq_init);
module_exit(wq_exit);

用戶空間代碼
app_read.c

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

int main(void)
{
    char buf[20];
    int fd;
    int ret;

    fd = open("/dev/wqlkp", O_RDWR);
    if(fd < 0)
    {
        perror("open");
        return -1;
    }

    read(fd, buf, 10);
    printf("<app>buf is [%s]\n", buf);

    close(fd);
    return 0;
}

app_write.c

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

int main(void)
{
    char buf[20];
    int fd;
    int ret;

    fd = open("/dev/wqlkp", O_RDWR);
    if(fd < 0)
    {
        perror("open");
        return -1;
    }

    write(fd, "wen qian", 10);

    close(fd);
    return 0;
}

另外大家可以修改下驅動代碼,修改kbuf的大小,將wqlkp.c 中的宏定義 DEV_SIZE 改爲10,那麼你在用戶態兩次運行./app_write,發現第二次會阻塞,因爲空間滿了,運行./app_read後就會喚醒阻塞的那個write。

這是調試成功後的代碼,在此之前有個小插曲,就是未成功的版本,代碼編譯成功,但是在用戶態驗證的時候,運行app_read後,再開個終端運行app_write,整個電腦崩潰完全死機,我是在物理機操作,模塊直接裝載入內核。然後只能強制重啓電腦,後來仔細分析代碼及調試,發現是由於在read和write函數之間出現了阻塞,read一進去就阻塞等待write喚醒,然後write一進去就阻塞等待read喚醒,然後就沒然後了。
希望大家額外注意這點。
改動了以後出現了上面這個調試成功但並不完美的阻塞性I/O驅動代碼,由於着重點在阻塞性I/O,所以並沒有考慮併發控制的問題。

發現linux內核驅動學問大了去了,po主只是一枚小小菜鳥…

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