简单字符设备驱动和自动创建设备文件

简单字符设备驱动和自动创建设备文件

国庆花了两天时间把字符设备驱动重新学习了一下,发现自己之前学的几乎一半都忘得一干二净了,所以决定写成blog,以便以后会用到,也供初学者参考。

[必要的头文件]

/*
*       Asimple character driver for learn
*/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <linux/slab.h>    /* kmalloc */
#include <linux/device.h>         /* device_create, class_create */

[宏定义]

#define MAX_SIZE   1024
#define SIMPLE_MAJOR 256
#define CLASS_NAME "simple_class"
#define CHRDEV_NAME"simple_chrdev"

[定义simple_chrdev结构体来表示一个简单的字符设备]

struct simple_chrdev {
         structcdev cdev;
         charmem[MAX_SIZE];
};

struct simple_chrdev *dev;
static unsigned int major_no = 0;
static struct class *simple_class;

[open方法]

static int simple_open(struct inode *inode,struct file *filp)
{
         structsimple_chrdev *dev;
 
         dev= container_of(inode->i_cdev, struct simple_chrdev, cdev);
         filp->private_data= dev;
         return0;
}

填写filp->private_data里的数据结构,后面的read,write等方法可能会用到

container_of(pointer, containter_type, container_field):inode参数在其i_cdev字段中包含了我们所需要的信息,即我们先前设置的cdev。然而我们不需要cdev结构体本身,而是希望得到包含cdev结构体的simple_chrdev结构体。


[release 方法]

static int simple_release(struct inode*inode, struct file *filp)
{
         return0;
}

[read 方法]

static ssize_t simple_read(struct file*filp, char __user *buf, size_t count,
                                                                 loff_t*f_pos)
{
         structsimple_chrdev *dev = filp->private_data;
         unsignedlong pos = *f_pos;
         intret = 0;
 
         if(pos >= MAX_SIZE)
                   return- EFAULT;
 
         if(count > MAX_SIZE - pos)
                   count= MAX_SIZE - pos;
        
         if(copy_to_user(buf, (void *)(dev->mem + pos), count)) {
                   ret= -EFAULT;
                   gotoout;
         }
         *f_pos+= count;
         ret= count;
 
out:
         returnret;
}


[write 方法]

static ssize_t simple_write(struct file*filp, const char __user *buf,
                                     size_tcount, loff_t *f_pos)
{
         structsimple_chrdev *dev = filp->private_data;
         unsignedlong pos = *f_pos;
         intret = 0;
 
         if(pos >= MAX_SIZE)
                   return- EFAULT;
 
         if(count > MAX_SIZE - pos)
                   count= MAX_SIZE - pos;
 
         if(copy_from_user(dev->mem + pos, buf, count)) {
                   ret= - EFAULT;
                   gotoout;
         }
         *f_pos+= count;
         ret= count;
 
out:
         returnret;
}

read和write代码要做的工作就是在用户地址空间和内核地址空间之间进行整段数据的拷贝。注意:buf参数是用户空间的指针,内核代码不能直接引用其中的内容。

unsigned long copy_to_user(void __user *to,  const void * from, unsigned long count)

unsigned long copy_from_user(void *to,  const void __user *from, unsigned long count)

返回值:复制成功时,返回0;失败时,返回未复制的字节数。

这两个函数并不限于在内核空间和用户空间之间拷贝数据,它们还会检查用户空间的指针是否有效。


[llseek 方法]

loff_t simple_llseek(struct file *filp,loff_t off, int whence)
{
         loff_tnewpos;
 
         switch(whence) {
                   case0:     /*SEEK_SET*/
                            newpos= off;
                            break;
                  
                   case1:     /*SEEK_CUR*/
                            newpos= filp->f_pos + off;
                            break;
 
                   case2:     /*SEEK_END*/
                            newpos= MAX_SIZE + off;
                            break;
                  
                   default:
                            return-EINVAL;
         }
 
         if(newpos < 0)
                   return-EINVAL;
         filp->f_pos= newpos;
         returnnewpos;
}
 

