read和write在字符驱动内部的实现原理。包括read的IO阻塞方式的运行原理

使用read和write的函数打开一个设备文件,当write向设备文件中写数据时未完毕时,read经过wait_event_interruptible()判断标志变量condition为假处于可中断休眠状态,等待write写完数据后,更改condition为真再调用wake_up_interruptible()唤醒。

一.驱动代码,入口函数init采用字符设备驱动分布注册

字符设备驱动分布注册步骤:

1.实例化字符设备驱动对象 2.部分初始化字符设备驱动对象 3.注册驱动(采用自动创建设备节点) 4.注销

#include "head.h"
#include "cdev.h"
#if 0
unsigned int my_major=0;
#else
unsigned int my_major = 50;
#endif
unsigned int condition =0;
int mycdev_open(struct inode *inode, struct file *file)
{

    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
ssize_t mycdev_read(struct file *file, char *ubuf, size_t size, loff_t *lof)
{

    // 向用户空间读取拷贝
    if (size > sizeof(cdev_buf)) // 用户空间期待读取的大小内核满足不了,那就给内核支持的最大大小
        size = sizeof(cdev_buf);
    //检查condition是否为真,为真往下执行,否则执行可中断休眠
    wait_event_interruptible(wq_head,condition);
    ret = copy_to_user(ubuf, cdev_buf, size);
    if (ret) // 拷贝失败
    {
        printk("copy_to_user filed\n");
        return ret;
    }

    condition=0;//事件完毕,重置标志
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
ssize_t mycdev_write(struct file *file, const char *ubuf, size_t size, loff_t *lof)
{
    unsigned long ret;
    // 从用户空间读取数据
    if (size > sizeof(cdev_buf)) // 用户空间期待读取的大小内核满足不了,那就给内核支持的最大大小
        size = sizeof(cdev_buf);
    ret = copy_from_user(cdev_buf, ubuf, size);
    if (ret) // 拷贝失败
    {
        printk("copy_to_user filed\n");
        return ret;
    }
    //改变标志
    condition=1;
    //唤醒可中断进程
    wake_up_interruptible(&wq_head);
    return 0;
}
long mycdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{

    return 0;
}

int mycdev_close(struct inode *inode, struct file *file)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}

int all_led_init(void)
{

    return 0;
}


// 定义操作方法结构体变量并赋值
struct file_operations fops = {

    .open = mycdev_open,
    .read = mycdev_read,
    .write = mycdev_write,
    .release = mycdev_close,
    .unlocked_ioctl = mycdev_ioctl,
};
static int __init mycdev_init(void)
{
    int i;
    /****初始化区域_begin****/
    // 初始化等待队列头
    init_waitqueue_head(&wq_head);
    /****初始化区域_end****/

    /****驱动设备区域_begin****/
    // 1.实例化字符设备驱动对象
    cdev = cdev_alloc();
    if (cdev == NULL)
    {
        ret = -ENOMEM;
        goto OUT1;
    }
    // 2.部分初始化字符设备驱动对象
    cdev_init(cdev, &fops);
    // 3.申请设备号
    if (my_major == 0) // 动态申请设备号
    {
        ret = alloc_chrdev_region(&devno, my_minor, 3, "mycdev"); // 成功返回0,失败返回错误码
        if (ret)
        {
            printk("动态申请设备号失败\n");
            goto OUT2;
        }
        printk("动态申请设备号成功\n");
        my_major = MAJOR(devno); // 获取主设备号
        my_minor = MINOR(devno); // 获取次设备号
    }
    else // 静态制定设备号
    {
        // 通过自定义主设备号和次设备号获取设备号
        devno = MKDEV(my_major, 0);
        ret = register_chrdev_region(devno, 3, "mycdev"); // 成功申请设备号返回0,失败返回错误码
        if (ret)
        {
            printk("静态申请设备号失败\n");
            goto OUT2;
        }
        printk("静态申请设备号成功\n");
    }
    // 4.将字符设备驱动对象注册进内核
    ret = cdev_add(cdev, devno, 3);
    if (ret)
    {
        printk("字符设备驱动注册进内核失败\n");
        goto OUT3;
    }
    printk("字符设备驱动注册进内核成功\n");
    // 5.自动创建设备节点
    // 向上提交目录
    cls = class_create(THIS_MODULE, "mycdev");
    if (IS_ERR(cls)) // 为真表示cls指向4K的预留空间
    {
        printk("向上提交目录失败\n");
        ret = -PTR_ERR(cls);
        goto OUT4;
    }
    printk("向上提交目录成功\n");
    // 自动创建设备文件
    for (i = 0; i < 3; i++)
    {
        dev = device_create(cls, NULL, MKDEV(my_major, i), NULL, "mycdev%d", i);
        if (IS_ERR(dev))
        {
            printk("自动创建设备文件mycdev%d失败\n", i);
            ret = PTR_ERR(dev);
            goto OUT5;
        }
        printk("自动创建设备文件mycdev%d成功\n", i);
    }
    /****驱动设备区域_end****/

    all_led_init();
    return 0;
OUT5:
    // 释放提交成功的设备节点信息
    for (--i; i >= 0; i--)
    {
        device_destroy(cls, MKDEV(my_major, i));
    }
    // 销毁目录
    class_destroy(cls);
OUT4:
    cdev_del(cdev);
OUT3:
    unregister_chrdev_region(devno, 3);
OUT2:
    kfree(cdev);
OUT1:
    return ret;
}

static void __exit mycdev_exit(void)
{
    // 销毁设备信息
    device_destroy(cls, MKDEV(my_major, 0));
    device_destroy(cls, MKDEV(my_major, 1));
    device_destroy(cls, MKDEV(my_major, 2));
    // 销毁目录
    class_destroy(cls);
    //注销对象
    cdev_del(cdev);
    //释放设备号
    unregister_chrdev_region(MKDEV(my_major,my_minor),3);
    //释放对象的空间
    kfree(cdev);
}

module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

2.应用层read代码,循环等待

采用memset函数清空字符串更安全

#include "user.h"
//从内核的设备文件中读取数据
int main(int argc, char const *argv[])
{
    //fd_r=-1;
    fd_r=open("/dev/mycdev0",O_RDONLY);//是否阻塞具体看驱动代码,应用层无所谓
    if(fd_r<0)
    {   
        printf("%d\n",fd_r);
        printf("设备文件mycdev0打开失败\n");
        perror("read file:");
        return fd_r;
    }
    printf("设备文件mycdev0打开成功\n");
    while(1)
    {
    memset(user_buf_r,'0',sizeof(user_buf_r));
    read(fd_r,user_buf_r,sizeof(user_buf_r));
    printf("%s\n",user_buf_r);
    }
    close(fd_r);
    return 0;
}

3.应用层write代码,循环写入唤醒

#include "user.h"
//从内核的设备文件中写入数据
int main(int argc, char const *argv[])
{
       fd_w=-1;
    fd_w=open("/dev/mycdev0",O_WRONLY);//是否阻塞具体看驱动代码,应用层无所谓
    if(fd_w<0)
    {
        printf("设备文件mycdev0打开失败\n");
        return fd_w;
    }
    printf("设备文件mycdev0打开成功\n");
    while(1)
    {
    memset(user_buf_w,'0',sizeof(user_buf_w));
    printf("请输入你想说的话:>>");
    scanf("%s",user_buf_w);
    user_buf_w[strlen(user_buf_w)-1]='\0';
    write(fd_w,user_buf_w,sizeof(user_buf_w));
    }
    close(fd_w);
    return 0;
}

你可能感兴趣的:(嵌入式,驱动开发,嵌入式硬件)