【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主只是一枚小小菜鸟…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值