struct file_operations simple_fops = {
         .owner     = THIS_MODULE,
         .llseek       = simple_llseek,
         .read         = simple_read,
         .write       = simple_write,
         .open        = simple_open,
         .release   = simple_release,
};


[模块初始化函数]

static int __init simple_chrdev_init(void)
{
         intminor_no = 0;     /* 次设备号 */
         dev_tdev_no;     /* 设备号 */
         intret;
 
         if(major_no) {
                   dev_no= MKDEV(major_no, minor_no);
                   ret= register_chrdev_region(dev_no, 1, CLASS_NAME);
         }else {
                   ret= alloc_chrdev_region(&dev_no, minor_no, 1, CHRDEV_NAME);
                   major_no= MAJOR(dev_no);
         }
         if(ret) {
                   printk("cannotget major %d\n", major_no);
                   returnret;
         }
         printk("getmajor %d\n", major_no);
 
         dev= kmalloc(sizeof(struct simple_chrdev), GFP_KERNEL);
         if(dev == NULL) {
                   ret= - ENOMEM;
                   gotoerr_malloc;
         }
         memset(dev,0, sizeof(struct simple_chrdev));
 
         cdev_init(&dev->cdev,&simple_fops);
         dev->cdev.owner= THIS_MODULE;
         dev->cdev.ops= &simple_fops;
         ret= cdev_add(&dev->cdev, dev_no, 1);
         if(ret) {
                   printk("cannotcdev add\n");
                   gotoerr_cdev_add;
         }
 
         /*autocreate device inode file*/
         simple_class= class_create(THIS_MODULE, CHRDEV_NAME);
         if(IS_ERR(simple_class)) {
                   printk("ERR:cannot create a simple_class");
                   gotoerr_class_crt;
         }
         device_create(simple_class,NULL, MKDEV(major_no, 0), 0, CHRDEV_NAME);
 
         printk("Iam in\n");
         return 0; /* 这个return 0;不能少 */

err_class_crt:
         cdev_del(&dev->cdev);
err_cdev_add:
         kfree(dev);
err_malloc:
         unregister_chrdev_region(MKDEV(major_no,0), 1);
         returnret;
}

模块加载函数所做的工作:

1、申请主设备号;

静态分配设备号  int register_chrdev_region(dev_t dev_no, unsigned int count, char *dev_name);

动态分配设备号  int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *dev_name);

2、注册字符设备;

初始化字符设备 cdev_init();

注册字符设备   cdev_add();

3、创建设备节点文件

当使用udev制作的文件系统时,内核为我们提供了一组函数,可以用来在模块加载的时候自动在/dev目录下创建设备文件OR在卸载时自动删除设备文件。

主要涉及两个函数

class_create(owner, class_name) 

用来在sysfs创建一个类,设备信息导出在这下面;

struct device *device_create(struct class *class, struct device *parent,
    dev_t devt, void *drvdata, const char *fmt, ...)

用来在/dev目录下创建一个设备文件;

加载模块时,用户空间的udev会自动响应device_create()函数,在sysfs下寻找对应的类从而创建设备节点。


[模块卸载函数]

static void __exit simple_chrdev_exit(void)
{
         device_destroy(simple_class,MKDEV(major_no, 0));
         class_destroy(simple_class);
         cdev_del(&dev->cdev);
         kfree(dev);
         unregister_chrdev_region(MKDEV(major_no,0), 1);
         printk("Iam exit\n");
}
device_destroy函数用来注销和删除设备节点文件,作用与device_create相反
注意:卸载函数顺序必须加载函数顺序相反

module_init(simple_chrdev_init);
module_exit(simple_chrdev_exit);
 
MODULE_AUTHOR("CJOK<[email protected]>");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("A simple characterdriver for learn");


if you have any questions, please contact me<[email protected]> or leave a comment, we will exchange views, it's good for us, so great!




你可能感兴趣的:(简单字符设备驱动和自动创建设备文件)