简单字符设备的驱动源码讲解

#include <linux/module.h>
#include<linux/types.h>
#include<linux/fs.h>
#include<linux/errno.h>
#include<linux/sched.h>
#include<linux/init.h>
#include<linux/cdev.h>
#include<asm/io.h>
#include<asm/uaccess.h>
#include<linux/timer.h>
#include<asm/atomic.h>
#include<linux/slab.h>
#include<linux/device.h>
#include<asm/ioctl.h>
#define CDEVDEMO_MAJOR 255 //一个宏定义 主设备号

static int cdevdemo_major = CDEVDEMO_MAJOR;
static struct class *cdevdemo_class; //为了在release中都destory,所以这里将其定义为一个全局变量
int private_data = 0;
struct cdevdemo_dev //这里将structr cdev放在了另外的一个结构体中,是为了迎合最新写法。也可以不这样做,直接定义为structcdev
{
struct cdev cdev;
};
struct cdevdemo_dev *cdevdemo_devp; //定义了一个指针变量,后面要用kmalloc动态分配内存

int cdevdemo_open(struct inode *inode,struct file *filp) //打开一个字符设备,并传入参数inode和filp;在/dev下每个节点都有一个inode,file在
系统空间调用open时就自动创建,并且传递给每一个操作函数;
{
filp->private_data=&private_data; //通过filp中的private_data来保存自己的data;

printk(KERN_NOTICE "cdevdemo_open");
return 0;
}

int cdevdemo_release(struct inode *inode,struct file *filp) //release函数
{


printk(KERN_NOTICE "cdevdemo_release");
return 0;
}
static ssize_t cdevdemo_read(struct file *filp,char *buf,size_t count,loff_t *ppos) //传入filp ,用户控件的buf地址,读数据的长度
{
int *data = filp->private_data;
if(copy_to_user(buf,data,sizeof(int))) //用户空间从内核空间读数据通过这个函数,从内核空间读一个int长度的data存入到用户空间的buf中
{
return -EFAULT;
}
printk(KERN_NOTICE"cdevdemo_read");
return sizeof(int); //返回读取的字节个数


}
static ssize_t cdevdemo_write(struct file *filp,char *buf,size_t count,loff_t *ppos) //传入filp,用户空间的buf以及写的字节数
{
        int *data = filp->private_data;
        if(copy_from_user(data,buf,sizeof(int))) //向内核空间写数据只能通过这个函数,从用户空间的buf中向内核空间写一个int长度的data
        {
                return -EFAULT;
        }
        printk(KERN_NOTICE"cdevdemo_write");
        return sizeof(int);//返回写的字节数
}

static long cdevdemo_ioctl(struct file *filp,unsigned int cmd,unsigned long arg) //ioctl函数 通过用户空间传入的cmd来判断执行哪种操作
{
        int *data = filp->private_data;
        switch(cmd)
        {
                case 0:printk("ioctl:CDEV_HELLO\n");
                        break;
                case 1:printk("ioctl:CDEV_BYE\n");
                        break;
                case 2:*data=(int)arg;
                        break;
                case 3:return *data;
                        break;
                default:printk("unknow command\n");
        }

}

static const struct file_operations cdevdemo_fops= //内核就是通过这个结构体来找到具体的函数操作
{
        .owner = THIS_MODULE, //意思是这个file_operation指向这个模块 几乎都是这种写法
        .open = cdevdemo_open,
        .release = cdevdemo_release,
        .read = cdevdemo_read,
        .write = cdevdemo_write,
        .unlocked_ioctl=cdevdemo_ioctl,
};


//下面这个函数中基本完成了对cdev的接口调用 从cdev_init-->cdev_add;(注册-->添加),在cdev_init之前要进行cdev内存的分配(在cdevdemo_init中完成)

static void cdevdemo_setup_cdev(struct cdevdemo_dev *dev,int index) //传入含有cdev结构体的指针,以及从设备号;
{
        printk(KERN_NOTICE"cdevdemo_setup_cdev 1");
        int err,devno = MKDEV(cdevdemo_major,index); //通过MKDEV(主设备号,从设备号)来获取设备号
        printk(KERN_NOTICE"cdevdemo_setup_cdev 2");
        cdev_init(&dev->cdev,&cdevdemo_fops); //cdev初始化,完成注册
        printk(KERN_NOTICE"cdevdemo_setup_cdev 3");
        dev->cdev.owner = THIS_MODULE; //指向本模块
        dev->cdev.ops = &cdevdemo_fops; //将file_operation的指针赋给cdev结构体的ops
        printk(KERN_NOTICE"cdevdemo_setup_cdev 4");
        err = cdev_add(&dev->cdev,devno,1); //向内核空间添加cdev,注意传入cdev指针和设备号;添加成功后,可以通过设备号找到cdev,
从而找到file_operation;
        printk(KERN_NOTICE "cdevdemo_setup_cdev 5");
        if(err)
        {
                printk(KERN_NOTICE "Error %d add cdevdemo %d",err,index);
        }
}


int cdevdemo_init(void)
{
        printk(KERN_NOTICE "cdevdemo_init");
        int ret;
        dev_t devno = MKDEV(cdevdemo_major,0); //获取设备号
        if(cdevdemo_major) //判断主设备号是否被占用,若没占用就静态注册设备号,若占用就动态注册设备号
        {
                printk(KERN_NOTICE"cdevdemo_init 1");
                ret = register_chrdev_region(devno,1,"cdevdemo"); //若静态注册失败(可能被占用),再动态注册
        }
        else
        {
                printk(KERN_NOTICE"cdevmo_init 2");
                ret = alloc_chrdev_region(&devno,0,1,"cdevdemo"); //静态注册失败,动态注册
                cdevdemo_major = MAJOR(devno); //获取动态注册设备号的主设备号
        }
 if(ret < 0)//判断设备号是否注册失败
        {
                printk(KERN_NOTICE"cdevdemo_init 3");
                return ret;
        }
        cdevdemo_devp = kmalloc(sizeof(struct cdevdemo_dev),GFP_KERNEL); //动态分配结构体的内存(前面定义的时指针变量)
        if(!cdevdemo_devp) //判断是否分配失败
        {
                ret = -ENOMEM;
                printk(KERN_NOTICE"Error add cdevdemo");
                goto fail_malloc;
        }
        memset(cdevdemo_devp,0,sizeof(struct cdevdemo_dev)); //内存空间初始化,初始化结构体里的每一个变量
        printk(KERN_NOTICE"cdevdemo_init 3");
        cdevdemo_setup_cdev(cdevdemo_devp,0);

cdevdemo_class = class_create(THIS_MODULE,"cdevdemoo"); //创建一个类
        device_create(cdevdemo_class,NULL,MKDEV(cdevdemo_major,0),NULL,"cdevdemoo"); //创建一个节点
        printk(KERN_NOTICE"cdevdemo_init 4");
        return 0;
        fail_malloc:
                unregister_chrdev_region(devno,1);
                cdev_del(&cdevdemo_devp->cdev);
}

static void cdevdemo_exit(void)//退出时进行一些资源的释放
{
        printk(KERN_NOTICE"End cdevdemo");
        dev_t devno = MKDEV(cdevdemo_major,0);
        cdev_del(&cdevdemo_devp->cdev); //释放字符设备结构体
        device_destroy(cdevdemo_class,devno); //销毁节点
        class_destroy(cdevdemo_class);// 销毁class类
        kfree(cdevdemo_devp); // 释放结构
        unregister_chrdev_region(MKDEV(cdevdemo_major,0),1); //释放设备号
}
MODULE_LICENSE("Dual BSD/GPL");
module_param(cdevdemo_major,int,S_IRUGO);
module_init(cdevdemo_init); //驱动入口 动态加载这个函数时 第一个执行的函数
module_exit(cdevdemo_exit);//模块卸载时 最后执行的函数


总结:首先申请主设备号(动态申请或静态申请),通过主设备号和次设备号产生设备号。再通过cdev_init函数完成cdev的注册,再向内核添加注册过的cdev的指针和设备号。通过 device_create函数创建一个节点,创建节点时要将设备号传入。这样以来,创建了节点就能在/dev目录下找到该设备节点,由于设备节点和设备号绑定,cdev结构体和设备号绑定,通过设备节点能找到设备号,再从设备号能找到cdev结构体,cdev结构里定义了file_operations指针,从而完成相应的读写工作。


Makefile文件
ifneq ($(KERNELRELEASE),)
obj-m := mydecice.o
else
KERNELDIR ?= /usr/src/kernels/3.10.0-514.16.1.el7.x86_64
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif

clean:
rm -rf *.o *~core .depend .*.cmd *.ko *.mod.c .tmp_version modules.order Module.symvers




测试程序
int main()
{
        int fd;
        int old_data;
        int new_data;
        if(-1!=(fd=open("/dev/cdevdemoo",O_RDWR)))
        {
                if(-1==read(fd,&old_data,sizeof(int)))
                {
                        printf("read error\n");
                        return -1;
                }
                else
                {
                        printf("old data is %d\n",old_data);
                }
                new_data = ++ old_data %3;
if(-1==write(fd,&new_data,sizeof(int)))
                {
                        printf("write error\n");
                        return -1;
                }

                if(-1==read(fd,&new_data,sizeof(int)))
                {
                        printf("read error\n");
                        return -1;
                }
                else
                {
                        printf("new data is %d\n",new_data);
                }
ioctl(fd,0,0);
                ioctl(fd,1,0);
                ioctl(fd,2,90);
                new_data = ioctl(fd,3,0);
                printf("ioctl : new data is %d\n",new_data);
                ioctl(fd,4,0);
        }
else
        {
                perror("open error\n");
                return -1;
        }
        close(fd);

        return 0;
}






测试
1 make(生成ko文件)


2 insmod (动态加载模块)


3 lsmod | less (查看加载的模块)


4 cd /dev 可以查看到创建的节点(cdevdemo)


5 dmesg 产看内核打印信息


6 编译测试程序 直接gcc即可
7 执行二进制测试程序


可以看到已经完成了 read、write、ioctl操作。
8 dmesg产看内核消息

9 rmmod 卸载内核模块




猜你喜欢

转载自blog.csdn.net/chihunqi5879/article/details/77932448
今日推荐