简单linux字符设备驱动程序与编程小技巧(下)

紧接前文《简单linux字符设备驱动程序与编程小技巧(上)》 http://blog.csdn.net/jiebaoabcabc/article/details/19242621

前文重点介绍了自定义的描述设备的结构体_dev_sct和模块init函数中的设备注册实现策略。今天将重点实现file_operations结构里的open,read,write,llseek,release函数方法。

41-54

int rw_open(struct inode *node, struct file *filp){    struct _dev_sct *dev;        dev = container_of(node->i_cdev, struct _dev_sct, c_dev);    filp->private_data = dev;    dev->d_minor = iminor(node);    P_DEBUG("open minor = [%d]\n", dev->d_minor);    return 0;}int rw_close(struct inode *node, struct file *filp){    return 0;}

模块中的open方法与应用程序库中的open相对应,可以理解为应用程序中的open函数将调用模块中的open钩子函数完成程序员想要在设备文件打开时所要完成的相关初始化或限制的动作。

inode结构中关键保存着被打开的设备文件关联的设备号,我们从inode结构中获得我们想要用来区分次设备的次设备号,这个设备号被保存在inode中的i_cdev指针上,而这个指向cdev类型的指针指向的正是我们分配的结构体_dev_sct中的c_dev,这样我们就获得了结构体_dev_sct中一个重要成员的地址了,但是我们还是关心如何操作结构体_dev_sct,因为这个结构中包含很多重要信息。根据成员获得包含这个成员结构体的方法就是container_of宏,这个宏根据成员指针和在结构体中的偏移量获得结构体指针,在链表中很容易找到这个宏。

container_of的具体介绍可以看看牛人的详解http://blog.csdn.net/yinkaizhong/article/details/4093795 这个宏很经典,自己也完全有能力编写试试看。

在成功获得_dev_sct的地址后就要像办法把它传递出去,file结构帮了大忙,file的private_data用来保存为驱动程序提供服务的结构或函数,file结构将变成服务的接口,提高移植性。

在获取_dev_sct结构后也应该对其重要数据初始化,如打开的设备文件对应的次设备号赋值给d_minor。

55-88

int rw_read(struct file *filp, char __user *buf, size_t count, loff_t *offset){    int ret = 0;    struct _dev_sct *dev = filp->private_data;          // 获取设备结构        if(*offset >= dev->cur_size)                        // 如果偏移量超出当前buf数据范围    {    return ret;    }    if(*offset + count > dev->cur_size)                 // 如果偏移量将会超出当前buf范围    {    count = dev->cur_size - *offset;    }        if(dev->d_minor >= 0 && dev->d_minor < d_count)     // 如果设备的次设备号在允许范围    {    if(copy_to_user(buf, (*dev->kbuf)[dev->d_minor] + *offset, count))    // 如果向用户空间buf传递错误    {    ret = -EFAULT;    }    else                                                                  // 如果向用户空间buf传递成功    {    ret = count;                                                      // 设置返回数为读取的字节个数    dev->cur_size -= count;                                           // 更新buf尺寸    if(dev->cur_size < 0)                                             // 排除buf尺寸异常状况    dev->cur_size = 0;    *offset += count;                                                 // 更新偏移量    P_DEBUG("read %dB, cur_size:[%d]\n", count, dev->cur_size);    }    }    else    ret = -EFAULT;      return ret;}

读函数要介绍的关键是copy_to_user,通过函数名就能猜出它将内存数据从内核空间传递到用户空间,相当于带排错功能的memcpy,其中(*dev->kbuf)将找到对应的二维数组,这个数组第二维元素通过dev->d_minor定位。这也是顺序表的常见分配方法。

89-123

int rw_write(struct file *filp, const char __user *buf, size_t count, loff_t *offset){    int ret = 0;    struct _dev_sct *dev = filp->private_data;        if(*offset >= rw_BUF_SIZE)    {    P_DEBUG("can`t alloc mem count = [%d]\n", count);    return count ? -ENOMEM : 0;    }    if(*offset + count > rw_BUF_SIZE)    {    count = rw_BUF_SIZE - *offset;    }    if(dev->d_minor >= 0 && dev->d_minor < d_count)    {    if(copy_from_user((*dev->kbuf)[dev->d_minor], buf, count))    {    ret = -EFAULT;    }    else    {    ret = count;    dev->cur_size += count;    if(dev->cur_size > rw_BUF_SIZE)    dev->cur_size = rw_BUF_SIZE;    *offset += count;    P_DEBUG("write %dB, cur_size:[%d]\n", count, dev->cur_size);    P_DEBUG("kbuf is [%s]\n", (*dev->kbuf)[dev->d_minor]);    }    }    else    ret = -EFAULT;        return ret;}

write与read相似,重点是copy_from_user和它对偏移量超出buf范围的处理,我来分析下后一个。

为什么偏移量超出buf范围后要错误退出呢? 因为如果还是像read一样返回0的话,虽然不是一个错误,但是它会引起系统对它反复的写重试,这不是我们想看到的,所以在用到阻塞式write以前就让它返回错误吧。

124-163

loff_t rw_llseek(struct file *filp, loff_t offset, int whence){    struct _dev_sct *dev = filp->private_data;    loff_t new_pos;    loff_t old_pos = filp->f_pos;        switch(whence)    {    case SEEK_SET:    new_pos = offset;    break;    case SEEK_CUR:    new_pos = old_pos + offset;    break;    case SEEK_END:    new_pos = dev->cur_size + offset;    break;    default:    P_DEBUG("unknow whence\n");    return -EINVAL;    }        if(new_pos < 0 || new_pos > rw_BUF_SIZE)    {    P_DEBUG("f_pos failed\n");    return -EINVAL;    }        filp->f_pos = new_pos;    return new_pos;}struct file_operations f_opts = {.open = rw_open,.release = rw_close,.read = rw_read,.write = rw_write,.llseek = rw_llseek,};

llseek基本上张一样,不怎么变,它的作用就是更新file里的f_pos文件偏移指针,不多说了。

后面的struct file_operations f_opts对结构体进行初始化就可以在init函数中注册去了。

现在一个完整的简易式scull完成了,make一下就可以装载了,Makefile自己写吧,每个人编译环境不一定一样。

先在root用户下执行insmod scull.ko

没有出错的话,我的debug返回主设备号,或者可以cat /proc/devices 查看主设备号

mknod /dev/myscull0 c 252 0 mknod /dev/filename type major minor

mknod /dev/myscull1 c 255 1

我创建了两个设备文件myscull0 和 myscull1

对这两个文件的写操作可以用echo linjie > /dev/myscull0

读操作可以是 cat /dev/myscull0

都不需要编写app,但是复杂的设备驱动不写app是不行的,所以试试自己写个设备app吧!

上半部分《简单linux字符设备驱动程序与编程小技巧(上)》http://blog.csdn.net/jiebaoabcabc/article/details/19242621

源代码链接http://download.csdn.net/detail/jiebaoabcabc/6926511

最后上一张成功的终端信息,米娜桑 下回见

人若勇敢就是自己最好的朋友

简单linux字符设备驱动程序与编程小技巧(下)

相关文章:

你感兴趣的文章:

标签云: